在 Java 开发过程中,使用 bigdecimal 记录那些小数点后 N 位的数字,但是在处理比如(1 / 3 * 6)这样的场景时,得到的结果会是 1.999999999999998 。但是在 Oracle 数据库或者是 windows 自带的计算器中,使用 0.33333333333333333 * 6 ,得到的结果均是 2 。想问下 Java 在这一块有什么办法可以做到和其他两个一样的结果么?
1
yule111222 2023-05-30 17:06:14 +08:00
java 这个才是正常现象好么一点也不神奇,不如说 Oracle 数据库或者是 windows 自带的计算器中,使用 0.33333333333333333 * 6 ,得到的结果均是 2 才是异常的。盲猜一下不一定准是计算器缓存了前面的除法表达式,然后如果后面有乘法就先做乘法运算,再做除法运算
|
2
cc666 2023-05-30 17:06:18 +08:00
解:不用 bigdecimal 即可
jshell> 0.33333333333333333 * 6 $2 ==> 2.0 |
3
shanch 2023-05-30 17:07:13 +08:00
你用其他计算器先计算 1 / 3 等于的值 * 6 试试
|
4
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 |
5
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 |
6
Gct012 OP @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 了
|
7
zjsxwc 2023-05-30 17:13:44 +08:00
换 float 运算 1.0/3*6 结果都是 2
换限制了 scale 小数点位数的大数运算 "1.0 " / "3" * "6" 结果都是 "1.999..98" |
8
cc666 2023-05-30 17:14:34 +08:00
@shanch #2
试了一下也是,不知道楼主怎么算出 1.999999999999998 的 Windows 计算器计算 0.33333333333333333 * 6 的结果是 1.999999999999998 |
9
Gct012 OP @cc666 我也是 win11 ,自带计算器用 0.33333333333333333 * 6 得到的就是 2 ,用的标准的不是科学计算器........
|
10
zjsxwc 2023-05-30 17:16:48 +08:00
本质问题是 限制了 scale 的 除法 必然会丢失精度。
|
11
LongerAng 2023-05-30 17:17:35 +08:00
@mineralsalt 兄弟,不是你这样算的。你这和直接输出 1.0 / 3.0 * 6 不是一样的吗。浮点数计算最好调用方法
|
12
Gct012 OP @cc666 https://imgur.com/a/ftoo4mV 结果是这样的
|
13
cc666 2023-05-30 17:23:18 +08:00
@Gct012 #6 a.divide(b,12,RoundingMode.HALF_UP).multiply(c) 浮点数的存储方式无法精确表示 1/3 ,你这限制了 scale 又设置了 RoundingMode 进位,肯定丢失京都了
|
14
oldshensheep 2023-05-30 18:03:28 +08:00
你不用 bigdecimal 使用的是浮点数,计算不精确,溢出的位数会 round ,也就是 1.999999999999999998 ( 9 的个数是乱打的)这个数表达不了,进位了所以得到了 2.0
而是用 bigdecimal 是精确计算,1/3 是无限循环会要设置精度,所以都是精确计算。 你要做到一样就把结果转成 double 吧…… |
15
urnoob 2023-05-30 18:55:42 +08:00 via Android
@mineralsalt
您这写不对啊。。。在变 bigdxxxx 前就已经算好了结果。。。 |
16
qwerthhusn 2023-05-30 19:31:22 +08:00 1
看了这么多评论,我感觉好像我也没那么菜,找工作的信心又增加了一分。
|
17
luhe 2023-05-30 19:33:14 +08:00 via iPhone
业务要求先算除法保留小数位,即使丢失精度也在所不惜么,这个小数位给你提需求具体是多少了么
|
18
nothingistrue 2023-05-31 09:48:17 +08:00 1
@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 。但是请不要这么干,原因去找初中的数学教科书。 |
19
newaccount 2023-05-31 12:46:30 +08:00
调整计算顺序,先计算乘法。需要设置 setScale 的放到最后计算,只需要保证数学上相等就行,精度问题无解
|
20
mmdsun 2023-05-31 12:55:40 +08:00 via iPhone
整个算完后再 setScale 设置精度呢?
|
21
newaccount 2023-05-31 13:09:12 +08:00
@newaccount 说的有点含糊。具体就是套数学公式,转换成只有一个除法计算。最后计算除法的时候同时设置 divide(xxx, scale, roundingMode),这样可以获得最接近结果。另外对于可能有小数的运算(比如乘 0.3 ),提前放大成整数运算,最后再除回去。当成蝴蝶效应好了,中间的精度损失会导致最后结果越偏越多
|