在十进制列中存钱-精度和小数位数是多少?


173

我正在使用一个十进制列将货币值存储在数据库中,今天我想知道使用什么精度和小数位数。

由于据说固定宽度的char列效率更高,所以我想对于十进制列也是如此。是吗?

我应该使用什么精度和规模?我正在考虑精度24/8。那是矫kill过正,还不够还是可以?


这是我决定要做的:

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

我认为转换率的浮动不成问题,因为它主要用于参考,而且无论如何我都会将其转换为小数。

谢谢大家的宝贵意见。


2
问自己一个问题:真的有必要以十进制形式存储数据吗?我不能将数据存储为美分/便士->整数?
Terence

5
DECIMAL(19, 4) 是一种流行的选择,请在检查并在此处检查世界货币格式,以确定要使用的小数位数,希望有所帮助。
shaijut

Answers:


181

如果您正在寻找一种千篇一律的产品,我建议您DECIMAL(19, 4)选择一个流行的选择(Google很快就证实了这一点)。我认为这源自旧的VBA / Access / Jet Currency数据类型,它是该语言中的第一个定点十进制类型;Decimal在VB6 / VBA6 / Jet 4.0中仅采用“版本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的博客


微软和语言设计师选择银行家四舍五入是因为硬件设计师选择了[citation?]。例如,它包含在电气和电子工程师协会(IEEE)标准中。硬件设计师之所以选择它,是因为数学家更喜欢它。参见维基百科 ; 解释一下:1906年的《概率与错误理论》称此为“计算机规则”(“计算机”是指执行计算的人类)。


1
有关语言设计师为何选择Banker的舍入的更多信息,请参见此答案本页
Nick Chammas 2012年

1
一天时间:银行家的四舍五入不是由于微软。@NickChammas:也不是语言设计师的发明。微软和语言设计师之所以选择它,是因为硬件设计师选择了它。例如,它包含在电气和电子工程师协会(IEEE)标准中。硬件设计师之所以选择它,是因为数学家更喜欢它。参见en.wikipedia.org/wiki/Rounding#History ; 解释一下:1906年的《概率与错误理论》称此为“计算机规则”(“计算机”是指执行计算的人类)。
pho13 2013年

9
那我应该用比特币做什么?
工具包

1
@onedaywhen为什么DECIMAL(19, 4)比最受欢迎DECIMAL(19, 2)?大多数世界货币仅两位小数。
cokedude

3
@ zypA13510:是的,我的主张是如此十年前!但是,这是我投票得票最多的第三项,并且对我的SO代表来说是相当大的变化,所以我很在意:)
某天,

104

我们最近实施了一个系统,该系统需要处理多种货币的值并在它们之间进行转换,并且很难找到一些方法。

切勿使用浮点数来赚钱

浮点算术会引入一些不准确的错误,直到它们弄乱了某些东西。所有值都应存储为整数或固定小数类型,如果选择使用固定小数类型,请确保完全了解该类型在幕后的作用(即,它内部使用整数还是浮点数)类型)。

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

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

在第3步中将浮点数转换回整数时,不要只是将其强制转换-请先使用数学函数将其舍入。通常是这样round,尽管在特殊情况下可能是floorceil。了解差异并谨慎选择。

将数字的类型与值一起存储

如果您只处理一种货币,这对您可能并不重要,但对于我们处理多种货币而言,这对我们很重要。我们使用3个字符的代码表示一种货币,例如USD,GBP,JPY,EUR等。

根据情况,存储以下内容也可能会有所帮助:

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

了解您要处理的数字的精度范围

对于实值,您希望与货币的最小单位一样精确。这意味着您没有不小于美分,一分钱,日元,一分钱等的值。不要无缘无故地存储比该精度更高的值。

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


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

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

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

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

USD:2500

它将存储$ 25.00的值。我们之所以能够做到这一点,仅仅是因为处理货币的代码不需要位于数据库层本身内,因此所有值都可以首先转换为内存。毫无疑问,其他情况将适用于其他解决方案。


并且如果我之前没有弄清楚,请不要使用float!


1
永远不要说永远:有时金额会按比例分配,需要稍后再累加。示例:将总股息(相对较小)除以已发行股数(相对较小),得出每股净值。有时浮球更好:)
一天,

