提问人:bubblefoil 提问时间:1/3/2019 最后编辑:bubblefoil 更新时间:1/3/2019 访问量:1369
在加法之前将双精度转换为 BigDecimal 以保持原始精度是否足够?
Is it sufficient to convert a double to a BigDecimal just before addition to retain original precision?
问:
我们正在解决一个与数字精度相关的错误。我们的系统收集一些数字并吐出它们的总和。 问题是系统没有保留数字精度,例如 300.7 + 400.9 = 701.599...,而预期结果为 701.6。精度应该适应输入值,因此我们不能只是将结果四舍五入到固定精度。
问题很明显,我们使用 double 作为值,加法会从十进制值的二进制表示中累积误差。
数据的路径如下:
- XML 文件,键入 xsd:decimal
- 解析为 java 基元双精度。它的 15 位小数应该足够了,我们期望值总计不超过 10 位,5 位小数。
- 存储到数据库 MySql 5.5 中,键入 double
- 通过 Hibernate 加载到 JPA 实体中,即仍然是原始的 double
- 这些值的总和
- 将总和打印到另一个 XML 文件中
现在,我认为最佳解决方案是将所有内容转换为十进制格式。不出所料,使用最便宜的解决方案存在压力。事实证明,在添加几个数字之前将双精度转换为BigDecimal在以下示例中的情况B中有效:
import java.math.BigDecimal;
public class Arithmetic {
public static void main(String[] args) {
double a = 0.3;
double b = -0.2;
// A
System.out.println(a + b);//0.09999999999999998
// B
System.out.println(BigDecimal.valueOf(a).add(BigDecimal.valueOf(b)));//0.1
// C
System.out.println(new BigDecimal(a).add(new BigDecimal(b)));//0.099999999999999977795539507496869191527366638183593750
}
}
关于这一点的更多信息:为什么我们需要将 double 转换为字符串,然后才能将其转换为 BigDecimal? BigDecimal(double) 构造函数的不可预测性
我担心这样的解决方法会是一颗定时炸弹。 首先,我不太确定这个算术是否适用于所有情况。 其次,仍然存在一些风险,将来有人可能会实施一些更改并将 B 更改为 C,因为这个陷阱远非显而易见,即使是单元测试也可能无法揭示错误。
我愿意接受第二点,但问题是:这种解决方法会提供正确的结果吗?有没有一种情况,不知何故
Double.valueOf("12345.12345").toString().equals("12345.12345")
是假的吗?根据 javadoc 的说法,鉴于 Double.toString 只打印唯一表示底层双精度值所需的数字,那么当再次解析时,它会给出相同的双精度值?对于这个用例来说,这还不够吗,我只需要用这个神奇的 Double.toString(Double d) 方法添加数字并打印总和?需要明确的是,我确实更喜欢我认为的干净解决方案,到处使用 BigDecimal,但我有点缺乏论据来推销它,我的意思是理想情况下,在加法之前转换为 BigDecimal 无法完成上述工作。
答:
It depends on the Database you are using. If you are using SQL Server you can use data type as numeric(12, 8) where 12 represent numeric value and 8 represents precision. similarly, for my SQL DECIMAL(5,2) you can use.
You won't lose any precision value if you use the above-mentioned datatype.
Java Hibernate Class :
You can define private double latitude;
Database:
评论
double
If you can't avoid parsing into primitive or store as double, you should convert to as early as possible.double
BigDecimal
double
can't exactly represent decimal fractions. The value in will never be exactly 7.3, but something very very close to it, with a difference visible from the 16th digit or so on to the right (giving 50 decimal places or so). Don't be mislead by the fact that printing might give exactly "7.3", as printing already does some kind of rounding and doesn't show the number exactly.double x = 7.3;
If you do lots of computations with numbers, the tiny differences will eventually sum up until they exceed your tolerance. So using doubles in computations where decimal fractions are needed, is indeed a ticking bomb.double
[...] we expect values no longer than 10 digits total, 5 fraction digits.
I read that assertion to mean that all numbers you deal with, are to be exact multiples of , without any further digits. You can convert doubles to such BigDecimals with 0.00001
new BigDecimal.valueOf(Math.round(doubleVal * 100000), 5)
This will give you an exact representation of a number with 5 decimal fraction digits, the 5-fraction-digits one that's closest to the input . This way you correct for the tiny differences between the and the decimal number that you originally meant.doubleVal
doubleVal
If you'd simply use , you'd go through the string representation of the double you're using, which can't guarantee that it's what you want. It depends on a rounding process inside the Double class which tries to represent the double-approximation of 7.3 (being maybe 7.30000000000000123456789123456789125) with the most plausible number of decimal digits. It happens to result in "7.3" (and, kudos to the developers, quite often matches the "expected" string) and not "7.300000000000001" or "7.3000000000000012" which both seem equally plausible to me.BigDecimal.valueOf(double val)
That's why I recommend not to rely on that rounding, but to do the rounding yourself by decimal shifting 5 places, then rounding to the nearest long, and constructing a BigDecimal scaled back by 5 decimal places. This guarantees that you get an exact value with (at most) 5 fractional decimal places.
Then do your computations with the BigDecimals (using the appropriate MathContext for rounding, if necessary).
When you finally have to store the number as a double, use . The resulting double will be close enough to the decimal that the above-mentioned conversion will surely give you the same BigDecimal that you had before (unless you have really huge numbers like 10 digits before the decimal point - the you're lost with anyway).BigDecimal.doubleValue()
double
P.S. Be sure to use only if decimal fractions are relevant to you - there were times when the British Shilling currency consisted of twelve Pence. Representing fractional Pounds as would give a disaster much worse than using doubles.BigDecimal
BigDecimal
评论
new
评论
BigDecimal
double
double
double
0.2
double
double
BigDecimal.valueOf("0.3")
BigDecimal.valueOf(0.3)