使用 Java 在 PostgreSQL 上的货币数据

Posted

技术标签:

【中文标题】使用 Java 在 PostgreSQL 上的货币数据【英文标题】:Money data on PostgreSQL using Java 【发布时间】:2013-08-12 17:45:06 【问题描述】:

我正在编写一个挖掘货币兑换数据的 Java 程序。数据可以有多个十进制数字,例如“0.973047”。经过一番研究,我发现 BigDecimal 是 Java 的正确数据类型,但我应该为 PostgreSQL 使用哪种数据类型?

【问题讨论】:

【参考方案1】:

NUMERIC/DECIMAL

作为Joachim Isaksson said,您想使用NUMERIC/DECIMAL 类型,作为任意精度类型。

关于NUMERIC/DECIMAL的两个重点:

仔细阅读doc 以了解您应该指定比例以避免默认比例0,这意味着小数部分被截断的整数值。虽然这是 Postgres 偏离标准 SQL 的地方之一(让您可以扩大到实施限制)。因此,不指定比例是一个糟糕的选择。 根据 SQL 标准,NUMERICDECIMAL 的 SQL 类型 接近但不相同。在SQL:92 中,遵循您为NUMERIC 指定的精度,而对于DECIMAL,允许数据库服务器添加超出您指定的额外精度。这里 Postgres 再次偏离标准,NUMERICDECIMAL documented 是等效的。

条款:

精度是数字中的总位数。 Scale 是小数点右边的位数(小数部分)。 ( Precision - Scale ) = 小数点左边的位数(整数部分)。

明确项目的精度和规模规格:

精度必须足够大,以处理未来可能需要的更大数字。这意味着......也许您的应用今天的工作量为数千美元,但将来必须执行汇总报告,最终达到数百万美元。 出于某些会计目的,您可能需要存储最小货币金额的一小部分。意思是……超过 3 或 4 位小数,而不是 USD 中的一分钱所需的 2 位。

避免MONEY 类型

Postgres 也提供MONEY 类型。这听起来可能是对的,但对于大多数用途来说可能不是最好的。一个缺点是MONEY规模由基于locale 的数据库范围的配置设置 设置。因此,当您切换服务器或进行其他更改时,该设置很容易发生危险的变化。此外,您无法控制特定列的设置,但您可以在NUMERIC 类型的每一列上设置比例。最后,MONEY 不是标准 SQL,如 list of standard SQL data types 所示。 Postgres 包含MONEY 以方便人们从其他数据库系统移植数据。

移动小数点

有些人采用的另一种选择是移动小数点,并以大整数数据类型存储。

例如,如果将USD 美元存储为一美分,则将任何给定的小数乘以 100,然后转换为整数类型,然后继续。例如,$123.45 变成整数 12,345。

这种方法的好处是执行时间更快。对整数执行诸如sum 之类的操作非常快。整数的另一个好处是更少的内存使用。

我发现这种方法很烦人、令人困惑且有风险。烦人,因为计算机应该我们工作,而不是反对我们。有风险,因为某些程序员或用户可能会忽略乘法/除法以转换回小数,从而给出不正确的结果。如果在没有对精确小数的良好支持的系统中工作,这种方法可能是一种可接受的解决方法。

当我们在 SQL 中使用 DECIMAL/NUMERIC 在 Java 中使用 BigDecimal 时,我认为移动小数点没有任何好处。

四舍五入和NaN

在您的应用程序编程以及在 Postgres 服务器端进行的任何计算中,要非常小心并注意小数部分的舍入和截断。并测试无意中弹出的NaNs。

在应用程序和 Postgres 双方中,始终避免使用 floating point 数据类型来赚钱。 浮点是为性能速度而设计的,但以准确性为代价计算可能会导致小数部分出现看似疯狂的额外数字。不适合财务/金钱或其他需要准确性的目的。

BigDecimal

是的,在 Java 中,您希望 BigDecimal 作为您的任意精度类型。 BigDecimal 速度较慢并使用更多内存,但会准确存储您的金额。 SQL NUMERIC/DECIMAL 应该映射到BigDecimal,正如在here 和*** 上讨论的那样。

