首页 / 知识

关于sql server:使用浮点数还是小数来表示会计应用程序的美元金额?

2023-04-15 07:11:00

Use Float or Decimal for Accounting Application Dollar Amount?

我们正在VB.NET和SQL Server中重写旧的记帐系统。我们引入了一个新的.NET / SQL程序员团队来进行重写。大多数系统已经使用浮动金额以美元金额完成。我编程的传统系统语言没有浮点数,因此我可能会使用十进制。

你有什么建议?

浮点数或小数数据类型应用于美元金额吗?

两者都有哪些利弊?

我们每天的讨论中提到的一个骗局是,当您计算返回的结果超过两位小数时,必须小心。听起来您必须将金额四舍五入到小数点后两位。

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

优点是Float仅占用磁盘上的8个字节,而十进制将占用9个字节(十进制12,2)


Should Float or Decimal data type be used for dollar amounts?

答案很简单。永不漂浮。永不!

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

1
2
3
4
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进行四舍五入。向上(右)。
鉴于浮点计算的结果始终包含较小的误差项,因此该决定纯属偶然。如果您希望使用二进制数字进行十进制的舍入到偶数处理,它将毫无希望。

不服气?您坚持认为在您的帐户系统中一切正常吗?
资产负债相等吗?好的,然后获取每个条目的每个给定格式的数字,解析它们并使用独立的十进制系统求和!
将其与格式化的总和进行比较。
糟糕,出问题了,不是吗?

For that calculation, extreme accuracy and fidelity was required (we used Oracle's
FLOAT) so we could record the"billionth's of a penny" being accured.

无助于解决此错误。因为所有人都会自动假定计算机正确,所以实际上没有人独立检查。


这张照片回答:

photo1,C.O.

这是另一种情况:来自北安普敦的人收到一封信,说如果他不付零美元零零钱,他的家就会被扣押!

photo2,C.O.


首先,您应该阅读每位计算机科学家应该了解的有关浮点算法的内容。然后,您应该真正考虑使用某种类型的定点/任意精度数字包(例如java BigNum,python十进制模块),否则您将遭受很大的伤害。然后确定使用本地SQL十进制类型是否足够。

浮点数/双精度数已存在,可以暴露现在已经过时的快速x87 fp。如果您担心计算的准确性和/或不能完全弥补其局限性,请不要使用它们。


就像其他警告一样,SQL Server和.Net框架使用不同的默认算法进行舍入。确保在Math.Round()中签出MidPointRounding参数。 .Net框架默认使用Bankers算法,而SQL Server使用对称算法舍入。在此处查看Wikipedia文章


问你的会计师!他们会因使用浮子而对您皱眉。就像以前发布的一些一样,仅在不关心准确性的情况下才使用float。尽管在金钱方面我总是反对的。

在会计软件中,不接受浮点数。使用带4个小数点的小数。


浮点数具有意外的无理数。

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

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

因此1.5被存储为-1(或3/2)的3 x 2

使用这些以2为底的指数可以创建一些奇数的无理数,例如:

将1.1转换为float,然后再次将其转换回,您的结果将类似于:1.0999999999989

这是因为1.1的二进制表示形式实际上是154811237190861 x 2 ^ -47,远远超过了double可以处理的数量。

有关此问题的更多信息,请访问我的博客,但基本上,对于存储,最好使用小数点。

在Microsoft SQL Server上,您具有money数据类型-通常最适合财务存储。精确到小数点后4位。

对于计算,您还有更多问题-不精确度只是很小的一部分,但是将其放入幂函数中并很快变得很重要。

但是,小数对于任何数学都不是很好-例如,没有对小数幂的本地支持。


这里有点背景...

没有数字系统可以准确地处理所有实数。它们都有局限性,包括标准IEEE浮点数和带符号的十进制数。 IEEE浮点数使用的每位精度更高,但这并不重要。

财务数字基于数百年来的纸笔练习以及相关惯例。它们相当准确,但更重要的是,它们是可重现的。两名使用不同编号和费率的会计师应得出相同的编号。任何差异的空间就是欺诈的空间。

因此,对于财务计算,正确的答案是与擅长算术的CPA给出相同答案的一切。这是十进制算法,不是IEEE浮点数。


我建议使用64位整数,以分存储整个数据。


使用SQL Server的十进制类型。

