PHP BIGINT representation - php

I'm experimenting with PHP representation of BIGINT values (which are keys in tables), and to test how PHP handles large numbers as strings/float values i wrote a tiny test:
<?php
echo "PHP_INT_MAX=".PHP_INT_MAX."\n";
$x = "9223372036854775107";
echo "Defining x as : 9223372036854775107\n";
$y = floatval($x);
echo "float of x: ".$y."\n";
echo "float to string using strval: ".strval($y)."\n";
echo "float to string using sprintf: ".sprintf( "%.0f", $y)."\n";
?>
So I'm curious about the output:
PHP_INT_MAX=9223372036854775807
Defining x as : 9223372036854775107
float of x: 9.2233720368548E+18
float to string using strval: 9.2233720368548E+18
float to string using sprintf: 9223372036854774784
So why am I getting values which don't match? (precision in php.ini file = 14)

It's all about float type. PHP uses common standart for it IEEE 754.
Float size is 64. On 64 system integer size is 64 too.
But max float number without fractional part without loss of precision is 9007199254740991. Numbers more than those lose their precision, because of format storing float numbers.
Between 2^52=4,503,599,627,370,496 and 2^53=9,007,199,254,740,992 the representable numbers are exactly the integers. For the next range, from 2^53 to 2^54, everything is multiplied by 2, so the representable numbers are the even ones, etc. Conversely, for the previous range from 2^51 to 2^52, the spacing is 0.5, etc.
The spacing as a fraction of the numbers in the range from 2^n to 2^n+1 is 2^n−52. The maximum relative rounding error when rounding a number to the nearest representable one (the machine epsilon) is therefore 2^−53.
Double-precision floating-point format

Related

PHP cast float to string is different from cast to int

