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 中作为现场测试使用。
我正在开发一个需要四舍五入到最接近的便士财务付款的系统。我天真地以为我会乘以 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 中作为现场测试使用。