12

因为 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)

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

4

5 回答 5

27

试试这个作为一个快速修复:

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

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

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

你得到 114 而不是 115 的原因是floor向下舍入,向最接近的整数,因此 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 是一个小整数,舍入误差为零。
  • 它计算前两个数字的乘积。这还涉及一点舍入,以找到最接近的二进制浮点数。在此操作中,舍入误差恰好为零。
  • 它将二进制浮点数转换为带点的以 10 为底的十进制数,并打印此表示。这也涉及到一点四舍五入。

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

echo 1.15 * 100;  # this prints 115
printf("%.30f", 1.15 * 100);  # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different";  # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less";    # this prints `less'

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

另请参阅PHP 浮动文档开头附近的大警告。

于 2009-05-01T19:16:12.487 回答
4

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

为了从不同的角度解决问题:

为了在 MySQL 中存储价格值,您可能应该查看DECIMAL 类型,它可以让您存储带小数位的精确值。

于 2009-05-01T19:20:58.807 回答
4

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

intval(number_format($problematic_float, 0, '', ''));
于 2013-04-23T19:27:24.137 回答
3

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

于 2009-05-01T19:16:59.893 回答
0

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

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

例如,而不是 $price_corrected = $price*100;

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

于 2013-04-27T12:10:41.960 回答