我可以依靠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=20
, precision=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
免得使用bcmath
, number_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是带小数点的数字? 如果有,请给我一个例子
从0
到999999.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一个特殊的Decimal
types。
如果您决定使用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)
这说明:
- 浮点计算不受影响,只有显示更改
- 整数在所有情况下都不受影响
我相信,如果你只是简单地把你的结果放在一边,那么你就可以为你处理浮点问题,而不必对整个服务器进行更改。
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)您的计算中的最后一个数字是完全不可靠的。