我可以依靠PHP的php.ini精确解决浮点问题

我发现PHP的浮点问题的一些解决方法:

php.ini设置precision = 14

 342349.23 - 341765.07 = 584.15999999992 // floating point problem 

php.ini设置,比方说precision = 8

 342349.23 - 341765.07 = 584.16 // voila! 

演示: http : //codepad.org/r7o086sS

那有多糟?

如果我只需要精确的2位数计算(金钱),我可以依靠这个解决scheme吗?

如果这个解决scheme失败了,你能不能给我一个明确的例子?

编辑:3.哪个php.ini.precision值适合最好的两位数字,金钱计算


  • 请介意,我不能使用整数计算(浮点数* 100 =美分),这是为时已晚。
  • 我不打算超过10 ^ 6的数字
  • 我不需要比较数字

UPDATE

@Baba答案很好,但是他在testing中使用了precision=20precision=6 …所以我还不确定它是否会工作。

请考虑以下事项:

假设precision = 8而我所做的只是加法+减法-

A + B = C

A - B = C

问题1:精度解决方法是否会失败,数字介于0..999999.99之间,其中A和B是带小数点的数字? 如果有,请给我一个例子。

简单的testing就能完成这项工作:

 // if it fails what if I use 9,10,11 ??? // **how to find when it fails??? ** ini_set('precision', 8); for($a=0;$a<999999.99;$a+=0.01) { for($b=0;$b<999999.99;$b+=0.01) { // mind I don't need to test comparision (round($a-$b,2) == ($a-$b)) echo ($a + $b).','.($a - $b)." vs "; echo round($a + $b, 2).','.round($a - $b, 2)."\n"; } } 

但显然99999999 * 2是太大的工作,所以我不能运行这个testing

问题2:精度变通方法失败时如何估算/计算? 没有这样疯狂的testing? 有没有math*,直接的答案呢? 如何计算是要失败或不?

*我不需要知道浮点计算的工作原理,但是如果知道精度和A和B的范围,则变通方法失败


请介意我真的知道美分和美分是最好的解决办法。 但是,我仍然不确定解决方法是否会失败或不适用于减法和加法

介绍

浮点运算被许多人认为是一个深奥的主题。 这是相当惊人的,因为浮点在计算机系统中是无处不在的。 大多数小数没有一个二进制小数的精确表示,所以有一些四舍五入的情况。 一个好的开始是每个计算机科学家应该知道的浮点运算

问题

问题1

如果我只需要精确的2位数的计算 (金钱),我可以依靠这个解决scheme吗?

答案1

如果您需要精确的2位数,那么答案是NO ,即使您not going to work on numbers higher than 10^6您也不能使用php精度设置来确定一个2位数的小数位。

在计算过程中,如果长度小于8,可能会增加精度长度

问题2

如果不能,当这个解决scheme失败时,你能否给我一个清晰的例子

答案2

 ini_set('precision', 8); // your precision $a = 5.88 ; // cost of 1kg $q = 2.49 ;// User buys 2.49 kg $b = $a * 0.01 ; // 10% Discount only on first kg ; echo ($a * $q) - $b; 

产量

 14.5824 <---- not precise 2 digits calculations even if precision is 8 

问题3

哪个php.ini.precision值适合最好的两位数字,钱计算?

答案3

精确度和金钱计算是两个不同的东西…它不是一个好主意,使用PHP精度作为您的财务计算或浮点长度的基础

简单的testing

免得使用bcmathnumber_format和简单的minus一起运行一些例子

Base

 $a = 342349.23; $b = 341765.07; 

Example A

 ini_set('precision', 20); // set to 20 echo $a - $b, PHP_EOL; echo floatval(round($a - $b, 2)), PHP_EOL; echo number_format($a - $b, 2), PHP_EOL; echo bcsub($a, $b, 2), PHP_EOL; 

产量

 584.15999999997438863 584.15999999999996817 <----- Round having a party 584.16 584.15 <-------- here is 15 because precision value is 20 

Example B

 ini_set('precision', 14); // change to 14 echo $a - $b, PHP_EOL; echo floatval(round($a - $b, 2)), PHP_EOL; echo number_format($a - $b, 2), PHP_EOL; echo bcsub($a, $b, 2), PHP_EOL; 

产量

 584.15999999997 584.16 584.16 584.16 <-------- at 14 it changed to 16 

Example C

 ini_set('precision', 6); // change to 6 echo $a - $b, PHP_EOL; echo floatval(round($a - $b, 2)), PHP_EOL; echo number_format($a - $b, 2), PHP_EOL; echo bcsub($a, $b, 2), PHP_EOL; 

产量

 584.16 584.16 584.16 584.00 <--- at 6 it changed to 00 

Example D

 ini_set('precision', 3); // change to 3 echo $a - $b, PHP_EOL; echo floatval(round($a - $b, 2)), PHP_EOL; echo number_format($a - $b, 2), PHP_EOL; echo bcsub($a, $b, 2), PHP_EOL; 

