it-swarm.cn

使用Float或Decimal作为会计应用程序的美元金额?

我们正在用VB.NET和SQL Server重写我们的旧会计系统。我们引入了一个新的.NET/SQL程序员团队来进行重写。大多数系统已经使用Floats完成了Dollar数量。我编程的遗留系统语言没有Float,所以我可能会使用Decimal。

你有什么建议?

Float或Decimal数据类型应该用于美元金额吗?

两者的优点和缺点有哪些?

在我们的日常scrum中提到的一个骗局是,当你计算一个返回超过两个小数位的结果的金额时,你必须要小心。听起来你必须将金额四舍五入到小数点后两位。

另一个Con是所有显示和打印金额必须有一个显示两个小数位的格式声明。我注意到有几次没有这样做,金额看起来不正确。 (即10.2或10.2546)

专家是Float只占用磁盘上的8个字节,其中Decimal占用9个字节(十进制12,2)

75
Gerhard Weiss

Float或Decimal数据类型应该用于美元金额吗?

答案很简单。永远不要漂浮。 _ never _

浮点数符合 IEEE 754 always二进制,只有新标准 IEEE 754R 定义的十进制格式。许多小数二进制部分永远不能等于精确的十进制表示。
任何二进制数都可以写成m/2^nmn正整数),任何十进制数作为m/(2^n*5^n)
由于二进制文件缺少素数factor 5,所有二进制数都可以用小数精确表示,但反之亦然。

0.3 = 3/(2^1 * 5^1) = 0.3

0.3 = [0.25/0.5] [0.25/0.375] [0.25/3.125] [0.2825/3.125]

          1/4         1/8         1/16          1/32

因此,您最终会得到一个高于或低于给定十进制数的数字。总是。

为什么这么重要?四舍五入。
正常舍入表示0..4向下,5..9向上。所以它 确实 重要的是,如果结果是0.049999999999....或0.0500000000 ..你可能知道它意味着5美分,但是计算机不知道那个并且将0.4999...向下舍入(错误)和0.5000... up(右)。
鉴于浮点计算的结果总是包含小的错误项,因此决定是纯粹的运气。如果你想要使用二进制数进行十进制舍入到偶数处理,那就无望了。

不相信?你坚持认为在你的帐户系统中一切都很好吗?
资产和负债相等吗?好的,然后获取每个条目的每个给定格式化的数字,解析它们并用独立的十进制系统求和它们!将其与格式化的总和进行比较。
糟糕,有些不对劲,不是吗?

对于那个计算,需要极高的准确性和保真度(我们使用Oracle的FLOAT),因此我们可以记录得到的“十分之一便士”。

无助于解决此错误。因为所有人都自动认为计算机总和正确,所以几乎没有人独立检查。

107
TSK
41
Nakilon

首先你应该阅读这个 每个计算机科学家应该知道的浮点运算 。那么你应该考虑使用某种类型的 定点/任意精度数 package(例如Java BigNum,python十进制模块),否则你将陷入一个受伤的世界。然后弄清楚是否使用本机SQL十进制类型就足够了。

浮点/双打存在(ed)以暴露快速x87 fp,现在已经过时了。如果您关心计算的准确性和/或不完全补偿其限制,请不要使用它们。

22
Rich Schuler

正如另一个警告,SQL Server和.Net框架使用不同的默认算法进行舍入。请务必查看Math.Round()中的MidPointRounding参数。 .Net框架默认使用Bankers算法,SQL Server使用Symmetric Algorithmic Rounding。查看维基百科文章 这里

10
Darrel Miller

问问你的会计师!他们会对你使用漂浮物皱眉。就像之前发布的一些,如果您不关心准确性,请仅使用float。虽然在谈到金钱时我总是反对它。

在会计软件中不可接受浮动。使用带小数点后4位的小数。

7
Ricardo C

浮点有意想不到的无理数。

例如,你不能存储1/3作为小数,它将是0.3333333333 ......(依此类推)

浮点数实际上存储为二进制值和2指数幂。

所以1.5被存储为3 x 2到-1(或3/2)

使用这些base-2指数会创建一些奇怪的无理数,例如:

