PHP how to show all decimal places? - php

this might be a stupid question but I have searched again and again without finding any results.
So, what I want is to show all the decimal places of a number without knowing how many decimal places it will have. Take a look at this small code:
$arrayTest = array(0.123456789, 0.0123456789);
foreach($arrayTest as $output){
$newNumber = $output/1000;
echo $newNumber;
echo "<br>";
}
It gives this output:
0.000123456789
1.23456789E-5
Now, I tried using 'number_format', but I don't think that is a good solution. It determines an exact amount of decimal places, and I do not know the amount of decimal places for every number. Take a look at the below code:
$arrayTest = array(0.123456789, 0.0123456789);
foreach($arrayTest as $output){
$newNumber = $output/1000;
echo number_format($newNumber,13);
echo "<br>";
}
It gives this output:
0.0001234567890
0.0000123456789
Now, as you can see there is an excess 0 in the first number, because number_format forces it to have 13 decimal places.
I would really love some guidance on how to get around this problem. Is there a setting in PHP.ini which determines the amount of decimals?
Thank you very much in advance!
(and feel free to ask if you have any further questions)

It is "impossible" to answer this question properly - because a binary float representation of a decimal number is approximate: "What every computer scientist should know about floating point"
The closest you can come is write yourself a routine that looks at a decimal representation of a number, and compares it to the "exact" value; once the difference becomes "small enough for your purpose", you stop adding more digits.
This routine could then return the "correct number of digits" as a string.
Example:
<?php
$a = 1.234567890;
$b = 0.123456789;
echo returnString($a)."\n";
echo returnString($b)."\n";
function returnString($a) {
// return the value $a as a string
// with enough digits to be "accurate" - that is, the value returned
// matches the value given to 1E-10
// there is a limit of 10 digits to cope with unexpected inputs
// and prevent an infinite loop
$conv_a = 0;
$digits=0;
while(abs($a - $conv_a) > 1e-10) {
$digits = $digits + 1;
$conv_a = 0 + number_format($a, $digits);
if($digits > 10) $conv_a = $a;
}
return $conv_a;
}
?>
Which produces
1.23456789
0.123456789
In the above code I arbitrarily assumed that being right to within 1E-10 was good enough. Obviously you can change this condition to whatever is appropriate for the numbers you encounter - and you could even make it an optional argument of your function.
Play with it - ask questions if this is not clear.

Related

Why PHP number_format() change the number? [duplicate]

