Groovy BigDecimal精度问题

3

在给定下列代码行的情况下

BigDecimal step = 10.0G**-1.0G

groovy 1.7.5 返回了错误的结果

0.1000000000000000055511151231257827021181583404541015625

并且 groovy 1.8 返回正确的结果。
0.1

很遗憾,我想解决我的Grails问题。使用groovy 1.8的1.4版本不够稳定(控制器在dev模式下不会刷新),而grails 1.3.7带有groovy 1.7.x。
两个问题:
1.7.5版本是我的问题还是一个bug?
如何避免这种行为?我认为BigDecimal非常适合这种舍入问题。
第二次更新:
现在我有点困惑了。每次尝试似乎都会得到不同的结果...
BigDecimal step = 10.0G**-1.0G
println step

返回 0.1000000000000000055511151231257827021181583404541015625

println 10.0G**-1.0G

返回值

0.1

在两个Groovy版本中都存在这个问题。但如果您只是在groovyConsole中输入BigDec step = 10.0G**-1.0G,并让控制台打印最后一个响应,则不同的Groovy版本会得到不同的结果。因此,一个问题似乎是groovyConsole。
另一个问题似乎是执行的toString转换。
而且似乎涉及到一些自动装箱... 当我执行一个
def step = 10.0G**-1.0G

结果是一个双精度...

我猜这个问题可以归结为两个问题:

  • a)哪些数学运算是BigDecimal运算?

  • b)如何轻松地将BigDecimal四舍五入,以便纠正上述问题?

谢谢您的耐心。


1
如果只是在Groovy控制台中,这仍然是个问题吗? - RonK
没错,主要问题是我的计算遇到了那些舍入问题。我以为我已经通过上面的例子找到了问题所在。我会再次尝试找到它。请保持关注... :-) - rdmueller
4个回答

4
我想我明白了。这是来自Java文档的提示:
BigDecimal(double val) 将double转换为BigDecimal,它是double二进制浮点值的确切十进制表示。
因此,我在代码中使用的Math.-Functions似乎不支持BigDecimal。
结果在幕后从Double转换为BigDecimal
当double为0.1时,double的二进制的确切表示为0.1000000000000000055511151231257827021181583404541015625 println 10.0G**-1.0G打印double值(0.1)
BigDecimal step = 10.0G**-1.0G; println step打印BigDecimal表示double 0.1,即上面的丑数。
似乎Groovy版本的行为没有区别(关于BigDecimal),但是在groovyConsole中输出结果的行为有所不同。

2

我使用的是Groovy 1.7.5版本,对于我来说10.0G**-1.0G返回结果为0.1

你正在使用哪个JDK版本?


嗯。两个Groovy控制台都从环境变量中读取JAVA_HOME jdk1.6.0_21,但是BigDecimal step = 10.0G**-1.0G; println stepprintln 10.0G**-1.0G之间似乎有所不同... - rdmueller

1
我猜测10.0G**-1.0G会变成Math.pow(10, -1),math.pow返回一个double,这就会产生舍入误差。 为了避免这种情况,如果你只是要求-1次方,可以将其实现为除法(1.0G/10.0G)。
话虽如此,我对Groovy并不是很了解,所以我的猜测可能是错误的,但这确实是它的样子,并且Groovy文档也支持这一点。

在这个例子中,用更简单的代码替换数学函数是一个好主意。不幸的是,我的代码使用了更多的数学函数(例如对数)。也许有特殊的BigMath函数吗? - rdmueller
1
据我所知,这不是标准的Java语法。你需要多少精度?如果小于16位数字,你可以使用double类型。如果需要更多,你需要考虑使用外部库或编写自己的库。在Stack Overflow上有过关于编写自己的BigDecimal实现的问题,例如log - Sysyphus
这不是关于精度的问题,更多的是我想要避免那些舍入误差……很难解释。这是绘制图表轴线的代码。 - rdmueller
1
BigDecimal 支持 pow 方法,但它只接受 int 类型的参数 - 所以如果你传递一个 BigDecimal 对象作为参数,我不知道它会有什么行为。你应该尝试查看 Groovy 的规范文档 - 我没有找到相关内容。 - RonK
BigDecimal step = 10**-1; println step 看起来还是很丑... :-( - rdmueller

1
如果您想将Double转换为BigDecimal并且不获取所有额外的数字,则可以通过将Double放入字符串中,然后将其转换为BigDecimal来实现,这将防止所有额外的数字从double的精确二进制表示中传输过来。
Double someFraction = 1.23456789
BigDecimal sameFraction = "${someFraction}".toBigDecimal()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接