将1.1转换为浮点数然后再将其转换回来,结果如下:1.0999999999989

这是因为1.1的二进制表示实际上是154811237190861 x 2 ^ -47,超过双倍可以处理。

关于这个问题的更多关于 我的博客 ,但基本上,对于存储,你最好用小数点。

在Microsoft SQL服务器上,您具有money数据类型 - 这通常最适合金融存储。精确到4位小数。

对于计算,你有更多的问题 - 不准确性是一小部分,但把它放入幂函数,它很快变得重要。

但是小数对于任何类型的数学都不是很好 - 例如,没有对十进制幂的原生支持。

6
Keith

这里有点背景......

没有数字系统可以准确处理所有实数。所有这些都有其局限性,这包括标准IEEE浮点和带符号小数。每个位使用的IEEE浮点更准确,但这并不重要。

财务数据基于几个世纪的纸笔练习以及相关的惯例。它们相当准确,但更重要的是,它们是可重复的。使用各种数字和费率的两名会计师应该提出相同的数字。任何差异的余地都是欺诈的余地。

因此,对于财务计算,正确答案是与擅长算术的注册会计师给出相同答案的答案。这是十进制算术,而不是IEEE浮点。

5
David Thornley

使用SQL server的decimal类型。

不要使用moneyfloat

