当然,PHP 浮点精度问题经常是一个关于 SO 的话题,但我仍然不明白为什么这个问题尽管有 18 票反对,因为这个问题非常精确地针对具体浮点数的具体问题提出。我已经解决了这个问题,每个人都应该清楚地理解这里的问题是什么(不仅仅是那些不精确的推理)。
我们开始 - 查看以下结果:
$example = 90000000000009132 - 1;
var_dump(PHP_INT_MAX);
var_dump($example);
var_dump(PHP_FLOAT_MAX);
var_dump(sprintf('%.0g', $example));
var_dump(sprintf('%.0f', $example));
结果:
int(9223372036854775807)
int(90000000000009131)
float(1.7976931348623E+308)
string(7) "9.0e+16"
string(17) "90000000000009136"
意思是:
- 您没有超出最大整数大小,这将使
算术运算失败
- 你的计算结果是正确的整数
- 您未超过 PHP_FLOAT_MAX(需要 PHP 7.2),但其大小取决于您的环境
- 计算的浮点表示意外错误(因为您的 printf 语句先转换为浮点数,然后再转换为字符串)
那么为什么我们会得到这个奇怪的结果呢?让我们做一些测试:
var_dump(sprintf('%.0f', 90000000000009051));
var_dump(sprintf('%.0f', 90000000000009101));
var_dump(sprintf('%.0f', 90000000000009105));
var_dump(sprintf('%.0f', 90000000000009114));
var_dump(sprintf('%.0f', 90000000000009121));
var_dump(sprintf('%.0f', 90000000000009124));
var_dump(sprintf('%.0f', 90000000000009128));
var_dump(sprintf('%.0f', 90000000000009130));
var_dump(sprintf('%.0f', 90000000000009131));
var_dump(sprintf('%.0f', 90000000000009138));
var_dump(sprintf('%.0f', 90000000000009142));
var_dump(sprintf('%.0f', 90000000000009177));
这样的结果是:
string(17) "90000000000009056"
string(17) "90000000000009104"
string(17) "90000000000009104"
string(17) "90000000000009120"
string(17) "90000000000009120"
string(17) "90000000000009120"
string(17) "90000000000009120"
string(17) "90000000000009136"
string(17) "90000000000009136"
string(17) "90000000000009136"
string(17) "90000000000009136"
string(17) "90000000000009184"
看看这些数字。末尾的数字总是可以被 16 整除。信息技术中的 16 是什么意思?一个字节。十六进制。它告诉我们什么?
您可能已经超出了浮点数基本精确的限制(不一定在算术运算中)。
如果我们将这些数字减少一个零到 16 位呢?
var_dump(sprintf('%.0f', 9000000000009051));
var_dump(sprintf('%.0f', 9000000000009101));
var_dump(sprintf('%.0f', 9000000000009124));
var_dump(sprintf('%.0f', 9000000000009138));
var_dump(sprintf('%.0f', 9000000000009142));
var_dump(sprintf('%.0f', 9000000000009177));
...
string(16) "9000000000009051"
string(16) "9000000000009101"
string(16) "9000000000009124"
string(16) "9000000000009138"
string(16) "9000000000009142"
string(16) "9000000000009177"
输出总是正确的。好吧。如果我们把它提高一个零位数呢?
var_dump(sprintf('%.0f', 900000000000009051));
var_dump(sprintf('%.0f', 900000000000009101));
var_dump(sprintf('%.0f', 900000000000009124));
var_dump(sprintf('%.0f', 900000000000009138));
var_dump(sprintf('%.0f', 900000000000009142));
var_dump(sprintf('%.0f', 900000000000009177));
...
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009216"
有趣的是,这又是 16 的因数 - 更加不精确。
现在让我们来解开这个谜吧:
var_dump(strlen(sprintf('%.0f', 9000000000009051)));
猜猜结果是什么:
int(16)
哇。这意味着浮点数在 PHP 中基本上可以精确到 16 位(如果超过这个神奇的数字,它就会变得不精确)。
那么你的计算发生了什么?
由于您超过了 16 位的位数,所以最后四舍五入到下一个 16 位。
有关 PHP 中浮点精度的更多信息可以在 Stackoverflow 上的几十个问题中找到,当然也可以在 PHP 手册中找到。
回答如何使您的代码正常工作:
如果您希望您的数字准确,请不要转换为浮点数。以整数为例,应该可以进行更高的计算。
如果超过 16 位数,像 -1 或 +1 这样的简单算术也会失败(至少在当前版本的 PHP 中看起来如此)。