4 浮点数的舍入和格式化
应考虑显式编码,通过格式化表达式或格式化工具
4.1 明确小数位数和舍入方式
- 通过String.format使用
%.1f
格式化double/float的3.35浮点数 - 结果
3.4和3.3
精度问题和舍入方式共同导致:double/float的3.35实际存储表示
3.350000000000000088817841970012523233890533447265625 3.349999904632568359375
String.format采用四舍五入的方式进行舍入,取1位小数,double的3.350四舍五入为3.4,而float的3.349四舍五入为3.3。
我们看一下Formatter类的相关源码,可以发现使用的舍入模式是HALF_UP(代码第11行):
若想使用其他舍入方式,可设置DecimalFormat
当把这俩浮点数向下舍入取2位小数时,输出分别是3.35、3.34,还是因为浮点数无法精确存储。
所以即使通过DecimalFormat精确控制舍入方式,double/float也可能产生奇怪结果,所以
4.2 字符串格式化也要使用BigDecimal
- BigDecimal分别使用向下舍入、四舍五入取1位小数格式化数字3.35
- 结果
3.3和3.4,符合预期。
最佳实践:应该使用BigDecimal来进行浮点数的表示、计算、格式化。
5 equals做判等就一定对?
包装类的比较要通过equals,而非==
。那使用equals
对两个BigDecimal判等,一定符合预期吗?
- 使用
equals
比较1.0和1这俩BigDecimal: - 结果自然是false。BigDecimal的equals比较的是BigDecimal的value和scale:1.0的scale是1,1的scale是0,所以结果false
- 若只想比较BigDecimal的value,使用compareto
- BigDecimal的
equals
和hashCode
会同时考虑value和scale,若结合HashSet/HashMap可能出问题。把值为1.0的BigDecimal加入HashSet,然后判断其是否存在值为1的BigDecimal,得到false
5.1 解决方案
5.1.1 使用TreeSet替换HashSet
TreeSet不使用hashCode,也不使用equals比较元素,而使用compareTo方法。
5.1.2 去掉尾部的零
把BigDecimal存入HashSet或HashMap前,先使用stripTrailingZeros方法去掉尾部的零。
比较的时候也去掉尾部的0,确保value相同的BigDecimal,scale也是一致的:
6 溢出问题
所有的基本数值类型都有超出保存范围可能性。
- 对Long最大值+1
- 结果是一个负数,Long的最大值+1变为了Long的最小值
-9223372036854775808
显然发生溢出还没抛任何异常。
6.1 解决方案
6.1.1 使用Math类的xxExact进行数值运算
这些方法会在数值溢出时主动抛异常。
执行后,会得到ArithmeticException,这是一个RuntimeException:
java.lang.ArithmeticException: long overflow
6.1.2 使用大数类BigInteger
BigDecimal专于处理浮点数的专家,而BigInteger则专于大数的科学计算。
使用BigInteger对Long最大值进行+1操作。若想把计算结果转为Long变量,可使用BigInteger#longValueExact,在转换出现溢出时,同样会抛出ArithmeticException
- 结果
9223372036854775808 java.lang.ArithmeticException: BigInteger out of long range
通过BigInteger对Long的最大值加1无问题,但将结果转为Long时,则会提示溢出。
参考