使用浮点数还是小数作为会计应用程序的美元金额?


75

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

您有什么建议?

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

两者都有哪些利弊?

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

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

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


5
回去摆脱你的花车。
罗伦·佩希特尔

Answers:


110

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

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

浮点数根据IEEE 754始终为二进制,只有新标准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...起来(右)。
鉴于浮点计算的结果始终包含较小的误差项,因此该决定纯属偶然。如果您希望使用二进制数字进行十进制的舍入到偶数处理,它将毫无希望。

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

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

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


但是,如果要进行特别是除法的计算,请确保在小数字段中至少使用4个小数位。
HLGEM,

1
并确保您知道(默认)$ 0.045会四舍五入为$ 0.04,$ 0.055会四舍五入为$ 0.06
Keith,2009年

7
对于那些不确定Keith含义的人,小数类型使用另一种舍入。它似乎通常被称为“银行家四舍五入”,但Wikipedia有许多备用名称:半舍入到偶数,无偏四舍五入,收敛四舍五入,统计学家四舍五入,荷兰语四舍五入,高斯四舍五入或银行家四舍五入(en.wikipedia.org / wiki /…)。
patridge 2010年

2
要记住的另一件事是Decimal.Round和String.Format给出不同的结果:Decimal.Round(0.045M,2)= 0.04和String.Format(“ {0:0.00}”,0.045M)= 0.05
Jason Massey 2012年


22

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

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


1
虽然了解有关浮点的更多信息很有用,但是在C#中使用十进制类型类似于您所建议的使用定点/任意精度数字包,该包是语言内置的。请参阅msdn.microsoft.com/zh-cn/library/system.decimal.aspx,以获取有关十进制如何存储带有十进制小数的精确乘方10而不是十进制分量的2的乘方的解释(它基本上是一个带小数位置组件的int )。
克里斯·莫斯基尼

1
“暴露出现在已经过时的快速x87 fp”,多数民众赞成在没有浮点数的情况下仍然是计算机上最常用的数据类型之一,例如模拟,游戏,信号处理……
markmnl 2015年

10

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


7

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

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


6

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

例如,您不能将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位。

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

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


3
“非理性”不是您要查找的词。1/3仍然是有理数,但它没有有限的二进制表示形式……
Brian Postow

是的,我知道-我只是不知道该怎么称呼:无法代表的数字太罗word了。
基思

它们是近似值,但是可以表示的数字也可以近似。实际的无理数是一个不能用整数表示的数,而与底数无关。这些数字可以10为基数,但不能以2为基数
Keith,

许多与非终止十进制表示-那太罗嗦了!
肯尼·埃维特

也许您可以说浮点数通常存储意外的和不相关的分数值。
肯尼·埃维特

5

使用SQL Server的小数类型。

不要用浮动

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


请参阅@David Thornley的答案。这可能是该货币类型最密切再现但是会计惯例(中)接近他们。
罗伊·廷克

5

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


有一个明显的警告,即分毫值(即$ 0.015)根本无法表示。大多数应用的合理限制。
rocketmonkeys 2010年

简单的解决方案:储存于成千上万美分的。我的东西存放在货币的百万分之一的问题..
Marenz

1
检查您的溢出。百万分之一的溢价刚好超过200亿美元。千分之二的货币为20万亿美元(可能接受或不可接受),而百分之20的万亿美元(我认为是安全的)。
约书亚

@Marenz:在计算的任何给定阶段,通常应该可以定义将要执行计算的最小尺寸单位,并且除了明确显示的内容之外,在任何一点上都不会发生任何大小的舍入误差四舍五入。如果某人以1美元的价格在3处购买了五千个东西,则总价格通常应为1666.67美元(5000/3,四舍五入到一分钱),而不是1666.66667美元(5000/3,四舍五入到1/1000便士)或1666.65美元(0.33333) 5000)。
supercat 2012年

5

这里有点背景...

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

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

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



4

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

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


小数也不是精确的,除非它们是无限的精度。
1800信息

0.1可以精确地存储在十进制字段中。小数并非对于每个数字都是精确的,但是对于大多数(某些)常见货币金额来说都是精确的。有时。
rocketmonkeys 2010年

4

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

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

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

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

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


一分钱的十亿分之一是0.01 ^ e-9-绝对没有理由在这里使用Oracle的FLOAT来实现“极高的准确性和逼真度”,因为它是浮点表示形式,它是一个近似数字而不是精确数字。TSQL的DECIMAL(38,18)会更准确。在您不解释如何处理多种货币的情况下,我怀疑您是不会出错的。如果测试人员从欧元转换为津巴布韦美元,他们可能会看到一个真正的四舍五入问题。
约翰·扎布罗斯基

为了澄清起见,我在应计利息过程中使用了浮点数。小数用于实际交易(应计利息支付时)。当时的系统是单一货币。如果我有时间,我可能不会使用浮子。:)
盖伊2014年

3

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

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

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


3
如果您的固定点类型不好,就应该这样做。好处是您可以确定小数点在哪里,缺点是要弄乱它。如果可以获得定点类型,则不必担心。
Michael Kohne

3

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

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



2

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


我假设您在过程中使用ROUND动词?
Gerhard Weiss,

如果您的意思是在SQL方面,则否。我更喜欢DAL像数据库一样返回整数。我是在业务逻辑层中进行转换的。整数美分=值%100; 整数美元=(价值-美分)/ 100; 使用.NET 3.5,我有一个扩展方法。
乔治,

2

在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;
}

为此,我复制了上面的代码并在键盘中运行了它。codepad.org/03hAQZwq这包括输出。
多米尼克K

1

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

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


1
不要使用money数据类型。(这是来自SyBase的宿醉)
米奇·

1

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


1

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


0

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


0

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

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

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


0

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

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

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

    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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.