php intval() 和 Floor() 返回值太低?
-
03-07-2019 - |
题
因为 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 浮点数 文档。
其他提示
PHP正在根据有效数字进行舍入。它隐藏了不准确性(第2行)。当然,当地板出现时,它不会更好地知道它并且一直停下来。
对于这个“问题”而言,这可能是另一种可能的解决方案:
intval(number_format($problematic_float, 0, '', ''));
如上所述这不是PHP本身的问题,它更多的是处理不能表示为有限浮点值的分数的问题,因此在舍入时会导致字符丢失。
解决方案是确保在处理浮点值时需要保持准确性 - 使用gmp函数或BC数学函数 - bcpow,bcmul等。问题将很容易解决。
而不是 $ price_corrected = $ price * 100;
使用$ price_corrected = bcmul($ price,100);