4

我有一个小型金融应用程序,前端是 PHP,后端是 MySQL。我有古老的偏见,我在 MySQL 中将货币值存储为整数美分。我的 HTML 表单允许输入美元值,例如“156.64”,我使用 PHP 将其转换为美分,然后将美分存储在数据库中。

我有一个函数可以从表单中清除美元值,并将其转换为美分。我去掉前导文本,去掉尾随文本,乘以 100 并转换为整数。最后一步是

$cents = (integer) ($dollars * 100);

这对几乎所有东西都适用,除了像 '156.64' 这样的极少数值始终转换为 15663 美分。为什么这样做?

如果我这样做:

$cents = (integer) ($dollars * 100 + 0.5);

然后它一直有效。为什么我需要添加该舍入值?

另外,我对将金额存储为整数而不是浮点值的偏见是否不再需要?现代浮动计算是否会产生足以保持 100% 准确会计的完美四舍五入和准确的货币价值?

4

9 回答 9

4

如果您想要精确,您应该在 MySQL 中使用DECIMAL数据类型存储您的货币值。

于 2008-10-11T02:12:58.807 回答
2

你对花车的“偏见”永远不会被克服——这是它们工作方式的基础。无需过多详细说明,它们存储基于 2 的幂的数字,并且由于并非所有十进制数都可以以这种方式呈现,因此它并不总是有效。您唯一可靠的解决方案是将数字存储为数字序列和小数点的位置(根据上述 DECIMAL 类型)。

我不是 100% 使用 PHP,但是乘法是否有可能将整数转换为浮点数,从而准确地引入你想要避免的问题?

于 2008-10-11T02:21:11.117 回答
2

货币/货币值不应该作为浮点数存储在数据库中(或在程序中使用)。

您的整数方法很好,在可用的情况下使用DECIMAL,NUMERICMONEY类型也是如此。

您的问题是由 $dollars 被视为浮点数引起的,而 PHP 没有更好的类型来处理金钱。根据分配 $dollars 的时间,它可能被视为字符串或浮点数,但如果它仍然是 * 100 操作的字符串(如果它看起来像浮点数),则肯定会转换为浮点数。

您最好自己将字符串解析为整数“金钱”值(使用正则表达式),而不是依赖 PHP 正在执行的隐式转换。

于 2008-10-11T04:03:14.203 回答
2

铸造不像round()四舍五入那样,它会截断小数点:(int)3.99yield 3(int)-3.99产量-3

由于浮点运算经常会导致错误(并且可能不是您想要的方向),round()如果您想要可靠的舍入,请使用。

于 2008-10-11T04:33:30.757 回答
2

您发布的代码首先进行乘法运算,在将值转换为整数之前强制进行会引入错误的浮点计算。相反,您应该通过颠倒顺序来完全避免浮点运算。首先转换为整数值,然后执行算术运算。

假设之前的代码已经验证并格式化了输入,试试这个:

list($bills, $pennies) = explode('.', $dollars);
$cents = 100 * $bills + $pennies;

您对代表货币的浮点值的偏见是有根据的,因为截断以及因为值从 base-10 转换为 base-2 并再次转换回来。

于 2008-10-13T05:31:29.093 回答
1

你永远不应该以浮点数存储货币,因为它总是会得到你意想不到的结果。

查看 php BC Maths,它允许您将货币存储为字符串,然后对它们执行非常高精度的算术运算。

于 2011-01-04T23:00:02.480 回答
0

如果您通过浮点运算(没有双关语)输入钱,那么将钱存储为整数是没有意义的。如果您想从 to 转换stringint与您的“偏见”保持一致,您可以简单地使用字符串函数。

您可以使用任意精度库除以 10(它们在内部将数字作为字符串处理),例如bcdiv()或gmp_div_q( ),但当然,您也可以从一开始就使用它来进行所有数学运算。

或者您可以使用纯字符串函数:

<?php
// Quick ugly code not fully tested
$input = '156.64';
$output = NULL;

if( preg_match('/\d+(\.\d+)?/', $input) ){
    $tmp = explode('.', $input);
    switch( count($tmp) ){
        case 1:
            $output = $tmp[0];
            break;

        case 2:
            $output = $tmp[0] . substr($tmp[1], 0, 2);
            break;

        default:
            echo "Invalid decimal\n";
    }
}else{
    echo "Invalid number\n";
}

var_dump($output);

?>
于 2010-01-14T13:29:23.730 回答
0

而不是使用

$cents = (integer) ($dollars * 100);

您可能想尝试使用:

$cents = bcmul($dollars, 100, 2);

于 2009-07-04T05:39:17.410 回答
0

从浮点数转换为整数时,数字将向src)四舍五入。

阅读浮点精度警告。

于 2010-01-14T12:31:21.917 回答