php intval()和floor()返回的值太低了?

时间:2009-05-01 19:12:56

标签: php floating-accuracy rounding-error

因为PHP中的浮点数据类型不准确,并且MySQL中的FLOAT占用的空间比INT多(并且不准确),所以我总是将价格存储为INT,在存储之前乘以100以确保我们有2个小数精确的地方。但是我认为PHP是行为不端的。示例代码:

echo "<pre>";

$price = "1.15";
echo "Price = ";
var_dump($price);

$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);


$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);

echo "</pre>";

产生输出:

Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)

我很惊讶。当最终结果低于预期值1时,我期待我的测试结果看起来更像:

Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)

这将证明浮点类型的不准确性。但为什么楼(115)返回114?

5 个答案:

答案 0 :(得分:26)

尝试将其作为快速解决方法:

$price_int = intval(floor($price_corrected + 0.5));

您遇到的问题不是PHP的错,所有使用带有浮点算术的实数的编程语言都有类似的问题。

货币计算的一般经验法则是永远不要使用浮点数(既不在数据库中也不在脚本中)。您可以通过始终存储美分而不是美元来避免各种问题。分是整数,你可以自由地将它们加在一起,并乘以其他整数。无论何时显示数字,请确保在最后两位数字前插入一个点。

你得到114而不是115的原因是floor向下舍入,朝向最接近的整数,因此楼层(114.999999999)变为114.更有趣的问题是为什么1.15 * 100是114.999999999而不是115原因是1.15不完全是115/100,但是它的数量要小得多,所以如果乘以100,你得到的数字会比115小一点。

以下是echo 1.15 * 100;所做的更详细解释:

  • 它将1.15解析为二进制浮点数。这涉及舍入,它恰好向下舍入一点以获得最接近1.15的二进制浮点数。您无法获得确切数字(没有舍入误差)的原因是1.15在基数2中具有无限数量的数字。
  • 它将100解析为二进制浮点数。这涉及舍入,但由于100是一个小整数,舍入误差为零。
  • 它计算前两个数字的乘积。这也涉及一点舍入,以找到最近的二进制浮点数。在此操作中,舍入误差恰好为零。
  • 它将二进制浮点数转换为带点的十进制十进制数,并打印此表示。这也涉及一点点。

PHP打印令人惊讶的Corrected price = float(115)(而不是114.999 ...)的原因是var_dump不打印确切的数字(!),但它会打印舍入到{{的数字1}}(或n - 2)个数字,其中n个数字是计算的精度。您可以轻松验证:

n - 1

如果要打印浮动,请记住:打印浮动时,并不总是看到所有数字

另请参阅PHP float文档开头附近的重要警告。

答案 1 :(得分:4)

我相信,其他答案已经涵盖了原因和解决问题的良好解决方案。

旨在从不同的角度解决问题:

要在MySQL中存储价格值,您应该查看DECIMAL type,它可以存储带小数位的精确值。

答案 2 :(得分:3)

PHP正在根据有效数字进行舍入。它隐藏了不准确性(第2行)。当然,当地板出现时,它不会更好地知道它并且一直停下来。

答案 3 :(得分:3)

也许这是这个“问题”的另一种可能的解决方案:

intval(number_format($problematic_float, 0, '', ''));

答案 4 :(得分:0)

如上所述这不是PHP本身的问题,它更多的是处理不能表示为有限浮点值的分数的问题,因此在舍入时会导致字符丢失。

解决方案是确保在处理浮点值时需要保持准确性 - 使用gmp函数或BC数学函数 - bcpow,bcmul等。问题将很容易解决。

而不是 $ price_corrected = $ price * 100;

使用$ price_corrected = bcmul($ price,100);