BigDecimal 是 Java 最棒的地方之一。我不知道有任何其他平台具有类似的类别,尤其是一个实施良好且经过多年改进和修复的平台。

使用BigDecimal 肯定比使用Java’s floating-point types、floatdouble 慢。但在现实世界的应用程序中,我怀疑你的金钱计算会成为任何瓶颈。此外,您或您的客户想要哪一种:最快金钱计算,还是准确金钱计算? ?

我一直认为BigDecimal 是 Java 中最大的沉睡特性,与许多其他缺乏对小数的复杂支持的平台相比,使用 Java 平台的最重要优势。

类似问题:Best Data Type For Currency

【讨论】:

使用Money 的另一个问题是,如果它是根据服务器的语言环境设置的,是否会使处理多种货币变得困难。或者,如果服务器时钟已设置为 UTC/GMT.... numericinteger(和“移动小数点”)的一个缺点在 the PostgreSQL documentation (version 9.1) 中提到——“与整数类型相比,数值的算术非常慢,或者到浮点类型”。 @KennyEvitt 是的,这就是浮点的全部意义(看看我在那里做了什么?)......速度。浮点牺牲精度以获得更快的性能。但是,现代计算机每秒执行几 十亿 次计算,您应该在通过破解数据类型过早优化之前三思而后行。在您确定(可测量的、凭经验的)您有问题之前,请在您的数据类型中使用适当的拟合。 嗨@basil-bourque。你说,“仔细阅读文档以了解你应该指定比例以避免默认比例 0。”看起来只有在没有刻度的情况下指定精度时,刻度才为 0。如果两者都没有指定,它“创建一个列,其中可以存储任何精度和比例的数值,直到精度的实现限制。这种列不会将输入值强制为任何特定比例。”对我来说,这似乎是对开发人员非常友好的行为——这似乎是一个不错的选择。可能值得调整您的答案以反映这一点。【参考方案2】:

要获得尽可能好的(和精确的)精度,您可以使用NUMERIC(或其别名DECIMAL),它具有很高的精度,可以让您决定所需的精度;

数字

用户指定精度,精确到小数点前131072位;小数点后最多 16383 位

【讨论】:

很好的答案,除了 DECIMAL 不是 NUMERIC 的别名。虽然非常相似,但 SQL 规范允许数据库实现以比您使用 DECIMAL 列指定的精度更高的精度存储数据。对于 NUMERIC 列,规范要求数据库实现尊重您指定的精度。 进一步说明:SQL 规范要求两种数据类型都遵守您指定的比例。要亲自查看规范,请参阅 SQL:92 规范草案,第 6.1 节规则 17 和 18。 在 Postgres 实现中:The types decimal and numeric are equivalent. Both types are part of the SQL standard. 如所述 here @Genma 正如我所说和展示的,这些类型在 SQL 标准和其他数据库中是等效的。这是 Postgres 偏离标准的少数几个地方之一,而且不是很好。因此将这两种类型视为等价是不明智的。详情请见my Answer。【参考方案3】:

一般来说,钱不应该存储为浮点数。最好的方法通常是将金额存储为最小允许大小的整数(例如,一美分),并将其格式化以供输入和显示。这本质上就是在 SQL 中固定精度的DECIMAL 列所做的事情,但是如果将其传输回 Java,您仍然冒着丢失精度的风险(例如,如果您正好拆分最后一个允许数字的一半会发生什么情况)?

【讨论】:

以上是关于使用 Java 在 PostgreSQL 上的货币数据的主要内容,如果未能解决你的问题,请参考以下文章

将 Openshift 上的 PostgreSQL 连接到 Amazon S3

woocommerce上的多种货币,可根据位置自动切换[关闭]

Heroku 上的 PSQLException

Money-rails Gem 货币符号在某些设备上的重叠问题

Elastic Beanstalk 上的 PostgreSQL (Amazon Linux 2)

OSGi 上的 PostgreSQL 连接器