Something you would not expect but need to be aware of when you're dealing with floating point numbers in php
<?php
$i = (32.87*100);
echo $i; // outputs 3287
echo (int) $i; // outputs 3286 !!
echo (int) (string) $i // outputs 3287
Internal representation of $i is something like 3286.9999999.
Why is the string representation of $i 3287 ?
Let's go through your code:
$i = (32.87*100);
Now $i is slightly less than 3287 as float as shown below:
echo sprintf('%.30f', $i) . PHP_EOL; //3286.999999999999545252649113535881
But when you print (echo) it, you'll get rounded value.
echo $i; // outputs 3287
And here we come to the trick - casting float to int means to simply cut off the part after dot, despite its .99999999(...) which is almost 1 (but it's not!). So the output is 3286.
echo (int) $i; // outputs 3286 !!
Now, in the last example, you first cast float to string, which means exactly what you already did by doing echo $i; because whatever you print, internally PHP need to cast to string. So it's 3286.999999999999545252649113535881 casted to "3287" and then casted to 3287, and then printed.
echo (int) (string) $i // outputs 3287;
To sum up, it's difference between the way float is casted to string and int.
EDIT Further explanation about "rounding"
Well it's not really rounding. I've made a mistake by saying that.
PHP uses 64 bit float (do called double), which in decimal representation has 14 digit precision.
As mentioned in PHP manual:
The size of a float is platform-dependent, although a maximum of approximately 1.8e308 with a precision of roughly 14 decimal digits is a common value (the 64 bit IEEE format).
That means, that a float can contain (for most of the time) a 14-digit number (in decimal) and it doesn't matter where the dot is placed.
Now, the most important thing:
Casting to string doesn't round the float number
Examples:
$a = 1.23456789012349 - the last 9 is 15th digit, so you'll get "rounded" float to 1.2345678901235
$a = 12345678901234.9 - same as above
$a = 1.99999999999999 - last 9 is 15th digit, so you'll get 2
And as a string it will be printed exactly as the float is, which means 14 digits precision. The "rounding" is at the moment when we create float variable's structure in memory.
The last example is what we're talking about in this topic.
Now, why I did that mistake and said about "rounding"?
I misunderstood the result of echo sprintf('%.30f', $i). A saw many more digits and thought it's the real value of the float number.
But it's not.
As we know, 64-bit float has only 14 digits precision.
So where the result of sprintf comes from?
The answer is actually pretty easy.
We already know that it's not always possible to express a decimal number in binary system. So for example a simple 0.1 in float (binary representation) is just an approximation because the real binary representation would be infinitely long.
Now it works exactly the same when converting binary system to decimal. What can be expressed in binary (which means every float value), not always is possible to express in decimal.
So what sprintf('%.30f', $i) is to give the 30-digit precision approximation of converting the float number from binary to decimal system.
Thanks to #Quasimodo'sclone for asking in comment for being more precise about this. That made me go a little deeper in this topic.
You're casting $i (3287) to a string and then to an int, so the result stays 3287.
If you cast $i to an int you'll get 3286, and then if you cast it to a string you'll have what you want.
Try echo (string) (int) $i

PHP incorrect precision while rounding off number

I need to round some numbers and get modifying value.
I've tried:
$Num=12.456;
$RoundDiff=$Num-round($Num,2);
$Num=round($Num,2);
echo $Num.'<br/>'.$RoundDiff;
but I've got:
12.46
-0.0040000000000013
while my expected result was:
12.46
-0.004
What's wrong?
How can I get what I need?
Try below code. It will give you the expected result. I have just rounded off the difference.
$Num = 12.456;
$RoundDiff = $Num-round($Num,2);
$Num = round($Num,2);
echo $Num.'<br/>'.round($RoundDiff,3);
CodePad
There is issue in precision of floating point values. Refer this article for reference - The PHP floating point precision is wrong by default
If you want exact precision you can use bcmath or gmp.
Because internally, computers use a format (binary floating-point)
that cannot accurately represent a number like 0.1, 0.2 or 0.3 at all.
When the code is compiled or interpreted, your “0.1” is already
rounded to the nearest number in that format, which results in a small
rounding error even before the calculation happens. — floating point
guide
Another Reference :
Given that the implicit precision of a (normal) IEEE 754 double
precision number is slightly less than 16 digits 3, this is a
serious overkill. Put another way, while the mantissa is composed of
52 bits plus 1 implicit bit, 100 decimal digits can carry up to
100*log2(10) =~ 332 bits of information, around 6 times more.
Given this, I propose changing the default precision to 17 (while the
precision is slightly less than 16, a 17th digit is necessary because
the first decimal digit carries little information when it is low). —
source
BCMATH : As requested in comments
$a = 12.456;
$b = round($a,2);
echo 'a ='.$a.'<br>';
echo 'b ='.$b.'<br>';
echo bcsub($a, $b, 3);

PHP casting float to int returns different value

When I'm executing the following code in PHP (v5.5.9) something unexpected happens:
$valueAsCents = 54780 / 100 * 100;
var_dump($valueAsCents);
var_dump((int) $valueAsCents);
This returns
float 54780
int 54779
So apparently the float value with no decimals, is not equal to the int value. Any ideas of what's going on here?
When you divide $valueAsCents = 54780 / 100 then it becomes a float which is not always accurate in digital form because of the way they are stored. In my tests I got
547.7999999999999545252649113535881042480468750000
When multiplied by 100 this is would be
54779.9999999999927240423858165740966796870000
When PHP casts to int, it always rounds down.
When converting from float to integer, the number will be rounded towards zero.
This is why the int value is 54779
Additionally, the PHP manual for float type also includes a hint that floating point numbers may not do what you expect.
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....

Convert large decimal numbers to hexadecimal

I had to convert a large decimal number to hexadecimal so i use the function dechex.
So my code go like these :
$large_number = '637188198442990866068821375'
$large_hex = dechex($large_number)
but when i print the variable $large
echo $large
i got these value 7fffffffffffffff and i am very sure that i lose some precision.
What can i do to get the full precision in these operation ?
The largest integer that Javascript can handle is 2^52. +/- 9007199254740992
What is JavaScript's highest integer value that a Number can go to without losing precision?
You'll either need to slice up your integer before it hits Javascript or look at one of the Big Integer libraries.

Why does PHP appear to evaluate this condition incorrectly?

I have the following code in PHP, where I've attempted to overcome the stated issue by type-casting the variables into Integers and also avoiding floating-point errors by multiplying all values by 100 before comparison in order remove the 2 decimal places.
However, the following code still evaluates the expression to true and colours the text in red instead of green but when I echo the two values of $eq_left and $eq_right, they are identical with no decimal point.
Here's the code:
$eq_left = (int) ($eq_bal_CurrentAssets*100) + ($eq_bal_NonCurrentAssets*100) ;
$eq_right = (int) ($eq_bal_Liabilities*100) + ($eq_bal_Taxation*100) + ($eq_bal_Equity*100) ;
if ($eq_left !== $eq_right) {
$color = 'red';
$diff = abs($eq_left - $eq_right);
} else {
$color = 'green';
}
echo "<div style=\"color: $color; font-weight:bold;\">\n";
echo " " . number_format(($eq_left/100),2,".",",") . " = " . number_format(($eq_right/100),2,".",",") . "<br />\n";
if ($diff) {
echo " Difference = " . number_format(($diff/100),2,".",",") . "\n";
}
echo "</div>\n";
echo $eq_left . " | " . $eq_right
Any ideas?
I agree with the recommendation against floating point if you want exact decimal fraction representation.
The reason is that many decimal fractions can only be approximated in float or double. They are based on binary, not decimal, fractions. In general, a rational number a/b, with no common factors in a and b, can be expressed exactly in a radix r representation if, and only if, all prime factors of b are also prime factors of b. For example, in decimal 1/5 is 0.2, but 1/3 is 0.333333333... In a binary system, 1/5 causes the same problem as 1/3 in decimal.
In your code, I suggest rounding to zero decimal places after doing the multiplication by 100. The (int) cast rounds towards zero, which is not what you need. If the input is even slightly less than a positive integer n, the result of the cast is n-1. The result of round is n.
The floating point representation of a decimal fraction that cannot be represented exactly may be either slightly lower or slightly higher than the original decimal fraction. If you start with e.g. 0.29, convert it to the nearest IEEE 754 64 bit float, and multiply by 100 you will actually get the floating point equivalent of 28.999999999999996447286321199499070644378662109375
Converting that to int with rounding towards zero gives 28, not 29. Rounding it to the nearest int would give 29.
Never use floating point numbers for money. Always store monetary values as integer cents. You store $5.40 as 540 and divide by 100 when you want to display. Floating point numbers cannot accurately represent the decimals that you think they are.
Here are some pages that discuss why floats as money is a terrible idea:
Why not use Double or Float to represent currency?
storing money amounts in mysql
If dealing with money in a float is bad, then why does money_format() do it?
The problems that you are having are inherent with float representations of decimals. The only way to reliably get around them is to use integers.

Categories