提问人:Guillermo Phillips 提问时间:1/22/2021 最后编辑:Guillermo Phillips 更新时间:1/22/2021 访问量:253
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
答:
另一个可用的函数是 round(),它接受两个参数 - 要四舍五入的数字,以及要四舍五入到的小数位数。如果 一个数字正好在两个整数之间,round() 总是 四舍五入。
使用回合 :
echo round (1298.34*100);
结果:
129834
评论
地板
完全相同的概念错误,只是颠倒了。使用或 - 如果你真的因为某种奇怪的原因不能 - 至少为你的秤添加一个可以忽略不计的数量(通常是一半)并使用.即使这样也不安全,因为四舍五入行为不是标准化的,你可以有“财务四舍五入”和“严格四舍五入”。ceil
round
floor
您可以使用 round()
四舍五入到所需的精度,并在四舍五入最后 5 时具有预期的行为(这是您可能会遇到的另一个财务障碍)。
$display = round(3895.0 / 3.0, 2);
另外,提醒一下,我习惯于总是用最后一个点或“.0”来写浮点整数。这可以防止某些语言推断错误的类型并进行整数除法,因此 5 / 3 将产生 1。
如果你需要一个“自定义舍入”,并且想要确定,那么,它不起作用的原因是,并非所有浮点数都存在于机器表示中。1298.34不存在;取而代之的是1298.33999999999999999124。
因此,当您将其乘以 100 并得到 129833.9999999999999124 时,截断它当然会产生129833。
然后,您需要做的是添加少量,该数量必须足以弥补机器错误,但不足以在财务计算中发挥作用。有一种算法可以确定这个数量,但你可能会得到“升级后的千分之一”。
所以:
$display = floor((3895.0 / 3.0)*100.0 + 0.001);
请注意,您将“看到”为 1234.56 的这个数字可能再次不精确存在。它可能真的是 1234.56000000000000123 或 1234.5599999999999876。这可能会在复杂的复合计算中产生后果。
评论
由于您正在与财务部门合作,因此您应该使用某种货币库(https://github.com/moneyphp/money)。几乎所有其他解决方案都在自找麻烦。
我不推荐的其他方法是:a) 仅使用整数,b) 计算或 c) 使用 Money 库中的 Number 类,例如:bcmath
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]);
}
也可作为此小提琴的现场测试。
评论
首先,您需要阅读PHP官方页面上的RED部分 https://www.php.net/manual/en/language.types.float.php
浮点数的操作并不精确,我们需要使用特殊函数 https://www.php.net/manual/en/ref.bc.php
为什么?因为
// 输出浮点数(129833.99999999999)var_dump(1298.34*100);
如何使用官方数学函数修复它?
捷径 - 使用 number_format()
number_format(1298.34*100, 0, '.', ''));
Long way - 使用 bc 函数
// 输出 “129834”bcmul(1298.34, 100);
如果要设置小数的精度,请执行以下操作:
// 输出 “129834.00”bcmul(1298.34, 100, 2);
最后,你可以把它投射到浮动状态
$result = (float)bcmul(1298.34, 100, 2);
小心 bcmul 中的 secound 参数,它不会向上或向下舍入,它会削减小数位数
四舍五入:
number_format自动向上舍入,但如果要向下使用
round($result,2,PHP_ROUND_HALF_DOWN) //round DOWN to 2 decimals
评论
自从我问这个问题以来,我一直在想这个问题的纯数值解决方案。
答案是在发言之前在乘数上增加一小部分:
echo floor(1298.349 * (100 + 1/10000)) / 100;
给出预期的答案:
1298.34
要添加多少分数取决于要处理的最大数字大小。对于小数点后两位,一般公式为:
echo floor($amount * (100 + 1 / ($max_num * 10))) / 100;
这适用于浮点数中任意数量的小数位。$amount
$amount
为了证明这可行,这里有一个小的测试程序。这将检查最多三个小数位的所有浮点数,截断到小数点后两位。它根据纯字符串解检查数值解,并输出第一个失败。
$increment = 1/((float) 100000);
$multiplier_float = (float) (100 + $increment);
for ($integer_part = 1; $integer_part <= 20000; $integer_part++) {
$integer_part_string = (string) $integer_part;
for ($decimal_part = 0; $decimal_part <= 999; $decimal_part++) {
$decimal_part_string = str_pad($decimal_part, 3, '0', STR_PAD_LEFT);
$truncated_decimal_part_string = substr($decimal_part_string, 0, 2);
$number_string = $integer_part_string . '.' . $decimal_part_string;
$number_float = (float) $number_string;
$number_truncated_string = $integer_part_string . '.' . $truncated_decimal_part_string;
$number_truncated_float = floor($number_float * $multiplier_float)/(float) 100;
$number_truncated_float_as_string = (string) $number_truncated_float;
if ($number_truncated_string != $number_truncated_float_as_string) {
echo $number_string . ' ' . $number_truncated_string . ' ' . $number_truncated_float_as_string . "\n";
exit;
}
}
}
我明确地将浮点数转换为浮点数以使其更清晰。
如果更改 您可以检查第一次故障发生的位置。该示例正在检查最多 20,000 个数字。您将看到第一个故障发生在 10000.009 上。$increment
实际上,在处理货币值时,将有最大数字大小(例如 6 个有效位置或更少)。因此,此解决方案的性能可能更高,并且是单行的。
上一个:PHP精度与大数字
评论