不要用钱或浮动。

金钱使用小数点后四位,比使用小数点要快,但舍入会遇到一些明显的问题,以及一些不是那么明显的问题(请参阅此连接问题)


对于我帮助开发的银行系统,我负责系统的"应计利息"部分。每天,我的代码都会计算出当天的余额产生了多少利息。

对于该计算,需要极高的准确性和逼真度(我们使用了Oracle的FLOAT),因此我们可以记录所累积的"十亿分之一美分"。

当涉及"资本化"利息(即将利息退还到您的帐户中)时,金额将四舍五入到一分钱。帐户余额的数据类型为两位小数。 (实际上,它更复杂,因为它是一种可以在许多小数位工作的多货币系统,但我们总是四舍五入到该货币的"便士")。是的-那里有损失和收益的"零碎"部分,但是当计算机数字被实际实现(已付或已付的钱)时,它始终是真实货币值。

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

因此,请与您的客户联系。他们会告诉您他们的银行/会计规则和惯例。


使用Float赚钱的唯一原因是,如果您不关心准确的答案。


浮点数不是精确的表示形式,例如在添加非常大和非常小的值时,可能会出现精度问题。因此,即使精度问题可能很少出现,也建议使用十进制类型作为货币。

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


比使用小数点更好的是,仅使用普通的旧整数(或某种Bigint)。这样,您始终可以获得最高的精度,但是可以指定精度。例如,数字100可能表示1.00,其格式如下:

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

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


在会计系统中,您还应该注意的另一件事是,没有人可以直接访问这些表。这意味着对会计系统的所有访问都必须通过存储的proc。这不仅可以防止欺诈,而且还可以防止SQ1注入攻击。想要进行欺诈的内部用户永远不能直接更改数据库表中的数据。这是系统上的关键内部控制。您是否真的要让一些心怀不满的员工进入您的数据库后端,并开始编写他们的支票?还是隐藏他们在没有批准权限的情况下批准了对未经授权的供应商的费用?整个组织中只有两个人应该可以直接访问财务数据库,dba及其备份中的数据。如果您有许多dbas,则其中只有两个应该具有此访问权限。

我之所以这样说是因为,如果您的程序员在会计系统中使用了float,那么他们很可能完全不熟悉内部控制的概念,并且在编程工作中没有考虑到它们。


我一直在使用SQL的money类型存储货币值。最近,我不得不使用许多在线支付系统,并且注意到其中一些使用整数来存储货币值。在当前和新项目中,我开始使用整数,对此解决方案我很满意。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

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

您总是可以为.Net编写类似Money类型的内容。

看一下这篇文章:CLR的Money类型-我认为作者做得非常出色。


无论您做什么,都需要小心舍入错误。使用比您显示的精度更高的精度进行计算。


您是否考虑过使用money-data类型存储美元金额?

关于小数点再占用一个字节的缺点,我会说不关心它。在100万行中,您将仅再使用1 MB,而如今这些存储非常便宜。


您可能需要对货币值使用某种形式的定点表示形式。您还将需要研究Banker的舍入(也称为"半舍入")。它避免了通常的"四舍五入"方法中存在的偏差。


您的会计师将要控制您的四舍五入方式。使用float意味着您将经常使用FORMAT()类型的语句不断舍入,这不是您想要的方式(而是使用floor / ceiling)。

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


始终使用十进制。由于四舍五入问题,Float将为您提供不正确的值。


这是一篇很棒的文章,描述了何时使用浮点数和十进制。浮点数存储一个近似值,十进制数存储一个精确值。

总而言之,精确值(如金钱)应使用小数,而近似值(如科学测量)应使用浮点。

这是一个有趣的示例,显示浮点数和十进制数均会丢失精度。当添加非整数的数字然后减去相同的数字时,float会导致精度下降,而十进制不会:

1
2
3
4
5
6
7
8
9
    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

当将一个非整数乘以该数字后,小数会丢失精度,而浮点数则不会。

1
2
3
4
5
6
7
8
9
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

浮点数只能表示为底数的负倍之和的数字-对于二进制浮点数,当然是2。

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

对于结果的标度很重要的计算,浮点数是一个不错的选择。在试图精确到小数位数的地方,这是一个错误的选择。


应用程序金额浮点数美元

最新内容

相关内容

猜你喜欢