把钱存在小数列里——精确度和比例是多少?

我使用一个十进制列在数据库中存储货币值,今天我想知道要使用什么样的精度和比例。

既然假定固定宽度的字符列更有效率,我认为十进制列也可以这样。是吗?

我应该使用什么样的精度和规模? 我想的是24/8精度。这是过度杀伤,还是不够还是可以?


我决定这么做:

  • 将转换率(如果适用)以浮点形式存储在事务表本身中
  • 将货币存储在帐户表中
  • 交易金额为 DECIMAL(19,4)
  • 所有使用转换率的计算将由我的应用程序处理,因此我保持对舍入问题的控制

我不认为转换率的浮点数是一个问题,因为它主要是作为参考,而且无论如何我将把它转换为小数。

谢谢你们的宝贵意见。

129116 次浏览

SQLServer 上的货币数据类型在小数后面有四位数字。

来自 SQLServer2000联机丛书:

货币数据代表正数或负数的货币数额。在 MicrosoftSQLServerTM 2000中,货币数据使用货币和小额货币数据类型存储。货币数据的存储精度可达小数点后四位。使用 money 数据类型将值存储在从 -922,337,203,685,477.5808到 + 922,337,203,685,477.5807的范围内(存储值需要8个字节)。使用 smallmoney 数据类型将值存储在 -214,748.3648到214,748.3647之间(存储值需要4个字节)。如果需要更多的小数位,则改为使用十进制数据类型。

如果您使用的是 IBM Informix Dynamic Server,那么您将拥有一个 MONEY 类型,它是 DECIMAL 或 NUMERIC 类型的次要变体。它始终是定点类型(而 DECIMAL 可以是浮点类型)。您可以指定从1到32的刻度,以及从0到32的精度(默认为刻度为16,精度为2)。因此,根据你需要存储的数据,你可以使用 DECIMAL (16,2)——仍然足以将美国联邦赤字保持在最接近1美分的水平——或者你可以使用一个更小的范围,或者更多的小数位。

有时你需要去不到一分钱,有国际货币使用非常大的妖魔化。例如,您可能在每次交易中向客户收取0.088美分。在我的 Oracle 数据库中,列被定义为 NUMBER (20,4)

我认为,在很大程度上,您或您的客户的需求应该决定使用的精度和规模。例如,对于电子商务网站,我正在处理的钱只有英镑,我已经被要求保持它的十进制(6,2)。

如果你正在寻找一个一刀切的尺寸,我建议 DECIMAL(19, 4)是一个流行的选择(快速谷歌证明了这一点)。我认为这源于旧的 VBA/Access/Jet Currency 数据类型,它是语言中的第一个定点小数类型; Decimal只是在 VB6/VBA6/Jet 4.0中以‘ version 1.0’风格(即未完全实现)出现。

对于定点小数值的 储藏室,经验法则是至少存储一个小数位,而这个小数位是允许四舍五入的。将前端的旧 Currency类型映射到后端的 DECIMAL(19, 4)类型的原因之一是,Currency表现出银行家的舍入性质,而 DECIMAL(p, s)则表现出截断舍入性质。

DECIMAL的存储空间中增加一个小数点后的位置,可以实现自定义舍入算法,而不是采用供应商的默认值(至少可以说,对于一个期望所有以.5结尾的值从零舍入的设计者来说,银行家的舍入是令人担忧的)。

是的,我觉得 DECIMAL(24, 8)听起来有点过了。大多数货币的报价为小数点后四或五位。我知道有些情况需要一个8(或更多)的小数位 ,但这是一个“正常”的货币量(比如说四个小数位)已经按比例分配,这意味着小数精度应该相应降低(也考虑在这种情况下浮点类型)。如今,没有人有那么多钱来要求精度达到小数点后24位:)

然而,一些研究可能是合适的,而不是一刀切的方法。向您的设计师或领域专家询问可能适用的会计准则: GAAP、 EU 等。我依稀记得一些欧盟国家之间的转账,它们有四舍五入到小数点后五位的明确规则,因此使用 DECIMAL(p, 6)进行存储。会计师通常倾向于小数点后四位。


