因为 PHP 中的 float 数据类型不准确,并且 MySQL 中的 FLOAT 比 INT 占用更多空间(并且不准确),所以我总是将价格存储为 INT,在存储之前乘以 100,以确保精确到小数点后两位。但我认为 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)

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

有帮助吗?

解决方案

尝试将此作为快速修复:

$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 浮点数 文档。

其他提示

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

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

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

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

对于这个“问题”而言,这可能是另一种可能的解决方案:

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

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

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

而不是 $ price_corrected = $ price * 100;

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top