PHP 舍入浮点数

PHP Rounding Float

我正在开发一个需要四舍五入到最接近的便士财务付款的系统。我天真地以为我会乘以 100,请发言,然后再除以。然而,下面的例子是错误的:

echo 1298.34*100;

正确显示:

129834

但是

echo floor(1298.34*100);

意外显示:

129833

我在使用 intval 时遇到了同样的问题。

我怀疑乘法与浮点舍入不符。但是如果我不能依赖乘法,我该怎么做呢?我总是想可靠地四舍五入,我不需要考虑负数。

需要说明的是,我希望去掉任何小数便士金额:

1298.345 should give 1298.34
1298.349 should give 1298.34
1298.342 should give 1298.34

he other function available is round(), which takes two parameters - the number to round, and the number of decimal places to round to. If a number is exactly half way between two integers, round() will always round up.

使用回合:

echo round (1298.34*100);

结果:

129834

您可以使用 round() 舍入到所需的精度,并在舍入最后的 5 时具有预期的行为(这是您可能遇到的另一个财务障碍)。

  $display = round(3895.0 / 3.0, 2);

此外,提醒一下,我习惯于在写浮点数 整数 的时候最后加上一个点或“.0”。这可以防止某些语言推断出错误的类型并进行整数除法运算,这样 5 / 3 将得到 1.

如果您需要“自定义舍入”并希望确定,那么它不起作用的原因是因为并非所有浮点数 都存在 机器表示中。 1298.34 不存在;确实存在的东西(我正在编造精确的数字!)在它的位置可能是 1298.33999999999999124.

所以当你将它乘以 100 得到 129833.999999999999124 时,当然截断它会产生 129833。

然后您需要做的是添加少量数量,该数量必须足以弥补机器误差不足以影响财务计算。有一种算法可以确定这个数量,但你可能会说“放大后的千分之一”。

所以:

 $display = floor((3895.0 / 3.0)*100.0 + 0.001);

请注意,您将“看到”为 1234.56 的这个 数字,可能再次不准确地存在。它可能真的是 1234.5600000000000123 或 1234.559999999999876。这可能会对复杂的复合计算产生影响。

由于您从事财务工作,因此您应该使用某种货币库 (https://github.com/moneyphp/money)。几乎所有其他解决方案都是自找麻烦。


我不推荐的其他方法是:a) 仅使用整数,b) 使用 bcmath 计算或 c) 使用 Money 库中的数字 class 例如:

function getMoneyValue($value): string
{
    if (!is_numeric($value)) {
        throw new \RuntimeException(sprintf('Money value has to be a numeric value, "%s" given', is_object($value) ? get_class($value) : gettype($value)));
    }

    $number = \Money\Number::fromNumber($value)->base10(-2);

    return $number->getIntegerPart();   
}

由于您提到您仅将此用于显示目的,您可以获取金额,将其转换为字符串,然后 运行对小数点后第二位的任何内容进行分类。正则表达式可以完成这项工作:

preg_match('/\d+\.{0,1}\d{0,2}/', (string) $amount, $matches);

此表达式适用于任意小数位数(包括零)。具体工作原理:

  • \d+ 匹配任意数量的数字
  • \.{0,1} 匹配 0 或 1 个文字点
  • \d{0,2} 匹配点
  • 后的零个或两个数字

您可以运行下面的代码进行测试:

$amounts = [
    1298,
    1298.3,
    1298.34,
    1298.341,
    1298.349279745,
];
foreach ($amounts as $amount) {
    preg_match('/\d+\.{0,1}\d{0,2}/', (string) $amount, $matches);
    var_dump($matches[0]);
}

也可在 this fiddle 中作为现场测试使用。