Why PHP's float 0 (zero) is being displayed with sign? - php

In PHP, lets create a variable and set it value to 0:
$x = 0;
echo $x;
it would display 0. If one would multiply that by -1:
$x = -1 * $x;
echo $x;
we still see 0. But if $x is a float:
$x = 0;
$x = (float)$x;
$x = -1 * $x;
echo $x;
we would get the output: -0.
Why is that? Shouldn't zero be always displayed unsigned, regardless of its underlying type?

Because PHP typically uses the IEEE 754 double precision format for floating point numbers which consists of:
1) sign (1 bit)
2) exponent (11 bit)
3) fraction (52 bit)
If you multiply $x with -1 the sign bit is set.
Integers use the two complement where the most significant bit (MSB) determines the sign. The MSB is 0 if you multiply with 0.
See: https://en.wikipedia.org/wiki/Signed_zero

Floating point zero is used for more than just absolute zero. It is used to represent tiny results, too small absolute magnitude even for subnormal numbers.
In some calculations, the sign of such numbers does matter. For example, it matters that 1/+0 is positive infinity, but 1/-0 is negative infinity. To make that work right, multiplying 0 by -1 gives -0.
For example, See W. Kahan's paper "Branch Cuts for Complex Elementary Functions or Much Ado About Nothing's Sign Bit".

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

Machine epsilon computation issue

I stumbled on the difference between the result of Machine epsilon calculation.
When compared to 0 PHP yields 4.9406564584125E-324.
While for 1 it pops up with 1.1102230246252E-16.
Quite a difference. Guess it's something with the type of data initially set by default in PHP.
The code is:
<?php
//Machine epsilon calculation
$e = 1;
$eTmp = null;
for ($i = 0; 0 != 0 + $e; $i++){ //Changing 0 by 1 produces absolutely different result
$e = $e/2;
if ($e != 0) {$eTmp = $e;}
}
echo $eTmp;
//var_dump($eTmp);
?>
Any clarification on the difference between the two?
And how can a variable or value by assigned manually to float in PHP?
Many thanks for your ideas!
Your PHP implementation appear to be using the common IEEE 754 64-bit binary format. In this format, finite values are represented, effectively, as a sign, an integer M less than 253, and an integer exponent e between −1074 and 971, inclusive. The value represented is +M•2e or −M•2e, according to the sign. (This format will often be described with M being a fraction between 1 and 2 with a certain number of bits after the radix point. The descriptions are mathematically equivalent, just useful for different purposes.)
Given this, we can easily see that the next representable number after 0 is +1•2−1074, which is approximately 4.94065645841246544•10−324.
In this format as stated, 1 can be represented as +1•20. However, to see what the smallest change that can be made to 1 is, we must normalize it by making M as large as possible. 1 can also be represented with M = 252 and e = −52, resulting in +252•2−52. In this form, we can see the next representable value greater than 1 is achieved by adding 1 to M, mkaing the number +(252+1)•2−52.
The difference between 1 and +(252+1)•2−52 is of course 1•2−52, which is exactly 2.220446049250313080847263336181640625•10−16.
Note that your code records $eTmp (if $e is not zero) after reducing $e with $e = $e/2, which means it reports the first value of $e that does not cause a change. Thus it reports 1.11e-16 rather than 2.22e-16, which is the last value that does cause a change to 1.

Why floor(0.99999999999999999) = 1 and floor(0.9999999999999999) = 0?

floor function in PHP behave weirdly.
For 16 decimal values it gives floor value but by increasing 1 decimal it round.
$int = 0.99999999999999999;
echo floor($int); // returns 1
$int = 0.9999999999999999;
echo floor($int); // returns 0
$int = 0.99999999999999994;
echo floor($int); // returns 0
Is it defined/explained somewhere, at which point it gives "round" value?
Is there any function which gives 0 anyhow how many 9 in decimals?
It's not floor that rounds, it's floating point math that does.
This line:
echo 0.99999999999999999;
Prints 1 (demo) because 0.99999999999999999 is too precise to be represented by a (64-bit?) float, so the closest possible value is taken, which happens to be 1.
0.99999999999999994 is also too precise to be represented exactly, but here the closest representable value happens to be 0.9999999999999999.
Is it defined/explained somewhere, at which point it gives "round" value?
It's complicated, but the numbers are rounded almost always.
I believe there is no definition of "from when values will be approximated", but that is a mathematical property that follows from the definitions in the IEEE 754 floating point standard.
To be safe, just assume everything is approximated.
Is there any function which gives 0 anyhow how many 9 in decimals?
No. The problem is that, for PHP, 0.99999999999999999 is literally the same as 1.
They're represented by exactly the same sequence of bits, so it can't distinguish them.
There are some solutions to work with bigger precision decimals, but that requires some major code changes.
Probably of interest to you:
Working with large numbers in PHP
Note that while you may get arbitrary precision, you will never get infinite precision, as that would require infinite amounts of storage.
Also note that if you actually were dealing with infinite precision, 0.999... (going on forever) would be truly (as in, mathematically provable) equal to 1, as explained in depth in this Wikipedia article.
$float_14_digits = 0.99999999999999;
echo $float_14_digits; // prints 0.99999999999999
echo floor($float_14_digits); // prints 0
$float_15_digits = 0.999999999999999;
echo $float_15_digits; // prints 1
echo floor($float_15_digits); // prints 1
exit;
on my development machine that behavior happens on digit '15' not '17' like yours. PHP rounds the last digit in the floating numbers. your floor() function has nothing to do with this behavior

PHP: rounding a number into 16 decimal digits

Hi I'm trying to rounding a number into the 16 decimal digits but it only show and doesn't round up till 14 decimal digit.
Here's my attempt:
<?php
$num= 0.16346153846153846;
$round = number_format((float)$num, 17, '.', '');
echo $round * -1;
?>
OUTPUT:
-0.16346153846154
EXPECTED OUTPUT:
0.1634615384615385
I know that float is only 14 decimal digits. Is there any other way around for the 16 decimal digits?
You can set this parameters at run-time. 16 digits is usually the maximum
value on most platforms, larger values giving only meaningless or "fictional"
digits:
ini_set("precision", "16");
See Changing precision level floating-point variables
Recently we experienced an issue with the reading of amount(decimal number) from an excel file which has a decimal number with more than 14 digits after the decimal. The issue is fixed after using the option of an advanced algorithm for converting the decimal number. For this, we need to set,
ini_set('precision', -1);
Ref: http://php.net/precision
Can you try doing this, & see what happens? (May not be the best way of dealing with floats and numbers though)
$num = 0.16346153846153846;
$new_num = $num * -1;
echo number_format((float)$new_num, 17);
Multiply first, then try rounding the result.
number_format and the precision INI setting uses float, which is likely to result in unexpected behaviour if you're rounding to that many decimal digits.
You can alternatively use the PHP decimal extension with $decimal->toFixed(16) or $decimal->round(16) to achieve this with guaranteed accuracy regardless of your INI.

Understanding floating point numbers in php

I know these questions may get asked a lot but from my reading and testing it had me confused a bit and a lot of the reading I have done has just confused me more as it is quite complex.
Some people seem to have issues with simple comparisons, however I have had no issues myself.
For example...
$num1 = 27.64;
$num2 = 27.64;
if ($num1 == $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's "Good!"
...and
$num1 = 27.60;
$num2 = 27.6;
if ($num1 == $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's Good
...and
$num1 = 27.60;
$num2 = 57.60;
if ($num1 <= $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's Good
...and
$num1 = 25.00;
$num2 = 12.50 + 12.5;
if ($num1 == $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's Good
Then I see pages like http://patchlog.com/php/comparing-float-values-in-php/ that seem to have simple issues and I don't get it.
I just want to understand how he is getting problems with his simple code but I am not with mine.
Example 1
Those values will be the same -- you assign the same decimal literal to each variable. Compare that to this code:
$num1 = 27.64;
$num2 = 10.0 + 2.88 + 2.88 + 2.88 + 9.0; //In decimal arithmetic adds to 27.64
if ($num1 == $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's "Bad!"
$num2 looks like it should be 27.64, but it really adds to something like 27.639999999999997015720509807579219341278076171875 (that's what I get when I do that calculation in Visual C++ on my machine). $num1 = 27.6400000000000005684341886080801486968994140625 (on my machine), so they differ.
Example 2
The trailing 0 makes no difference.
Example 3
The numbers are not within the floating-point "tolerance" so of course will differ.
Example 4
12.5 is exactly representable in floating point, so 12.5 + 12.5 is too (0.5 is 2^-1).
Here is a clear example:
$a = 0;
for ($i = 0; $i < 100000; $i++) {
$a += 0.00001;
}
print("$a\n");
You would expect you'll get 1 printed, but actually the output is 0.99999999999808.
(result is on an x86_64 architecture)
The bigger (or smaller) the floating point number, the less precise it is. Exactly how precise will vary based on processor architecture.
Try doing all your tests at 1E30 or 1E-30...
The first two have the value provided by the compiler, which is resolving both numbers to the same bit pattern.
I'm not going to touch the third since it should be obvious why it works.
For the fourth, the values used have well defined, fully accurate bit patterns. Try using numbers a little more off the beaten path.
You can try
$a = '12.30';
as string to get exact match otherwise floatbox by default remove ending '0'.
Floating-point errors occur only when there are operations whose mathematical results cannot be exactly represented in floating point. The errors are precisely defined; they are not random or arbitrary, so identical results are produced when identical operations are performed.
In your first example, you assign “27.64” to $num1 and to $num2. There is an operation being performed here: The parser must interpret the character string “27.64” and produce a floating-point result. Likely, the parser produces the floating-point number that is closest to 27.64. (As a hexadecimal floating-point numeral, that number is 0x1.ba3d70a3d70a4p+4. The part before the “p” is a hexadecimal numeral, with a fractional part. The “p4” means multiply by 24. As a decimal numeral, it is 27.6400000000000005684341886080801486968994140625.) And it produces the same number in both cases, so the comparison of $num1 to $num2 indicates they are equal to each other, although neither is equal to 27.64 (because 27.64 cannot be exactly represented in floating point).
In your second example, the floating-point number that is closest to the value of the numeral “27.60” is the same as the floating-point number that is closest to the value of the numeral “27.6”, since the two numerals represent the same value. So, again, you get identical results.
In your third example, the values of the two numerals are far apart, so you get different floating-point numbers, and the comparison indicates they are unequal.
In your fourth example, all of the values are exactly representable in floating-point, so there is no error. 25, 12.50, and 12.5 are all small multiples of a power of two (includes powers with a negative exponent, such as .5 = 2-1, within the range of the floating-point type, so they are representable. Further, the sum of 12.50 and 12.5 is exactly representable, so there is no rounding error when adding them. Thus, all the results are exact, and the comparison indicates the sum equals 25.
Problems arise when people expect identical results from two different calculations that would have the same mathematical result. A classic example is comparing “.3” to “.1+.2”. Converting the numeral “.3” to floating-point yields the closest representable value, which is 0x1.3333333333333p-2 (0.299999999999999988897769753748434595763683319091796875), slightly under .3. Converting “.1” to floating-point yields the closest representable value, which is 0x1.999999999999ap-4 (0.1000000000000000055511151231257827021181583404541015625), slightly over .1. Converting “.2” to floating-point yields the closest representable value, which is 0x1.999999999999ap-3 (0.200000000000000011102230246251565404236316680908203125), slightly over .2. Adding the latter two values yields the representable value closest to their sum, which is 0x1.3333333333334p-2 (0.3000000000000000444089209850062616169452667236328125). As you can see, that sum is different from the value obtained by converting “.3” to floating-point, so comparing them indicates they are unequal.

Categories