【问题标题】:Unexpected rounding with money_format, sample code included使用 money_format 进行意外舍入,包括示例代码
【发布时间】:2014-07-18 16:05:48
【问题描述】:

我正在使用 money_format,格式为 %.2n,但结果很奇怪。

我已经整理了示例代码,所以其他人可以自己测试。

<?php
    setlocale(LC_MONETARY, 'en_US');
    $deal = array(
        array(
            'amt' => 1350,
            'rate' => .75,
            'lod' => 47
        ),
        array(
            'amt' => 990,
            'rate' => .75,
            'lod' => 27
        ),
        array(
            'amt' => 4180,
            'rate' => .75,
            'lod' => 65
        ),
        array(
            'amt' => 2370,
            'rate' => .75,
            'lod' => 26
        )
    );
    foreach ($deal as $value) {
        $fee = (($value['amt'] / 1000) * $value['rate']) * $value['lod'];
        var_dump($fee);
        echo '<br />money_format: ', money_format('%.2n', $fee), '<br />number_format: ', number_format($fee, 2), '<br /><br />';
    }

输出:

float(47.5875)
money_format: $47.59
number_format: 47.59

float(20.0475)
money_format: $20.05
number_format: 20.05

float(203.775)
money_format: $203.77
number_format: 203.78

float(46.215)
money_format: $46.22
number_format: 46.22

您会注意到第三个结果,203.775 显示为 $203.77,而不是 $203.78。

我对 money_format 的理解是否遗漏了什么?

phpfiddle 链接:http://phpfiddle.io/fiddle/1909516587

【问题讨论】:

  • 为什么不直接使用number_format,它更容易使用。使用money_format的原因是什么?
  • 他只是想酷一点,用一个没人用的功能。 :P
  • 这似乎是一个错误bugs.php.net/bug.php?id=61787
  • 是的,我可以使用 number_format,但这并不能解释结果。因此,为什么我要问我对函数的理解是否不正确。

标签: php rounding-error money-format


【解决方案1】:

这是floating point math operations 的常见问题

造成混淆的原因是默认情况下 PHP 中的精度设置得太低,并且在从 var_dump 显示时显示浮点的舍入值。

来源:http://php.net/manual/en/language.types.float.php

要查看实际数字,您需要设置更高的精度或使用printf 定义精度,这将显示更多来自$fee 的浮点值。

ini_set('precision',32);
var_dump($fee);
//or
printf("%01.32f", $fee);

精度示例:https://3v4l.org/ue0Th

float(47.587500000000005684341886080801)
money_format: 47.59
number_format: 47.59

float(20.04749999999999943156581139192)
money_format: 20.05
number_format: 20.05

float(203.77499999999997726263245567679)
money_format: 203.77
number_format: 203.78

float(46.215000000000003410605131648481)
money_format: 46.22
number_format: 46.22

因此,我们可以看到 number_format 不正确地舍入。当给定相同的十进制值时,我们得到一个四舍五入的数字和一个不四舍五入的数字:https://3v4l.org/pgKDs

echo number_format(23.77499999999997, 2); //returns 23.78
echo number_format(2.77499999999997, 2); //returns 2.77

作为一般规则,尽量不要使用涉及精度的浮点数学,并倾向于使用尽可能低的面额(在货币的意义上是一美分),或者依赖 bcmath 函数并使用乘数来检​​索小数值. http://php.net/manual/en/book.bc.php


将计算更改为使用 bcmath 将解决 money_formatnumber_format 的问题:

BC 数学示例:https://3v4l.org/JEZds

bcscale(4);
foreach ($deal as $value) {
    $fee = (float) bcmul(bcmul(bcdiv($value['amt'], 1000), $value['rate']), $value['lod']);
    echo 'money_format: ' . money_format('%.2n', $fee) . PHP_EOL;
    echo 'number_format: ' . number_format($fee, 2) . PHP_EOL . PHP_EOL;
}

结果:

string(7) "47.5875"
money_format: 47.59
number_format: 47.59

string(7) "20.0475"
money_format: 20.05
number_format: 20.05

string(8) "203.7750"
money_format: 203.78
number_format: 203.78

string(7) "46.2150"
money_format: 46.22
number_format: 46.22

【讨论】:

  • @Andre 更新了 BCMath 函数修复
【解决方案2】:

国际支持的解决方案如下:

<?php

setlocale(LC_MONETARY, 'en_US');

function _money_format($number, $decimals=2){
    $number = number_format($number, $decimals);
    $local_settings = localeconv();
    $currency_symbol = $local_settings['currency_symbol'];
    return $currency_symbol . $number;
}

echo _money_format('23.4567');

?>

仍然得到货币符号 + 使用 number_format 应该得到你需要的东西。

在这里演示:http://phpfiddle.io/fiddle/772115651

【讨论】:

    猜你喜欢
    • 2020-08-06
    • 2020-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-16
    • 1970-01-01
    • 2018-12-19
    相关资源
    最近更新 更多