ORM / DSL 中返回的浮点类型
Floating point types returned in ORM / DSL
The Java™ Tutorials 指出 "this data type [double] should never be used for precise values, such as currency." ORM / DSL 正在为存储用于计算货币金额的值的数据库列返回浮点数这一事实是否存在问题?我正在使用 QueryDSL 并且正在处理金钱。 QueryDSL 为任何精度高达 16 的数字返回 Double
,此后返回 BigDecimal
。这让我很担心,因为我知道浮点运算不适合货币计算。
来自 this QueryDSL issue 我被引导相信 Hibernate 做同样的事情;参见 OracleDialect
。为什么它使用 Double
而不是 BigDecimal
?检索 Double
并构造 BigDecimal
是否安全,或者是否有可能错误地表示精度小于 16 的数字? Double
是否只有在执行算术运算时才会出现浮点问题,或者是否存在无法准确初始化的值?
使用浮点数存储货币确实是 bad idea。浮点数可以逼近一个运算结果,但在处理金钱时,那不是你想要的。
以数据库可移植的方式修复它的最简单方法是简单地存储美分。这是在金融业务中处理货币业务的推荐方式。请注意,大多数数据库都使用 half-away from zero 舍入算法,因此请确保它适合您的上下文。
谈到钱,你应该总是问当地的会计师,尤其是四舍五入的部分。安全比抱歉好。
现在回到你的问题:
Is it safe to retrieve the Double and construct a BigDecimal, or is
there a chance that a number with a precision of less than 16 could be
incorrectly represented?
只要您的数据库最多使用 16 位精度,此操作就是安全的。如果它使用更高的精度,则需要覆盖 OracleDialect 和
Is it only when performing arithmetic operations that a Double can
have floating-point issues, or are there values to which it cannot be
accurately initialised?
执行算术运算时,您必须始终考虑货币四舍五入,这也适用于 BigDecimal
。因此,如果您可以保证数据库值在转换为 java Double 时不会丢失任何小数,则可以从中创建 BigDecimal
。在将算术运算应用于数据库加载值时,使用 BigDecimal
会有所回报。
关于16的门槛,根据Wiki:
The 11 bit width of the exponent allows the representation of numbers
with a decimal exponent between 10−308 and 10308, with full 15–17
decimal digits precision. By compromising precision, subnormal
representation allows values smaller than 10−323.
想来想去,我必须得出结论,我自己的问题的答案:
Is the fact that an ORM / DSL is returning floating point numbers for database columns storing values to be used to calculate monetary amounts a problem?
简单地说,是的。请继续阅读。
Is it safe to retrieve the Double and construct a BigDecimal, or is there a chance that a number with a precision of less than 16 could be incorrectly represented?
以下示例中错误地表示了精度小于 16 位十进制数字的数字。
BigDecimal foo = new BigDecimal(1000.1d);
foo
的BigDecimal
值为1000.1000000000000227373675443232059478759765625。 1000.1 的精度为 1,并且从 BigDecimal
值的精度 14 误表示。
Is it only when performing arithmetic operations that a Double can have floating-point issues, or are there values to which it cannot be accurately initialised?
根据上面的例子,有些值无法准确初始化。正如 The Java™ Tutorials 明确指出的那样,"This data type [float / double] should never be used for precise values, such as currency. For that, you will need to use the java.math.BigDecimal class instead."
有趣的是,调用 BigDecimal.valueOf(someDouble)
起初似乎神奇地解决了问题,但在意识到它调用 Double.toString()
然后阅读 Double
's documentation 时,很明显这也不适合精确值.
总之,在处理精确值时,浮点数永远不合适。因此,在我看来,除非另有说明,否则 ORM / DSL 应该映射到 BigDecimal
,因为大多数数据库使用都涉及精确值的计算。
更新:
基于这个结论,我提出了 this issue with QueryDSL。
不仅是算术运算,还是纯读写。
Oracle NUMBER
和 BigDecimal
都使用 decadic base。因此,当您从数据库中读取数字然后将其存储回去时,您可以确定写入了相同的数字。 (除非它超过 Oracle 的 38 位数字限制)。
如果您将 NUMBER
转换为二进制基数 (Double
),然后将其转换回十进制,那么您可能会遇到问题。而且这个操作一定要慢很多。
Robert Bain 的问题、评论和回答中似乎提到了几个问题。我收集并解释了其中的一些内容。
Is it safe to use a double to store a precise value?
是的,前提是有效位数(精度)足够小。
If a decimal string with at most 15 significant digits is converted to IEEE 754 double precision representation and then converted back to a string with the same number of significant digits, then the final string should match the original.
But new BigDecimal(1000.1d)
has the value 1000.1000000000000227373675443232059478759765625, why not 1000.1?
在上面的引用中我强调了 - 当从双精度转换时,必须指定有效数字的数量,例如
new BigDecimal(1000.1d, new MathContext(15))
Is it safe to use a double for arbitrary arithmetic on precise values?
不,计算中使用的每个中间值都可能引入额外的错误。
使用双精度值存储精确值应该被视为一种优化。它引入了风险,如果不小心,精度可能会丢失。使用 BigDecimal
不太可能产生意想不到的后果,应该是您的默认选择。
Is it correct that QueryDSL returns a double for precise value?
这不一定不正确,但可能并不理想。我建议您与 QueryDSL 开发人员合作...但我看到您已经提出 an issue 并且他们打算改变这种行为。
The Java™ Tutorials 指出 "this data type [double] should never be used for precise values, such as currency." ORM / DSL 正在为存储用于计算货币金额的值的数据库列返回浮点数这一事实是否存在问题?我正在使用 QueryDSL 并且正在处理金钱。 QueryDSL 为任何精度高达 16 的数字返回 Double
,此后返回 BigDecimal
。这让我很担心,因为我知道浮点运算不适合货币计算。
来自 this QueryDSL issue 我被引导相信 Hibernate 做同样的事情;参见 OracleDialect
。为什么它使用 Double
而不是 BigDecimal
?检索 Double
并构造 BigDecimal
是否安全,或者是否有可能错误地表示精度小于 16 的数字? Double
是否只有在执行算术运算时才会出现浮点问题,或者是否存在无法准确初始化的值?
使用浮点数存储货币确实是 bad idea。浮点数可以逼近一个运算结果,但在处理金钱时,那不是你想要的。
以数据库可移植的方式修复它的最简单方法是简单地存储美分。这是在金融业务中处理货币业务的推荐方式。请注意,大多数数据库都使用 half-away from zero 舍入算法,因此请确保它适合您的上下文。
谈到钱,你应该总是问当地的会计师,尤其是四舍五入的部分。安全比抱歉好。
现在回到你的问题:
Is it safe to retrieve the Double and construct a BigDecimal, or is there a chance that a number with a precision of less than 16 could be incorrectly represented?
只要您的数据库最多使用 16 位精度,此操作就是安全的。如果它使用更高的精度,则需要覆盖 OracleDialect 和
Is it only when performing arithmetic operations that a Double can have floating-point issues, or are there values to which it cannot be accurately initialised?
执行算术运算时,您必须始终考虑货币四舍五入,这也适用于
BigDecimal
。因此,如果您可以保证数据库值在转换为 java Double 时不会丢失任何小数,则可以从中创建BigDecimal
。在将算术运算应用于数据库加载值时,使用BigDecimal
会有所回报。
关于16的门槛,根据Wiki:
The 11 bit width of the exponent allows the representation of numbers with a decimal exponent between 10−308 and 10308, with full 15–17 decimal digits precision. By compromising precision, subnormal representation allows values smaller than 10−323.
想来想去,我必须得出结论,我自己的问题的答案:
Is the fact that an ORM / DSL is returning floating point numbers for database columns storing values to be used to calculate monetary amounts a problem?
简单地说,是的。请继续阅读。
Is it safe to retrieve the Double and construct a BigDecimal, or is there a chance that a number with a precision of less than 16 could be incorrectly represented?
以下示例中错误地表示了精度小于 16 位十进制数字的数字。
BigDecimal foo = new BigDecimal(1000.1d);
foo
的BigDecimal
值为1000.1000000000000227373675443232059478759765625。 1000.1 的精度为 1,并且从 BigDecimal
值的精度 14 误表示。
Is it only when performing arithmetic operations that a Double can have floating-point issues, or are there values to which it cannot be accurately initialised?
根据上面的例子,有些值无法准确初始化。正如 The Java™ Tutorials 明确指出的那样,"This data type [float / double] should never be used for precise values, such as currency. For that, you will need to use the java.math.BigDecimal class instead."
有趣的是,调用 BigDecimal.valueOf(someDouble)
起初似乎神奇地解决了问题,但在意识到它调用 Double.toString()
然后阅读 Double
's documentation 时,很明显这也不适合精确值.
总之,在处理精确值时,浮点数永远不合适。因此,在我看来,除非另有说明,否则 ORM / DSL 应该映射到 BigDecimal
,因为大多数数据库使用都涉及精确值的计算。
更新:
基于这个结论,我提出了 this issue with QueryDSL。
不仅是算术运算,还是纯读写。
Oracle NUMBER
和 BigDecimal
都使用 decadic base。因此,当您从数据库中读取数字然后将其存储回去时,您可以确定写入了相同的数字。 (除非它超过 Oracle 的 38 位数字限制)。
如果您将 NUMBER
转换为二进制基数 (Double
),然后将其转换回十进制,那么您可能会遇到问题。而且这个操作一定要慢很多。
Robert Bain 的问题、评论和回答中似乎提到了几个问题。我收集并解释了其中的一些内容。
Is it safe to use a double to store a precise value?
是的,前提是有效位数(精度)足够小。
If a decimal string with at most 15 significant digits is converted to IEEE 754 double precision representation and then converted back to a string with the same number of significant digits, then the final string should match the original.
But
new BigDecimal(1000.1d)
has the value 1000.1000000000000227373675443232059478759765625, why not 1000.1?在上面的引用中我强调了 - 当从双精度转换时,必须指定有效数字的数量,例如
new BigDecimal(1000.1d, new MathContext(15))
Is it safe to use a double for arbitrary arithmetic on precise values?
不,计算中使用的每个中间值都可能引入额外的错误。
使用双精度值存储精确值应该被视为一种优化。它引入了风险,如果不小心,精度可能会丢失。使用 BigDecimal
不太可能产生意想不到的后果,应该是您的默认选择。
Is it correct that QueryDSL returns a double for precise value?
这不一定不正确,但可能并不理想。我建议您与 QueryDSL 开发人员合作...但我看到您已经提出 an issue 并且他们打算改变这种行为。