我正在开发一个系统,我需要四舍五入到最接近的便士财务付款。我天真地以为我会乘以 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
由于您提到您仅将其用于显示目的,因此您可以获取金额,将其转换为字符串并截断第二位小数之后的任何内容。正则表达式可以完成这项工作:
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中进行现场测试。
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。这可能会对复杂的复合计算产生影响。
由于您正在从事金融工作,因此您应该使用某种 Money 库(https://github.com/moneyphp/money
)。几乎所有其他解决方案都是自找麻烦。我不推荐的其他方法是:a)仅使用整数,b)使用 bcmath
进行计算或 c)使用 Money 库中的 Number 类,例如:
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();
}
首先您需要阅读 PHP 官方页面上的红色部分
https://www.php.net/manual/en/language.types.float.php
https://www.php.net/manual/en/ref.bc.php
为什么?因为//输出浮点数(129833.99999999999)
如何用官方数学函数解决这个问题?
bcmul(1298.34, 100);
//输出“129834”
如果您想设置小数精度,请执行以下操作:
bcmul(1298.34, 100, 2);
//输出“129834.00”
最后你可以将其投射到浮动
$result = (float)bcmul(1298.34, 100, 2);
如果您想向上或向下舍入您的$结果,请使用:
round($result,2) //round UP to 2 decimals
round($result,2,PHP_ROUND_HALF_DOWN) //round DOWN to 2 decimals
另一个可用的函数是 round(),它有两个参数 - 要舍入的数字以及要舍入的小数位数。如果 一个数字正好是两个整数之间的一半,round() 总是 向上舍入。
结果:
echo round (1298.34*100);
129834