23
我永远不会站着。浮点规范的不准确之处将使您进行的计算更多。如果您需要存储小于一美分或一美分的值,请定义所需的精度级别并坚持下去。不要使用浮点数。说真的 这是一个坏主意。
马库斯·唐宁

4
此答案还与Douglas Crockford在他的“ JavaScript上的Crockford系列”中概述的javascript最佳实践一致,他建议在PENNIES中进行所有货币计算,以避免舍入时出现机器错误。因此,如果您使用Javascript中的货币,则以这种方式存储值非常有意义。
paperreduction

1
@onedaywhen这些每股净额案例中,您可以将按比例分配的金额相加,然后与原始总数进行比较,并设计一种处理余数的策略(使用十进制/整数类型时)。
2014年

2
对于Mysql,bigint如果打算按数量排序,建议将整数(2500)存储为。并且在处理大整数时不要浪费时间使用PHP 32bit,升级到64bit或Node.JS;)
Ricky Boyce

4

在MySQL中处理货币时,如果您知道货币值的精度,请使用DECIMAL(13,2);如果您只想获得足够好的近似值,请使用DOUBLE。因此,如果您的应用程序需要处理高达一万亿美元(或欧元或英镑)的货币值,那么这应该可以工作:

DECIMAL(13, 2)

或者,如果您需要遵守GAAP,请使用:

DECIMAL(13, 4)

3
您能否链接到GAAP指南的特定部分而不是2500页文档的内容?谢谢。
ReactingToAngularVues

@ReactingToAngularVues似乎页面已更改。抱歉
pollux1er

2

小数点后四位将使您能够准确地存储世界上最小的货币子单位。如果您需要小额付款(nanopayment ?!)的准确性,可以将其进一步降低。

我也更喜欢DECIMAL特定于DBMS的货币类型,您可以更安全地在应用程序IMO中保留这种逻辑。同样的另一种方法是简单地使用[长]整数,将其格式化为¤unit.subunit以便在应用程序级别进行人工阅读(¤=货币符号)。


1

SQL Server上的money数据类型在小数点后有四位数。

从SQL Server 2000联机丛书中:

货币数据代表正数或负数。在Microsoft®SQL Server™2000中,货币数据是使用money和s​​mallmoney数据类型存储的。货币数据可以存储到小数点后四位的精度。使用money数据类型可以存储-922,337,203,685,477.5808到+922,337,203,685,477.5807之间的值(需要8个字节来存储值)。使用smallmoney数据类型来存储-214,748.3648到214,748.3647范围内的值(需要4个字节来存储值)。如果需要更多的小数位数,请改用小数数据类型。


1

有时您需要将其降到不足一美分,并且有些国际货币使用非常大的贬值。例如,您可能向客户收取每笔交易0.088美分的费用。在我的Oracle数据库中,这些列定义为NUMBER(20,4)


1

如果您打算在数据库中执行任何算术运算(乘以开票费率等),出于与您从未想过的相同原因,您可能想要的精度比这里的建议要高得多。想要在应用程序代码中使用小于双精度浮点值的任何内容。


那就是我的想法,但是就货币汇率而言(即将津巴布韦美元转换为美元)。我将对正在使用的数据库(psql,sqlite)进行一些实验,以了解它们如何处理很小的小数舍入。
伊万,

另外,浮点数在某些dbms /语言中是否不存在准确性问题?
伊万,

2
浮点数在所有语言中都存在精度问题。
马库斯·唐宁

如今,最常见的建议是使用任意精度(think BigDecimal),但长期以来一直是双精度(think double而不是float)。同样,在某些情况下,任意精度也会带来明显的性能损失。测试绝对是正确的方法。
汉克·盖伊

0

如果使用的是IBM Informix Dynamic Server,则将具有MONEY类型,这是DECIMAL或NUMERIC类型的次要变体。它始终是定点类型(而DECIMAL可以是浮点类型)。您可以在1到32之间指定比例,在0到32之间指定精度(默认为16比例,精度为2)。因此,根据需要存储的内容,您可以使用DECIMAL(16,2)-仍然足够大以将美国联邦赤字保持到最接近的美分-或者您可以使用较小的范围或更多的小数位。


0

我认为您或您的客户的需求在很大程度上应决定要使用的精度和规模。例如,对于我正在处理的一个电子商务网站,它仅处理GBP货币,我被要求将其保持为Decimal(6,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.