关于 Java 的一个神奇现象,有没有人可以解答一下的...

2023-05-30 16:58:16 +08:00
 Gct012

在 Java 开发过程中,使用 bigdecimal 记录那些小数点后 N 位的数字,但是在处理比如(1 / 3 * 6)这样的场景时,得到的结果会是 1.999999999999998 。但是在 Oracle 数据库或者是 windows 自带的计算器中,使用 0.33333333333333333 * 6 ,得到的结果均是 2 。想问下 Java 在这一块有什么办法可以做到和其他两个一样的结果么?

2967 次点击
所在节点    程序员
21 条回复
yule111222
2023-05-30 17:06:14 +08:00
java 这个才是正常现象好么一点也不神奇,不如说 Oracle 数据库或者是 windows 自带的计算器中,使用 0.33333333333333333 * 6 ,得到的结果均是 2 才是异常的。盲猜一下不一定准是计算器缓存了前面的除法表达式,然后如果后面有乘法就先做乘法运算,再做除法运算
cc666
2023-05-30 17:06:18 +08:00
解:不用 bigdecimal 即可
jshell> 0.33333333333333333 * 6
$2 ==> 2.0
shanch
2023-05-30 17:07:13 +08:00
你用其他计算器先计算 1 / 3 等于的值 * 6 试试
mineralsalt
2023-05-30 17:07:25 +08:00
我试了一下, java 也是 2, 不知道你是怎么计算的

BigDecimal bd = new BigDecimal(1.0 / 3.0 * 6);
System.out.println(bd.toString());

结果: 2.0
cc666
2023-05-30 17:11:03 +08:00
PS1 ,这没啥神奇的,基本的计算机组成原理
PS2 ,你问题中,1 / 3 * 6 和 0.33333333333333333 * 6 根本不是等价的
PS3 ,经验证,windows 自带的计算器,0.33333333333333333 * 6 = 1.99999999999999998 ,win11 22H2
Gct012
2023-05-30 17:12:19 +08:00
@mineralsalt 啊....不是,业务需求是先算除法再算的乘法,BigDecimal a = new BigDecimal("1");BigDecimal b = new BigDecimal("3");BigDecimal c = new BigDecimal("6"); a.divide(b,12,RoundingMode.HALF_UP).multiply(c),这样就是 1.9999999999998 了
zjsxwc
2023-05-30 17:13:44 +08:00
换 float 运算 1.0/3*6 结果都是 2

换限制了 scale 小数点位数的大数运算 "1.0 " / "3" * "6" 结果都是 "1.999..98"
cc666
2023-05-30 17:14:34 +08:00
@shanch #2
试了一下也是,不知道楼主怎么算出 1.999999999999998 的
Windows 计算器计算 0.33333333333333333 * 6 的结果是 1.999999999999998
Gct012
2023-05-30 17:15:56 +08:00
@cc666 我也是 win11 ,自带计算器用 0.33333333333333333 * 6 得到的就是 2 ,用的标准的不是科学计算器........
zjsxwc
2023-05-30 17:16:48 +08:00
本质问题是 限制了 scale 的 除法 必然会丢失精度。
LongerAng
2023-05-30 17:17:35 +08:00
@mineralsalt 兄弟,不是你这样算的。你这和直接输出 1.0 / 3.0 * 6 不是一样的吗。浮点数计算最好调用方法
Gct012
2023-05-30 17:19:35 +08:00
cc666
2023-05-30 17:23:18 +08:00
@Gct012 #6 a.divide(b,12,RoundingMode.HALF_UP).multiply(c) 浮点数的存储方式无法精确表示 1/3 ,你这限制了 scale 又设置了 RoundingMode 进位,肯定丢失京都了
oldshensheep
2023-05-30 18:03:28 +08:00
你不用 bigdecimal 使用的是浮点数,计算不精确,溢出的位数会 round ,也就是 1.999999999999999998 ( 9 的个数是乱打的)这个数表达不了,进位了所以得到了 2.0

而是用 bigdecimal 是精确计算,1/3 是无限循环会要设置精度,所以都是精确计算。
你要做到一样就把结果转成 double 吧……
urnoob
2023-05-30 18:55:42 +08:00
@mineralsalt
您这写不对啊。。。在变 bigdxxxx 前就已经算好了结果。。。
qwerthhusn
2023-05-30 19:31:22 +08:00
看了这么多评论,我感觉好像我也没那么菜,找工作的信心又增加了一分。
luhe
2023-05-30 19:33:14 +08:00
业务要求先算除法保留小数位,即使丢失精度也在所不惜么,这个小数位给你提需求具体是多少了么
nothingistrue
2023-05-31 09:48:17 +08:00
@Gct012 Bigdecimal (1) / Bigdecimal (3) * Bigdecimal (6) ,1 / 3 * 6 ,1.0 / 3 * 6 ,这在计算逻辑上就是三码事,你就不该把他们混为一谈。这是计算基础的问题,跟 Java 都没关系。

Bigdecimal (1) / Bigdecimal (3) * Bigdecimal (6),这是精确的十进制数字计算(现实世界的计算规则),在除法结果面对无限小数的时候,必定要做舍入。你的除法里面选择的规则是「精度 12 ,四舍五入」,因此 1/3 的结果是 0.333333333333 ,而最后结果就精确的为 1.999999999998 。

1 / 3 * 6 是整型计算,在除法结果面对小数的时候,要丢弃。故 1/3 的结果是 0 ,最终结果是 0

1.0 / 3 * 6 因为 1.0 的原因,被调整成了浮点计算,浮点数和浮点计算,是计算机专用的计算逻辑,与现实世界的计算规则有所不同,经常出现一些莫名其妙的错误,比如 0.33333333333333333 * 6 = 2 。


计算机默认的计算规则就是浮点计算,十进制计算是需要特殊处理的。Windows 计算器里面,科学计算器是高规格的精确结算,标准计算器是快速计算,这俩是不一样的。如果你想要结果都是 0.33333333333333333 * 6 = 2 ,那就别用 BigDecimal ,用 Double 或者 基本类型 double 。但是请不要这么干,原因去找初中的数学教科书。
newaccount
2023-05-31 12:46:30 +08:00
调整计算顺序,先计算乘法。需要设置 setScale 的放到最后计算,只需要保证数学上相等就行,精度问题无解
mmdsun
2023-05-31 12:55:40 +08:00
整个算完后再 setScale 设置精度呢?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/944261

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX