9

This code:

echo (40 * (10 / 100 + 1)); //44
echo (50 * (10 / 100 + 1)); //55
echo ceil(40 * ((10 / 100) + 1)); //44
echo ceil(50 * ((10 / 100) + 1)); //56 (!)

I think, that "56" by reason of floating point (55.0000000001 => 56), but I can't understood why for "40" result is "44", not "45"

Amarnasan
  • 14,939
  • 5
  • 33
  • 37
  • You can learn more about this: http://php.net/manual/en/function.ceil.php – Mayur Koshti Oct 23 '15 at 06:33
  • @MayurKoshti It doesn't look to me like he doesn't understand the function. It is actually pretty odd this is happening as `50 * ((10 / 100) + 1)` equals 55 perfectly. It's not a float. So it shouldn't round it up to 56 in the first place. – icecub Oct 23 '15 at 06:37
  • @NikitaKolosov: can you print the value of each intermediate step separately and see where it might've gone wrong? – klaar Oct 23 '15 at 06:40
  • I tested and getting same. :( – Mayur Koshti Oct 23 '15 at 06:43
  • 1
    Yes, that's because of the [floating point errors](http://floating-point-gui.de/). In you case, if for the case of "`56`", the number is actually sth about `56.000000001`; then, for the case of "`40`", the number is actually something sth about `39.999999999`! – someOne Oct 23 '15 at 07:16
  • @someOne No it isn't. `sprintf('%.50f', $y);` (assuming $y is the math) returns exactly 40.0000.. etc :P – icecub Oct 23 '15 at 07:19
  • @icecub then for the case of "`40`", the number is actually something sth about `39.999999999` _or simply and accurately (mathematically wise) is equal to "`40`"! (i.e. it's one the cases where the decimal number can be accurately be represented in the binary format)_ :) – someOne Oct 23 '15 at 07:23
  • @someOne I took a moment to read that document real quick (amazing reference btw!) and it explains why some floats are calculated perfectly and others not. Thanks a lot for that :) – icecub Oct 23 '15 at 07:25

2 Answers2

5

The 55 isn’t actually 55. You can verify that easily:

<?php
$x = (40 * (10 / 100 + 1)); // 44
$y = (50 * (10 / 100 + 1)); // 55
echo '$x == 44: ' . ($x == 44 ? 'True' : 'False') . "\n";
echo '$y == 55: ' . ($y == 55 ? 'True' : 'False') . "\n";
echo '$y > 55: ' . ($y > 55 ? 'True' : 'False') . "\n";
echo $y - 55;

Yields:

$x == 44: True
$y == 55: False
$y > 55: True
7.105427357601E-15

As you can see the difference is tiny (7.1 * 10^-15) but that still makes it larger than 55, so ceil will round it up.

The reason you just see 55 is because echoing it will convert the float into a string:

String conversion is automatically done in the scope of an expression where a string is needed. This happens when using the echo or print functions, or when a variable is compared to a string.

For this conversion the standard truncating behavior will cut off the digits at some point. This is configured by the precision configuration parameter and defaults to 14. You can avoid this behavior by using sprintf with a custom precision:

echo sprintf('%.50f', $y);
// 55.00000000000000710542735760100185871124267578125000
poke
  • 369,085
  • 72
  • 557
  • 602
  • So why does `abs($y)` return 55? – icecub Oct 23 '15 at 06:54
  • 1
    @icecub `abs()` does not change the number but only the sign. so `abs($y)` for a positive `$y` gives you the same number. It’s just shown as `55`, because printing the number truncates the digits at some point. – poke Oct 23 '15 at 07:00
  • I see. Still I find it a bit odd as I've always thought `abs()` returns the absolute number no matter the amount of floating point digits. Also looking from a math perspective, it should simply be 55 :P Anyway, thanks for explaining! – icecub Oct 23 '15 at 07:05
  • @icecub `abs()` just returns the [absolute value](https://en.wikipedia.org/wiki/Absolute_value) of a number. It has nothing to do with floats or decimals. And if there was a magic function that fixed incorrect float point issues: How would that function decide whether a number is off the integer by accident or by design? ;) – poke Oct 23 '15 at 07:09
  • Yeye, enough messing with my head already! Haha. I seriously even start thinking about it /cry – icecub Oct 23 '15 at 07:12
  • Sorry for being lazy, but why is $x exactly 44 but $y not exactly 55? Aren't both variables affected by the same shortcomings of floating point representation by binary mantissa? – klaar Oct 23 '15 at 07:15
  • @klaar It’s just a happy coincidence that multiplicating the floating point representation of `10 / 100 + 1` by 40 yields back to a precise floating point number while `50` doesn’t. It’s difficult to understand since the multiplication works with the binary representation while we think of it as “integer times broken float” (which should almost never give an integer back) – poke Oct 23 '15 at 09:30
  • I can't understood why it working only for '50'. Why for other numbers it is ok. – Nikita Kolosov Oct 23 '15 at 12:34
  • @NikitaKolosov You mean 40. But it works for other numbers too, e.g. `5`, `20`, `35`, `60`. It’s due to how floating point numbers are represented in memory (as sums of exponentials of 2^x). It just happens to be that some multiplications get rid of the imprecisions while most others don’t. – poke Oct 23 '15 at 12:42
1

We need to notice Floating point precision http://php.net/manual/en/language.types.float.php

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....

And this is the answers for the question Why don’t my numbers add up? http://floating-point-gui.de/ . Explain concisely why you get that unexpected result

Imvydao
  • 26
  • 2