浮点数相加
我们知道浮点数尽量避免不同量纲的数相加1
2> 1023 + 0.00000000000001
Double = 1023.0
浮点数除法
不同量纲之间的除法也会得到不一样的结果1
2
3
4> 1.023/2.047
Double = 0.49975574010747426
> 1023.0/2047.0
Double = 0.49975574010747437
因此对于有些应用可能引入意向不到的结果
比如1
2
3
4> 1023.0/2047.0*2.047 == 1.023
Boolean = false
> 1023.0/2047.0*2047 == 1023
Boolean = true
避免不同量纲的数做加减法,还要注意不同量纲的数做乘除法带来的意外结果
典型的例子
1 | 0.3 - 0.1 |
Double Eps 精度
1 | Float (B=2 / p=24): Eps = 2^(-24) = 5.9604644775390625E-8 |
也就是说计算机能表示的最小绝对值1
20+1E-16 = E-16
1+1E-16 = 1.0
他的精度跟指数位有关,总共E-16次方的误差范围,指数位占n位,那么误差就等与E(-16+n)1
2
3
41000+1e-16= 1000.0
1000+1e-15= 1000.0
1000+1e-14= 1000.0
1000+1e-13= 1000.0000000000001
只要指数位和分数位之和不要超过16,都可以表示1
2
3
4
5
6
71E10+1E-6 = 1.0000000000000002E10 //能表示1E-6不能表示
1E10+1E-7 = 1.0E10 //误差1E-7不能表示
1E8+1E-7 = 1.000000000000001E8 //指数位8,误差1E-7可以表示
1000000 +1E-10 = 100000.0000000001 //可以表示1E-10
10000000 +1E-10 = 10000000.0 //不能表示1E-10误差,指数位占了7
1E18 + 13 = 1e18
所以你在用浮点计算的时候,要看数的量纲,量纲越大,Eps要设置的大一些, 否则就失去了意义
所以在判断一个数是否为0是, 不能 a == 0
而是要 abs(a) <= Eps
,
同样在比较两个数是否相等时,不能 a == b
而是要 abs(a - b) <= Eps
BigDecimal
Double的问题是精度有限1
2
3val a = 0.02
val b = 0.03
val c = b - a
c的结果不是0.01,而是c: Double = 0.009999999999999998
因此有些系统设计中不能用double,不如金融银行金额计算
为什么会这样呢? 因为float和double都是浮点数, 都有取值范围, 都有精度范围. 浮点数与通常使用的小数不同, 使用中, 往往难以确定.
常见的问题是定义了一个浮点数, 经过一系列的计算, 它本来应该等于某个确定值, 但实际上并不是!
double相减会转换成二进制,因double有效位数为 16位这就会出现存储小数位数不够的情况,这种情况下就会出现误差,解决方法就是使用BigDecimal,它的有效长度足够长可存储小数位数。
因此可代替double来进行加减乘除, 金额必须是完全精确的计算, 故不能使用double或者float, 而应该采用java.math.BigDecimal.1
2
3val a = BigDecimal(0.02)
val b = BigDecimal(0.03)
val c = b - a
c的结果得到0.01
1 | val a = BigInt("1234567890123456").toLong |
c的值为1234567890123456,0.01丢失1
2
3val a = BigDecimal("1234567890123456")
val b = BigDecimal(0.001)
val c = a + b
c的值为1234567890123456.001,精度并不会丢失
BigDecimal(value), value可以用数字初始化,太大的值只能用字符串初始化如“123456789012345678”
1 | val a = BigDecimal("1234567890123456001") |
1 | val c= BigDecimal("1234.5000") |