产量

 584 584 584.16 <-------------------------------- They only consistent value 0.00  <--- at 3 .. everything is gone 

结论

忘记浮点数,只需计算分数,然后再除以100如果太晚,只需简单地使用number_format就可以了。

更新

问题1:精度解决方法是否会失败,数字介于0..999999.99之间,其中A和B是带小数点的数字? 如果有,请给我一个例子

0999999.99的增量为0.01约为99,999,999 ,你的循环的组合可能性是9,999,999,800,000,000我真的不认为有人会为你运行这样的testing。

由于浮点数是具有有限精度的二进制数,所以设置precision会有一定的影响,以保证精度下面是一个简单的testing:

 ini_set('precision', 8); $a = 0.19; $b = 0.16; $c = 0.01; $d = 0.01; $e = 0.01; $f = 0.01; $g = 0.01; $h = $a + $b + $c + $d + $e + $f + $g; echo "Total: " , $h , PHP_EOL; $i = $h-$a; $i = $i-$b; $i = $i-$c; $i = $i-$d; $i = $i-$e; $i = $i-$f; $i = $i-$g; echo $i , PHP_EOL; 

产量

 Total: 0.4 1.0408341E-17 <--- am sure you would expect 0.00 here ; 

尝试

 echo round($i,2) , PHP_EOL; echo number_format($i,2) , PHP_EOL; 

产量

 0 0.00 <------ still confirms number_format is most accurate to maintain 2 digit 

问题2:精度变通方法失败时如何估算/计算? 没有这样疯狂的testing? 有没有任何math*,直接的答案呢? 如何计算是要失败或不?

事实基础仍然浮点有准确性问题,但您可以看看math解决scheme

  • 机器精度和后向误差分析
  • 最大限度地减less精度问题的影响

我不需要知道浮点计算工作,但是当变通方法失败,如果你知道精度,和A和B的范围

在这里输入图像描述

不知道这个声明是什么意思:)

我只是引用这个有趣的网站来解决这个问题。 (没有声望预期:)但应该提到:

我能做些什么来避免这个(浮点)问题?

这取决于你在做什么样的计算。

  • 如果你真的需要你的结果加起来,特别是当你使用金钱:使用一个特殊的十进制数据types。

  • 如果您不想看到所有这些额外的小数位:只需在显示时将结果格式化为固定的小数位数。

  • 如果你没有可用的小数数据types,另一种方法是使用整数,例如完全以美分计算。 但这是更多的工作,并有一些缺点。

该网站还包含一些PHP的基本技巧

我会使用整数或创build一个特殊的Decimaltypes。

如果您决定使用bcmath如果将这些值传递给SQL查询或其他外部程序,请小心。 如果他们不知道精确度,可能会导致不必要的副作用。 (什么可能)

根据文档, 精度指令只是改变了数字转换为string时显示的数字:

精度 integer
浮点数中显示的有效位数。

所以对于number_format()或者money_format()来说 ,它基本上是一个非常复杂的select,只是它的格式化选项较less,而且可能会遇到一些您可能不知道的其他副作用:

 <?php $_POST['amount'] = '1234567.89'; $amount = floatval($_POST['amount']); var_dump($amount); ini_set('precision', 5); $amount = floatval($_POST['amount']); var_dump($amount); 

 float(1234567.89) float(1.2346E+6) 

编辑:

我坚持:这个设置不会改变PHP用数字进行math计算的方式。 当从浮点数(甚至不是整数!)转换为string时,这只是一种改变格式选项的神奇方式。 例:

 <?php ini_set('precision', 2); $amount = 1000; $price = 98.76; $total = $amount*$price; var_dump($amount, $total); ini_set('precision', 15); var_dump($amount, $total); 

…打印:

 int(1000) float(9.9E+4) int(1000) float(98760) 

这说明:

  1. 浮点计算不受影响,只有显示更改
  2. 整数在所有情况下都不受影响

我相信,如果你只是简单地把你的结果放在一边,那么你就可以为你处理浮点问题,而不必对整个服务器进行更改。

 round(342349.23 - 341765.07, 2) = 584.16 

如果使用precision = 8,如果使用8位数字,则不能确定第8位数字。 这可能会从第九位数字四舍五入。

例如

 12345678.1 -> 12345678 12345678.9 -> 12345679 

这可能不是很糟糕,但考虑一下

  (11111111.2 + 11111111.2) + 11111111.4 -> (11111111) + 11111111.4 -> 22222222.4 -> 22222222 

而如果你使用精度= 9,那么这将是22222222.8 ,这将是22222223

如果你只是做加法和减法,你应该至less使用2个或更多的精度数字,以避免在这些计算中四舍五入。 如果你正在做乘法或除法,你可能需要更多。 使用最低限度的必要可能会导致在这里和那里丢失数字。

所以,要回答你的问题,如果你幸运的话,你可能会逃避它,PHP使用高精度的计算,只有然后存储在一个较低的精度的结果(你不使用这个数字继续下去,做其他计算),但一般来说,这是一个非常糟糕的想法,因为(至less)您的计算中的最后一个数字是完全不可靠的。