I am working with huge numbers for website purposes and I need long calculation. When I echo a long number I don't get the correct output.
Example
// A random number
$x = 100000000000000000000000000;
$x = number_format($x);
echo "The number is: $x <br>";
// Result: 100,000,000,000,000,004,764,729,344
// I'm not getting the value assigned to $x
Your number is actually too big for php standard integers. php uses 64 bit integers which can hold values within range -9223372036854775808 (PHP_INT_MIN)
to +9223372036854775807 (PHP_INT_MAX).
Your number is about 87 bits long which simply is too much.
If you really need such big numbers you should use the php BC math types, explained in the manual: http://php.net/manual/en/ref.bc.php
If you just want to format a string formed like a huge number then use something like this:
function number_format_string($number) {
return strrev(implode(',', str_split(strrev($number), 3)));
}
$x = '100000000000000000000000000';
$x = number_format_string($x);
echo "The number is: $x\n";
// Output: The number is: 100,000,000,000,000,000,000,000,000
Edit:
Added strrev() to function because the string needs to be reversed before splitting it up (thanks to #ceeee for the hint). This ensures that the delimiter is placed at right position when length of input is not divisible by 3. Generated string needs to be reversed afterwards again.
Working example can be found at http://sandbox.onlinephpfunctions.com/code/c10fc9b9e2c65a27710fb6be3a0202ad492e3e9a
answer #maxhb has bug. if the input is '10000000000000000000000' the out put would be:
The number is: 100,000,000,000,000,000,000,00
Which is incorrect. So try below code:
function number_format_string($number, $delimeter = ',')
{
return strrev(implode($delimeter, str_split(strrev($number), 3)));
}
$x = '10000000000000000000000';
$x = number_format_string($x);
echo "The number is: $x\n";
// Output: The number is: 10,000,000,000,000,000,000,000
The largest integer that can be represented in a 64bit PHP install, compared to your number:
9,223,372,036,854,775,808 - largest possible signed 64bit integer
100000000000000000000000000 - your number
since you're exceeding the maximum number size, you can't expect to get useful results without using something like gmp/bcmath.
PHP does not yet support formatting long numbers, even when you always keep them as strings in your code (to avoid issues with PHP’s int type):
php > echo number_format('100000000000000000000000000');
100,000,000,000,000,004,764,729,344
php > echo number_format('3.14159265358979323846', 20);
3.14159265358979311600
The underlying ICU library supports formatting arbitrary precision decimal numbers, but PHP doesn’t use the relevant function yet – see request #76093.
http://php.net/manual/en/function.number-format.php
That is the solution:
<?php
# Output easy-to-read numbers
# by james at bandit.co.nz
function bd_nice_number($n) {
// first strip any formatting;
$n = (0+str_replace(",","",$n));
// is this a number?
if(!is_numeric($n)) return false;
// now filter it;
if($n>1000000000000) return round(($n/1000000000000),1).' trillion';
else if($n>1000000000) return round(($n/1000000000),1).' billion';
else if($n>1000000) return round(($n/1000000),1).' million';
else if($n>1000) return round(($n/1000),1).' thousand';
return number_format($n);
}
?>

Converting an int that is more than 16 digits long into a string in php

Is there any function that easily echos an integer that is 15+ digits long?
The only way I've managed is like this:
$num = 123456789012345;
$num = number_format($num);
$num = str_replace(',', '', $num);
echo $num;
But even this way it is only accurate up to 17 digits. After the 16th digit the number isn't printed accurately (because as a float it starts getting inaccurate - see here).
EDIT: From the answers below I wrote ini_set('precision',40); and then echoed $num straight. All this did was to, simply put, not show the decimal point in the float number. And again after the 16th digit it starts getting inaccurate.
I also tried the other suggestion of changing it into an array and then iterating through it with str_split($num); and again the numbers were inaccurate from the 17th digit on!
The simplest solution would be to convert the integer into a string. I've tried:
$num = (string)$num;
//and
$num = strval($num);
But neither change anything and they act as if as they remained as an int??
My question is specifically why are the conversions into strings not working. Is there a way to turn the number into a string? Thanks
The only solution I can think of is changing the precision of floats in the php.ini
ini_set('precision', 25);
I don't know where you get those large numbers from, but I'd suggest a look into bc functions too!
The last thing I thought of is using the explode function to split the string into an array and interate through it.
EDIT: When all suggestions failed, your only choices are to check out the BC Math and/or GMP functions as well as MoneyMath. The BigInteger package should also do the trick, which uses GMP and BC.
Well, you see, it's not an "int" as you claimed :)
echo PHP_INT_MAX; // echoes 9223372036854775807
$n = 9223372036854775807;
echo $n; // echoes 9223372036854775807
$n = 9223372036854775808;
echo $n; // echoes 9.2233720368548E+18
Setting precision to something greater, as manniL said, does the trick.
ini_set("precision", 50);
$n = 9223372036854775808;
echo $n; // echoes 9223372036854775808

PHP: How to raise number to (tiny) fractional exponent?

I'm doing a calculation in PHP using bcmath, and need to raise e by a fractional exponent. Unfortunately, bcpow() only accepts integer exponents. The exponent is typically higher precision than a float will allow, so normal arithmetic functions won't cut it.
For example:
$e = exp(1);
$pow = "0.000000000000000000108420217248550443400745280086994171142578125";
$result = bcpow($e, $pow);
Result is "1" with the error, "bc math warning: non-zero scale in exponent".
Is there another function I can use instead of bcpow()?
Your best bet is probably to use the Taylor series expansion. As you noted, PHP's bcpow is limited to raising to integer exponentiation.
So what you can do is roll your own bc factorial function and use the wiki page to implement a Taylor series expansion of the exponential function.
function bcfac($num) {
if ($num==0) return 1;
$result = '1';
for ( ; $num > 0; $num--)
$result = bcmul($result,$num);
return $result;
}
$mysum = '0';
for ($i=0; $i<300; $i++) {
$mysum = bcadd($mysum, bcdiv(bcpow($pow,$i), bcfac($i)) );
}
print $mysum;
Obviously, the $i<300 is an approximation for infinity... You can change it to suit your performance needs.
With $i=20, I got
1.00000000000000000010842021724855044340662275184110560868263421994092888869270293594926619547803962155136242752708629105688492780863293090291376157887898519458498571566021915144483905034693109606778068801680332504212458366799913406541920812216634834265692913062346724688397654924947370526356787052264726969653983148004800229537555582281617497990286595977830803702329470381960270717424849203303593850108090101578510305396615293917807977774686848422213799049363135722460179809890014584148659937665374616
This is comforting since that small of an exponent should yield something really close to 1.0.
Old question, but people might still be interested nonetheless.
So Kevin got the right idea with the Taylor-polynomial, but when you derive your algorithm from it directly, you can get into trouble, mainly your code gets slow for long input-strings when using large cut-off values for $i.
Here is why:
At every step, by which I mean with each new $i, the code calls bcfac($i). Everytime bcfac is called it performs $i-1 calculations. And $i goes all the way up to 299... that's almost 45000 operations! Not your quick'n'easy floating point operations, but slow BC-string-operations - if you set bcscale(100) your bcmul has to handle up to 10000 pairs of chars!
Also bcpow slows down with increasing $i, too. Not as much as bcfac, because it propably uses something akin to the square-and-multiply method, but it still adds something.
Overall the time required grows quadraticly with the number of polynomial terms computed.
So... what to do?
Here's a tip:
Whenever you handle polynomials, especially Taylor-polynomials, use the Horner method.
It converts this: exp(x) = x^0/0! + x^1/1! + x^2/2! + x^3/3! + ...
...into that: exp(x) = ((( ... )*x/3+1 )*x/2+1 )*x/1+1
And suddenly you don't need any powers or factorials at all!
function bc_exp($number) {
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $number), 1);
return $result;
}
This needs only 3 bc-operations for each step, no matter what $i is.
With a starting value of $i=299 (to calculate exp with the same precision as kevin's code does) we now only need 897 bc-operations, compared to more than 45000.
Even using 30 as cut-off instead of 300, we now only need 87 bc-operations while the other code still needs 822 for the factorials alone.
Horner's Method saving the day again!
Some other thoughts:
1) Kevin's code would propably crash with input="0", depending on how bcmath handles errors, because the code trys bcpow(0,0) at the first step ($i=0).
2) Larger exponents require longer polynomials and therefore more iterations, e.g. bc_exp(300) will give a wrong answer, even with $i=299, whyle something like bc_exp(3) will work fine and dandy.
Each term adds x^n/n! to the result, so this term has to get small before the polynomial can start to converge. Now compare two consecutive terms:
( x^(n+1)/(n+1)! ) / ( x^n/n! ) = x/n
Each summand is larger than the one before by a factor of x/n (which we used via the Horner method), so in order for x^(n+1)/(n+1)! to get small x/n has to get small as well, which is only the case when n>x.
Inconclusio: As long as the number of iterations is smaller than the input value, the result will diverge. Only when you add steps until your number of iterations gets larger than the input, the algorithm starts to slowly converge.
In order to reach results that can satisfie someone who is willing to use bcmath, your $i needs to be significantly larger then your $number. And that's a huge proplem when you try to calculate stuff like e^346674567801
A solution is to divide the input into its integer part and its fraction part.
Than use bcpow on the integer part and bc_exp on the fraction part, which now converges from the get-go since the fraction part is smaller than 1. In the end multiply the results.
e^x = e^(intpart+fracpart) = e^intpart * e^fracpart = bcpow(e,intpart) * bc_exp(fracpart)
You could even implement it directly into the code above:
function bc_exp2($number) {
$parts = explode (".", $number);
$fracpart = "0.".$parts[1];
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $fracpart), 1);
$result = bcmul(bcpow(exp(1), $parts[0]), $result);
return $result;
}
Note that exp(1) gives you a floating-point number which propably won't satisfy your needs as a bcmath user. You might want to use a value for e that is more accurate, in accordance with your bcscale setting.
3) Talking about numbers of iterations: 300 will be overkill in most situations while in some others it might not even be enough. An algorithm that takes your bcscale and $number and calculates the number of required iterations would be nice. Alraedy got some ideas involving log(n!), but nothing concrete yet.
4) To use this method with an arbitrary base you can use a^x = e^(x*ln(a)).
You might want to divide x into its intpart and fracpart before using bc_exp (instead of doing that within bc_exp2) to avoid unneccessary function calls.
function bc_pow2($base,$exponent) {
$parts = explode (".", $exponent);
if ($parts[1] == 0){
$result = bcpow($base,$parts[0]);
else $result = bcmul(bc_exp(bcmul(bc_ln($base), "0.".$parts[1]), bcpow($base,$parts[0]);
return result;
}
Now we only need to program bc_ln. We can use the same strategy as above:
Take the Taylor-polynomial of the natural logarithm function. (since ln(0) isn't defined, take 1 as developement point instead)
Use Horner's method to drasticly improve performance.
Turn the result into a loop of bc-operations.
Also make use of ln(x) = -ln(1/x) when handling x > 1, to guarantee convergence.
usefull functions(don't forget to set bcscale() before using them)
function bc_fact($f){return $f==1?1:bcmul($f,bc_fact(bcsub($f, '1')));}
function bc_exp($x,$L=50){$r=bcadd('1.0',$x);for($i=0;$i<$L;$i++){$r=bcadd($r,bcdiv(bcpow($x,$i+2),bc_fact($i+2)));}return $r;}#e^x
function bc_ln($x,$L=50){$r=0;for($i=0;$i<$L;$i++){$p=1+$i*2;$r = bcadd(bcmul(bcdiv("1.0",$p),bcpow(bcdiv(bcsub($x,"1.0"),bcadd($x,"1.0")),$p)),$r);}return bcmul("2.0", $r);}#2*Sum((1/(2i+1))*(((x-1)/x+1)^(2i+1)))
function bc_pow($x,$p){return bc_exp(bcmul((bc_ln(($x))), $p));}

Floating point rounding error php ...how can I make sure it works correctly?

I have the following function that determines if I sale is fully paid for. I don't remember why I did it this way, but it has been working so far and I don't remember why I had to do it this way.
function _payments_cover_total()
{
//get_payments is a list of payment amounts such as:
//10.20, 10.21, or even 10.1010101101 (10 decimals max)
$total_payments = 0;
foreach($this->sale_lib->get_payments() as $payment)
{
$total_payments += $payment['payment_amount'];
}
//to_currency_no_money rounds total to 2 decimal places
if (to_currency_no_money($this->sale_lib->get_total()) - $total_payments ) > 1e-6 ) )
{
return false;
}
return true;
}
I am wondering if there is ever a case where due to a rounding error that this function would return false when it shouldn't.
The main part I have a question about is:
> 1e-6
I think before I had, but it was causing problems in some cases.
> 0
I think you are doing what is mentioned on php floating help page. To quote it directly :
To test floating point values for equality, an upper bound on the
relative error due to rounding is used. This value is known as the
machine epsilon, or unit roundoff, and is the smallest acceptable
difference in calculations.
$a and $b are equal to 5 digits of precision.
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}
?>
So in your case:
(to_currency_no_money($this->sale_lib->get_total()) - $total_payments) > 1e-6
relative error due to rounding should not be great than 1e-6 or 0.000001
if you are not sure about left operand being greater than right 100% time,then you should add abs() e.g for correctness.
$relative_error=to_currency_no_money($this->sale_lib->get_total()) - $total_payments;
if(abs($relative_error) > 1e-6){
return false
}
return true;
$x = (1.333-1.233)-(1.334-1.234);
echo $x;
//result = $x = -2.2204460492503E-16 - close to zero
//but (1.333-1.233)-(1.334-1.234) = 0.1 - 0.1 = 0 (in calculator)
if($x === 0){
echo "|zero";
}
else {
echo "|non zero"; //<== this is result
}
//screen = -2.2204460492503E-16|non zero
//how to get to zero?
if($x > 1e-6){//1e-6 mathematical constant
echo "|non zero";
}
else {
echo "|zero";//this is result
}
//screen -2.2204460492503E-16|non zero|zero
if ($x > 1e-6 )
{
echo " false";
//echo "|non zero";
//return false;
}
else{
echo " true";//<== this resut
//echo "|zero";
//return true;
}
//screen -2.2204460492503E-16|non zero|zero true
printf("%.1f<br />", 1e-1);
printf("%.2f<br />", 1e-2);
printf("%.3f<br />", 1e-3);
printf("%.4f<br />", 1e-4);
printf("%.5f<br />", 1e-5);
printf("%.6f<br />", 1e-6);
printf("%.7f<br />", 1e-7);
printf("%.8f<br />", 1e-8);
printf("%.9f<br />", 1e-9);
printf("%.10f<br />", 1e-10);
printf("%.11f<br />", 1e-11);
printf("%.12f<br />", 1e-12);
printf("%.29f<br />", -2.2204460492503E-16);
//0.1
//0.01
//0.001
//0.0001
//0.00001
//0.000001
//0.0000001
//0.00000001
//0.000000001
//0.0000000001
//0.00000000001
//0.000000000001
//-0.00000000000000022204460492503
I am sorry, but when dealing with currency, you shouldn't really be using PHP floats, as IMSoP stated. The reason is also from PHP float help pages:
Additionally, rational numbers that are exactly representable as
floating point numbers in base 10, like 0.1 or 0.7, do not have an
exact representation as floating point numbers in base 2, which is
used internally, no matter the size of the mantissa. Hence, they
cannot be converted into their internal binary counterparts without a
small loss of precision. This can lead to confusing results: for
example, floor((0.1+0.7)*10) will usually return 7 instead of the
expected 8, since the internal representation will be something like
7.9999999999999991118....
So never trust floating number results to the last digit, and do not
compare floating point numbers directly for equality. If higher
precision is necessary, the arbitrary precision math functions and gmp
functions are available.
Note that the help page specifically says you can't trust float results to the last digit, no matter how short (after comma) it is.
So while you do have very short floats (just 2 digits after comma), the float precision (1e-6) doesn't enter into it really, and you can't trust them 100%.
Since it is a question of money, in order to avoid angry customers and lawsuits accusing of penny shaving (https://en.wikipedia.org/wiki/Salami_slicing), the real solutions are:
1) either use PHP BC math, which works with string representation of numbers
http://php.net/manual/en/book.bc.php
2) as IMSoP suggested, use integers and store the amounts in smallest denomination (cents, eurocents or whatever you have) internally.
First solution might be a bit more resource intense: I haven't used BC math myself much, but storing strings and doing arbitrary precision math (which might be a bit of an overkill in this case) are by definition more RAM and CPU intense than working with integers.
It might, however, need less changes in other parts of the code.
The second solution requires changes to represenation, so that wherever user sees the amounts, they are normalized to dollars,cents (or whatever have you).
Note, however, that in this case also you run problems with rounding risks at some point, as when you do:
float shown_amount; // what we show to customer: in dollars,cents
int real_amount; // what we use internally: in cents
shown_amount = cent_amount / 100;
you may reintroduce the rounding problems as you have floats again and possibilities for rounding errors, so tread carefully and be sure to make calculations and roundings only on real_amount, never on shown_amount

Issue with float multiplication and evaluation

This problem is best expressed in code:
$var1 = 286.46; // user input data
$var2 = 3646; // user input data
$var3 = 25000; // minumum amount allowed
$var4 = ($var1 * 100) - $var2; // = 250000
if ($var4 < $var3) { // if 250000 < 250000
print 'This returns!';
}
var_dump($var4) outputs: float(25000) and when cast to int, outputs: int(24999) - and thereby lies the problem.
I don't really know what to do about it though. The issue occurs upon multiplication by 100, and while there are little tricks I can do to get around that (such as *10*10) I'd like to know if there's a 'real' solution to this problem.
Thanks :)
This is a horrible hacky solution and I slightly hate myself for it, but this gives the expected behaviour:
<?php
$var1 = 286.46; // user input data
$var2 = 3646; // user input data
$var3 = 25000; // minumum amount allowed
$var4 = ($var1 * 100) - $var2; // = 250000
if ((string) $var4 < (string) $var3) { // if 250000 < 250000
print 'This returns!';
}
Cast them to strings, and they get converted back to int/float as appropriate for the comparison. I don't like it but it does work.
Really you need BC Math for precise floating point mathematics in PHP.
Its always a good idea to use ceil (or floor based on what you want) when using float number as int
In your case try ceil($var4) before comparison!
That's what floats do sometimes, it is all due to how floats are unable to precisely represent integers from time to time.
Instead of casting it to an int, you can round the number to an integer value and then cast it to an int. (possibly that cast unnecessary, but PHP isn't to clear about how such things happen internally, and even if you know how they happen right now, they may not in the future.
I think you could use bccomp for comparing floating point values but i think it's a function that's not in the PHP Core.
Otherwise i found this function here but i couldn't test it to see if it works
function Comp($Num1,$Num2,$Scale=null) {
// check if they're valid positive numbers, extract the whole numbers and decimals
if(!preg_match("/^\+?(\d+)(\.\d+)?$/",$Num1,$Tmp1)||
!preg_match("/^\+?(\d+)(\.\d+)?$/",$Num2,$Tmp2)) return('0');
// remove leading zeroes from whole numbers
$Num1=ltrim($Tmp1[1],'0');
$Num2=ltrim($Tmp2[1],'0');
// first, we can just check the lengths of the numbers, this can help save processing time
// if $Num1 is longer than $Num2, return 1.. vice versa with the next step.
if(strlen($Num1)>strlen($Num2)) return(1);
else {
if(strlen($Num1)<strlen($Num2)) return(-1);
// if the two numbers are of equal length, we check digit-by-digit
else {
// remove ending zeroes from decimals and remove point
$Dec1=isset($Tmp1[2])?rtrim(substr($Tmp1[2],1),'0'):'';
$Dec2=isset($Tmp2[2])?rtrim(substr($Tmp2[2],1),'0'):'';
// if the user defined $Scale, then make sure we use that only
if($Scale!=null) {
$Dec1=substr($Dec1,0,$Scale);
$Dec2=substr($Dec2,0,$Scale);
}
// calculate the longest length of decimals
$DLen=max(strlen($Dec1),strlen($Dec2));
// append the padded decimals onto the end of the whole numbers
$Num1.=str_pad($Dec1,$DLen,'0');
$Num2.=str_pad($Dec2,$DLen,'0');
// check digit-by-digit, if they have a difference, return 1 or -1 (greater/lower than)
for($i=0;$i<strlen($Num1);$i++) {
if((int)$Num1{$i}>(int)$Num2{$i}) return(1);
else
if((int)$Num1{$i}<(int)$Num2{$i}) return(-1);
}
// if the two numbers have no difference (they're the same).. return 0
return(0);
}
}
}
The problem is that floats just cannot represent some numbers. Since PHP doesn't have a "decimal" (or other fixed-point) type, you can basically only hack your way around these problems.
Assuming the first number in your example $var1 = 286.46 denotes some kind of money, you could just convert that to cents directly after the user entered it (e.g. through stripping the point and reading it as an integer) and thus calculate everything using integer math.
That's not a general solution - and I doubt that one exists (short of using arbitrary precision numbers, which some PHP extensions provide - but I that smells like overkill to me).

Categories