31

我在 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

那有多糟糕?

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

2.如果不能,当这个解决方案失败时,你能给我一个明确的例子吗?

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


  • 请注意我不能使用整数计算(float*100 = cents),太晚了。
  • 我不会处理高于 10^6 的数字
  • 我不需要比较数字

更新

@Baba 的答案很好,但他在测试中使用了precision=20, precision=6......所以我仍然不确定它是否会起作用。

请考虑以下内容:

比方说precision = 8,我唯一要做的就是加法+和减法-

A + B = C

A - B = C

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

简单的测试就可以完成这项工作:

// 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工作太大了,所以我不能运行这个测试

问题 2:当精确解决方法失败时如何估计/计算?没有如此疯狂的测试?有没有数学*的直接答案?如何计算会不会失败?

*我不需要知道浮点计算是否有效,但是如果您知道精度以及 A 和 B 的范围,则当解决方法失败时


请注意,我真的知道cents 和 bcmath 是最好的解决方案。但是我仍然不确定减法和加法的解决方法是否会失败

4

5 回答 5

49

介绍

许多人认为浮点运算是一门深奥的学科。这是相当令人惊讶的,因为浮点在计算机系统中无处不在。大多数小数没有作为二进制分数的精确表示,因此需要进行一些舍入。一个好的开始是每个计算机科学家都应该知道的关于浮点运算的知识

问题

问题 1

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

答案 1

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

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

问题2

如果不能,当这个解决方案失败时,你能给我一个明确的例子吗?

答案 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 精度作为财务计算或浮点长度的基础并不是一个好主意

简单测试

Lest Run some example together using 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 

结论

忘记浮点数,然后计算,cents然后除以100是否为时已晚,只需简单地使用number_format它对我来说看起来一致。

更新

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

增加的形式是0关于您的循环的组合可能性, 我真的认为没有人愿意为您运行这样的测试。999999.990.0199,999,9999,999,999,800,000,000

由于浮点数是具有有限精度的二进制数,因此尝试设置precision对确保精度的效果有限这是一个简单的测试:

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:当精确解决方法失败时如何估计/计算?没有如此疯狂的测试?有没有数学*的直接答案?如何计算会不会失败?

事实仍然是浮点数准确性问题,但对于数学解决方案,您可以查看

我不需要知道浮点计算是否有效,但是如果您知道精度以及 A 和 B 的范围,当变通方法失败时

在此处输入图像描述

不知道那句话是什么意思:)

于 2013-02-01T22:43:18.873 回答
5

根据文档,精度指令只是更改将数字转换为字符串时显示的数字:

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

因此,它基本上是number_format()money_format()的一个非常复杂的替代方案,只是它的格式选项较少,并且可能会受到您可能不知道的其他一些副作用的影响:

<?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 用数字进行数学计算的方式。从浮点数(甚至不是整数!)转换为字符串,这只是更改格式选项的一种神奇方式。例子:

<?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. 整数在所有情况下都不受影响
于 2013-01-29T16:24:43.247 回答
4

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

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

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

  • 如果你真的需要你的结果精确地加起来,尤其是当你处理钱的时候:使用一个特殊的十进制数据类型。

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

  • 如果您没有可用的十进制数据类型,另一种方法是使用整数,例如完全以美分计算货币。但这是更多的工作并且有一些缺点。

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

我会使用整数或Decimal为它创建一个特殊类型。

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

于 2013-01-29T17:15:24.420 回答
1

我相信,如果您只是简单地对您提出的结果进行四舍五入,那么这将为您解决浮点问题,而无需对您的配置进行服务器范围的更改。

round(342349.23 - 341765.07, 2) = 584.16
于 2013-01-29T16:37:20.460 回答
0

如果使用precision=8,如果使用8位数字,则无法确定第8位。这可能会从第 9 位四舍五入减去 1。

例如

12345678.1 -> 12345678
12345678.9 -> 12345679

这可能看起来不是那么糟糕,但考虑

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

然而,如果您使用精度 = 9,这将是22222222.8四舍五入为22222223.

如果你只是在做加法和减法,你应该使用至少 2 位左右的精度,而不是避免在这类计算中四舍五入。如果你在做乘法或除法,你可能需要更多。使用必要的最低限度可能会导致到处丢失数字。

因此,要回答您的问题,如果您很幸运并且 php 在计算中使用高精度并且仅以较低的精度存储结果(然后您不会使用该数字继续执行),您可能会侥幸逃脱其他计算),但总的来说,这是一个非常糟糕的主意,因为(至少)计算中的最后一个数字是完全不可靠的。

于 2013-02-08T23:35:34.367 回答