PS 避免使用 SQL Server 的 MONEY数据类型,因为它在舍入时存在严重的准确性问题,其他考虑因素包括可移植性等。见 Aaron Bertrand 的博客


微软和语言设计者之所以选择银行家舍入,是因为硬件设计者选择了它[引用? ].例如,它被载入美国电气与电子工程师协会(IEEE)标准。硬件设计者选择它是因为数学家更喜欢它。参见 维基百科; 转述: 1906年版的概率和错误理论称这为“计算机的规则”(“计算机”意味着人类执行计算)。

小数点后4位可以精确地存储世界上最小的货币子单位。如果你需要小额支付(纳米支付? !) ,你可以进一步降低准确性。

相对于特定于 DBMS 的货币类型,我也更喜欢 DECIMAL,在 IMO 应用程序中保持这种逻辑更安全。同样的另一种方法是简单地使用[ long ]整数,在应用程序级别将其格式化为 unit.subunit,以提高人们的可读性(= 货币符号)。

如果你要在数据库中进行任何类型的算术运算(乘以计费率等) ,你可能需要比这里的人建议的更高的精度,出于同样的原因,你永远不会想在应用程序代码中使用任何低于双精度浮点值。

我们最近实现了一个系统,该系统需要处理多种货币的价值并在它们之间进行转换,并通过艰难的方式解决了一些问题。

永远不要用浮点数来换钱

浮点数算法会引入一些不准确的地方,这些地方在他们搞砸某些事情之前可能不会被注意到。所有的值应该存储为整数或固定小数类型,如果你选择使用固定小数类型,然后确保你准确地理解该类型在底层做什么(即,它内部使用整数或浮点类型)。

当需要进行计算或转换时:

  1. 将值转换为浮点数
  2. 计算新值
  3. 将该数字四舍五入,并将其转换回整数

在步骤3中,当将一个浮点数转换回一个整数时,不要只是强制转换它——首先使用一个数学函数对它进行四舍五入。这通常是 round,虽然在特殊情况下它可能是 floorceil。知道区别,仔细选择。

将数字的类型存储在值旁边

如果你只处理一种货币,这对你来说可能没有那么重要,但对我们处理多种货币来说却很重要。我们使用3个字符的代码表示货币,如美元、英镑、日元、欧元等。

根据具体情况,存储以下信息可能也有帮助:

  • 数字是税前还是税后(以及税率是多少)
  • 数字是否是转换的结果(以及它是由什么转换的)

知道你要处理的数字的准确界限

对于实际值,您希望精确到货币的最小单位。这意味着你没有小于一分钱、一分钱、一日元、一分钱等的价值。不要无缘无故地存储比那个精度更高的值。

在内部,您可以选择处理较小的值,在这种情况下,这是 不同类型的货币价值。确保您的代码知道哪些是哪些,并且不要将它们混淆。即使在这里也要避免使用浮点值。


将所有这些规则加在一起,我们决定采用以下规则。在运行代码中,货币使用最小单位的整数存储。

class Currency {
String code;       //  eg "USD"
int value;         //  eg 2500
boolean converted;
}


class Price {
Currency grossValue;
Currency netValue;
Tax taxRate;
}

在数据库中,这些值以下列格式存储为字符串:

USD:2500

存储的价值是25美元。我们之所以能够做到这一点,仅仅是因为处理货币的代码不需要位于数据库层本身,所以所有的值都可以首先转换为内存。其他情况无疑会导致其他解决方案。


以防我之前没说清楚 别用浮子!

在使用 MySQL 处理货币时,如果你知道货币值的精确度,可以使用 DECIMAL (13,2) ; 如果你只是想要一个快速、足够好的 Circa,可以使用 DOUble。 因此,如果您的应用程序需要处理高达1万亿美元(或欧元或英镑)的货币价值,那么这应该可以工作:

DECIMAL(13, 2)

或者,如果你需要遵守 公认会计原则,那么使用:

DECIMAL(13, 4)

这个回答有点晚,不过我已经用过了

DECIMAL(13,2)

我觉得应该可以达到99.999.999.999.99。