• java浮点型精度丢失浅析


    java浮点型数值在运算中会出现精度损失的情况,在业务要求比较高比如交易等场景,一般使用BigDecimal来解决精度丢失的情况。最近一个同事在使用BigDecimal时仍然出现了精度损失,简略记录一下

    测试用例

    代码如下

    @Test
        public void fd() {
            double abc = 0.56D;
            System.out.println("abc: " + abc);
            System.out.println("new BigDecimal(abc): " + new BigDecimal(abc));
            System.out.println("BigDecimal.valueOf(abc): " + BigDecimal.valueOf(abc));
        }
    

    输出

    abc: 0.56
    new BigDecimal(abc): 0.560000000000000053290705182007513940334320068359375
    BigDecimal.valueOf(abc): 0.56
    

    可以看到在使用BigDecimal构造器转化浮点型仍然会有损失,而使用valueOf方法则不会出现精度损失。

    深入源码

    BigDecimal构造器,核心代码(BigDecimal(double val))如下

    public BigDecimal(double val, MathContext mc) {
      .....
        long valBits = Double.doubleToLongBits(val);
        int sign = ((valBits >> 63) == 0 ? 1 : -1);
        int exponent = (int) ((valBits >> 52) & 0x7ffL);
        long significand = (exponent == 0
                          ? (valBits & ((1L << 52) - 1)) << 1
                          : (valBits & ((1L << 52) - 1)) | (1L << 52));
      exponent -= 1075;
      ...
    }
    

    划重点, Double.doubleToLongBits返回根据IEEE754浮点“双精度格式”位布局,返回指定浮点值的表示

    BigDecimal.valueOf核心代码

    public static BigDecimal valueOf(double val) {
            return new BigDecimal(Double.toString(val));
        }
    public BigDecimal(char[] in, int offset, int len, MathContext mc) {
      ....
    }
    

    可以看到使用valueOf方法实际上是把double转为String,再调用string构造器的。

    那么为什么使用Double.doubleToLongBits会出现精度损失,而使用string构造器不会呢。主要原因是BigDecimal使用十进制(BigInteger)+小数点(scale)位置来表示小数,而不是直接使用二进制,如101.001 = 101001 * 0.1^3,运算时会分成两部分,BigInteger间的运算以及小数点位置的更新,这里不再展开。

    原理浅析

    Double.doubleToLongBits为什么会出现精度损失呢,主要原因是因为浮点型不能用精确的二进制来表述,就如十进制不能准确描述无穷小数一样。

    浮点型转化为二进制的算法是乘以2直到没有了小数为止,举个栗子,0.8表示成二进制

    0.8*2=1.6 取整数部分 1

    0.6*2=1.2 取整数部分 1

    0.2*2=0.4 取整数部分 0

    0.4*2=0.8 取整数部分 0

    可以看到上述的计算过程出现循环了,所以说浮点型转化为二进制有时是不可能精确的。

    结论

    如果想要把浮点型转化为BigDecimal,尽量选择使用valueOf方法,而不是使用构造器。

    本文由 歧途老农 创作,采用 CC BY 4.0 CN 协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。

  • 相关阅读:
    命令行jarsigner签字和解决找不到证书链错误
    ERROR ITMS-90034
    module.exports 和 exports
    php扩展包
    switch的使用
    debug安卓屏幕滑动会抖动
    react native编译报错
    使用iTerm2替代Mac自带Terminal终端
    编码转换
    git 操作远程 本地缓存删除
  • 原文地址:https://www.cnblogs.com/whereis/p/13131487.html
Copyright © 2020-2023  润新知