money使用4位小数,比使用decimal 快但是遇到一些明显的和一些不那么明显的舍入问题( 请看这个连接问题

5
Mitch Wheat

我推荐的是使用64位整数来存储整个东西。

5
Joshua

浮点数不是精确表示,精度问题是可能的,例如在添加非常大和非常小的值时。这就是为什么小数类型被推荐用于货币的原因,即使精度问题可能非常罕见。

为了澄清,十进制12,2类型将精确地存储这14个数字,而浮点数将不会,因为它在内部使用二进制表示。例如,0.01不能用浮点数精确表示 - 最接近的表示实际上是0.0099999998

4
Niall

对于我帮助开发的银行系统,我负责系统的“应计利息”部分。每天,我的代码计算了当天余额累计(收入)的利息。

对于该计算,需要极高的准确性和保真度(我们使用Oracle的FLOAT),因此我们可以记录累积的“十分之一便士”。

当涉及“利用”利息(即将利息支付回您的账户)时,金额将四舍五入到一分钱。帐户余额的数据类型是两位小数。 (实际上它更复杂,因为它是一个可以在许多小数位上工作的多货币系统 - 但我们总是四舍五入到该货币的“便士”)。是的 - 那里有损失和收益的“分数”,但是当计算机数字被实现(支付或支付的钱)时,它总是真正的货币价值。

这使会计师,审计师和测试人员满意。

所以,请咨询您的客户。他们会告诉你他们的银行/会计规则和做法。

4
Guy

使用Float换钱的唯一原因是你不关心准确的答案。

4
David Singer

在会计系统中你应该注意的另一件事是没有人应该直接访问这些表。这意味着对会计系统的所有访问都必须通过存储过程进行。这可以防止欺诈,而不仅仅是SQl注入攻击。想要提交欺诈的内部用户不应该有能力直接更改数据库表中的数据。这是对您系统的严格内部控制。你真的想要一些心怀不满的员工去你的数据库的后端并让它开始对他们进行检查吗?或者隐藏他们在没有批准权限的情况下批准未经授权的供应商的费用?整个组织中只有两个人能够直接访问您的财务数据库,dba和备份中的数据。如果您有许多dbas,则只有其中两个应具有此访问权限。

我之所以提到这一点,是因为如果你的程序员在会计系统中使用float,他们可能完全不熟悉内部控制的想法,并且在编程工作中没有考虑它们。

3
HLGEM

甚至比使用小数更好的是使用普通的旧整数(或者某种bigint)。这样,您始终可以获得最高精度,但可以指定精度。例如,数字100可能意味着1.00,其格式如下:

int cents = num % 100;
int dollars = (num - cents) / 100;
printf("%d.%02d", dollars, cents);

如果您希望获得更高的精度,可以将100更改为更大的值,例如:10 ^ n,其中n是小数位数。

3
Peter Stuifzand

在100个分数n/100中,其中n是自然数,使得0 <= n且n <100,只有四个可以表示为浮点数。看一下这个C程序的输出:

#include <stdio.h>

int main()
{
    printf("Mapping 100 numbers between 0 and 1 ");
    printf("to their hexadecimal exponential form (HEF).\n");
    printf("Most of them do not equal their HEFs. That means ");
    printf("that their representations as floats ");
    printf("differ from their actual values.\n");
    double f = 0.01;
    int i;
    for (i = 0; i < 100; i++) {
        printf("%1.2f -> %a\n",f*i,f*i);
    }
    printf("Printing 128 'float-compatible' numbers ");
    printf("together with their HEFs for comparison.\n");
    f = 0x1p-7; // ==0.0071825
    for (i = 0; i < 0x80; i++) {
        printf("%1.7f -> %a\n",f*i,f*i);
    }
    return 0;
}
2
Lars Bohl

你总是可以为.Net写一些钱类型。

看看这篇文章: CLR的钱类型 - 作者在我看来做了很好的工作。

2
Tomer Pintel

我一直在使用SQL的钱类型来存储货币价值。最近,我不得不与许多在线支付系统合作,并注意到其中一些使用整数来存储货币价值。在我目前和新的项目中,我已经开始使用整数,我非常满意这个解决方案。

2
George

您可能希望使用某种形式的固定点表示来表示货币值。您还需要调查Banker的舍入(也称为“舍入半舍入”)。它避免了通常的“舍入半部分”方法中存在的偏差。

1
user6931

您是否考虑过使用货币数据类型存储美元金额?

关于十进制占用一个字节的Con,我会说不关心它。在100万行中,您将只使用1 MB以上,而且这些天存储非常便宜。

1
Espo

无论你做什么,都需要注意舍入错误。使用比您显示的更高精度计算。

1
1800 INFORMATION

您的会计师希望控制您的回合方式。使用float意味着你将不断地舍入,通常使用FORMAT()类型语句,这不是你想要的方式(使用地板/天花板代替)。

你有货币数据类型(money,smallmoney),应该使用它而不是float或real。存储小数(12,2)将消除您的舍入,但也会在中间步骤中消除它们 - 这实际上不是您在财务应用程序中所需要的。

0
David T. Macknet

这是一篇很好的文章描述 何时使用float和decimal 。 Float存储近似值,decimal存储精确值。

总之,像货币这样的确切值应使用小数,而科学测量等近似值应使用浮点数。

这是一个有趣的例子,它表明浮点数和小数都能够失去精度。添加一个非整数的数字,然后减去相同的数字浮点数会导致精度损失,而小数则不会:

    DECLARE @Float1 float, @Float2 float, @Float3 float, @Float4 float; 
    SET @Float1 = 54; 
    SET @Float2 = 3.1; 
    SET @Float3 = 0 + @Float1 + @Float2; 
    SELECT @Float3 - @Float1 - @Float2 AS "Should be 0";

Should be 0 
---------------------- 
1.13797860024079E-15

当乘以非整数并除以相同的数字时,小数会失去精度,而浮点数则不会。

DECLARE @Fixed1 decimal(8,4), @Fixed2 decimal(8,4), @Fixed3 decimal(8,4); 
SET @Fixed1 = 54; 
SET @Fixed2 = 0.03; 
SET @Fixed3 = 1 * @Fixed1 / @Fixed2; 
SELECT @Fixed3 / @Fixed1 * @Fixed2 AS "Should be 1";

Should be 1 
--------------------------------------- 
0.99999999999999900
0
BrokeMyLegBiking

始终使用十进制。由于四舍五入问题,Float会给您不准确的值。

0
Roel Vlemmings

浮点数可以表示数字是基数的负数倍 - 对于二进制浮点数,当然是两个浮点数。

在二进制浮点中只有四个小数部分可以精确表示:0,0.25,0.5和0.75。其他所有东西都是近似值,就像0.3333 ...是十进制算术1/3的近似值一样。

浮点是计算的一个很好的选择,其中结果的比例是重要的。如果你想要精确到一些小数位,这是一个糟糕的选择。

0
Mike Dimmick