Coder-Spirit / php-bignumbers

A robust library to handle immutable big numbers inside PHP applications
MIT License
131 stars 29 forks source link

Float precision errors using number_format() #37

Closed GusGold closed 6 years ago

GusGold commented 9 years ago

When using number_format() like here, you are relying on php's float precision.

Trying to use a number like Decimal::fromFloat(90.05) will cause a precision error as number_format(90.05, 16, ".", "") === "90.0499999999999972".

There needs to be another method used to load in the numbers initially, as all following operation are going to be wrong.

shabbyrobe commented 9 years ago

Casting to string works as long as the number isn't too big or the fraction too small:

var_dump((string)90.05); // string(5) "90.05"

Unfortunately, it's pretty easy to get into scientific notation:

var_dump((string)9012908310238012.11) // string(18) "9.012908310238E+15"
var_dump((string)0.000021) // string(6) "2.1E-5"

It could work to check for "E+" or "E-" in the string and fall back to the number_format method:

$strValue = (string)$fltValue;
if (preg_match("/E[\+\-]\d+$/", $strValue)) {
    $strValue = number_format($fltValue, $scale, '.', '');
}

INF and NAN are already covered elsewhere in fromFloat() so I have not considered them here.

Here's the 3v4l with my experiment: http://3v4l.org/0aVdK

castarco commented 9 years ago

@GusGold you can use something like Decimal::fromString("90.05");.

fromFloat exists because sometimes we have to start with native float numbers. By the way, the precision error isn't because the conversion, but because a native float number can't exactly represent the 90.05 number, so it isn't php-bignumbers fault. (ok, if $scale is small we can have precision loss, but that's not the real problem).

In addition, the explanation of @shabbyrobe also holds.

castarco commented 9 years ago

EDIT: I've seen that @shabbyrobe proposes a solution to better cast "easy numbers" (not too big, not too small) , maybe it's possible to do a simple cast to string and call fromString. This evening I'll play with this to see if we can obtain better results.