java运算中的精度问题

0.59f * 100 和 0.59 * 100

上面两个运算公式的运算结果如下:

        int a1 = (int) (0.59f * 100);
        System.out.println(a1); // 输出:58
        int a2 = (int) (0.59 * 100);
        System.out.println(a2); // 输出:59

java中小数不带标识默认是double类型

好家伙,这是为啥呢,第一个居然是58,由于不明白上面的运算问题,一到简单题我愣是没搞出来😂。
上面的运算中使用了float和double来进行小数运算,但是还记得有句话是这样说的,使用float和double会有精度丢失,网上搜了搜才知道,上面的问题就正好遇到了float运算精度丢失的问题。而恰好double没有丢失精度。
为什么这样计算会丢失精度,先一起复习下计算机中二进制和十进制的转换:

二进制基础知识:

二进制数和10进制数字的相互转换

二进制数转为10进制数:
在这里插入图片描述
10进制转为2进制数:
在这里插入图片描述
当然上面的0.625只是刚好可以表示为二进制,并没有精度损失的问题,

计算机对浮点数的存储

IEEE754是一个最广为使用的浮点数运算标准,
在这里插入图片描述
在这里插入图片描述
图片出处:https://blog.csdn.net/user2025/article/details/107746452
float和double表示的数值的精度范围:
在这里插入图片描述
参考:https://baike.baidu.com/item/IEEE%20754/3869922?fr=aladdin

而对于我们上面计算公式中的0.59转为二进制表示为:

十进制小数基数取整
0.5921.181
0.1820.360
0.3620.720
0.7221.441
0.4420.880
0.8821.761

再往下按照二进制转10进制的规则,可以一直计算得到(自己借助这个平台计算的)

0.59=0.10010111000010100011110101110000101000111101011100001

因为floatM只有23位,外加开头默认的1,一共可以存储24位,即计算的时候实际使用的是:
0.10010111 00001010 00111101 参与运算,这样的话结果就为58了,
上面的小数部分刚好为53位,所以用double类型计算没有出现错误。

避免精度丢失:

使用float和double运算时会造成结果的不准确,因此浮点数运算的时候,我们可以使用java提供的BigDecimal,使用BigDecimal对小数进行运算的时候,会先将数字扩大N倍,同时保存精度,转为整数进行相应的运算,最后再转为小数。
题目:https://leetcode.cn/problems/percentage-of-letter-in-string/
当然这道题使用double也可以

   public int percentageLetter(String s, char letter) {
        int frequent = 0;
        char[] chars = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            if (chars[i] == letter) {
                frequent++;
            }
        }
        if (frequent == 0) {
            return 0;
        }
        if (frequent == s.length()) {
            return 1;
        }
        BigDecimal bigDecimal1 = new BigDecimal(String.valueOf(frequent));
        BigDecimal bigDecimal2 = new BigDecimal(String.valueOf(s.length()));
        return (int) (bigDecimal1.divide(bigDecimal2, 4, RoundingMode.FLOOR).multiply(new BigDecimal("100")).doubleValue());
    }

两个数相除得到double类型

下面的代码:

        double b = 499999999d / 500000000;
        System.out.println("b is:" + b);
        double c = 499999998d / 499999999;
        System.out.println("c is:" + c);

在我的电脑上输出是:

b is:0.999999998
c is:0.999999998

这两个小数我的电脑计算出来的结果是相等,但是leetcode提交过不了,我应该算错了,
如果 y1/x1=y2/x2,那么转换为乘法,y1x2=y2x1,
转换为下面的公式:

	    long d = 499999999L * 499999999;
        long e = 499999998L * 500000000;
        System.out.println(d);
        System.out.println(e);

输出:

249999999000000001
249999999000000000

这样计算又是不相等的,那到底是哪个错了?再使用BigDecimal进行除法运算计算下,看下准确的结果,根据上面的公式double最多保留15.95位十进制数,这里使用BigDecimal运算的时候我保留20位的小数

        BigDecimal dec1 = new BigDecimal(String.valueOf(499999998));
        BigDecimal dec2 = new BigDecimal(String.valueOf(499999999));
        BigDecimal dec3 = new BigDecimal(String.valueOf(499999999));
        BigDecimal dex4 = new BigDecimal(String.valueOf(500000000));

        System.out.println(dec1.divide(dec2, 20, RoundingMode.FLOOR).toString());
        System.out.println(dec3.divide(dex4, 20, RoundingMode.FLOOR).toString());

输出:

0.99999999799999999599
0.99999999800000000000

可以看到,499999998d / 499999999 计算的结果对比用BigDecimal计算的结果,结果小数部被舍弃了一些,所以导致最终的结果错误,因此计算double除法的时候要小心,进行下公式转换,或使用BigDecimal。
参考:
https://cloud.tencent.com/developer/article/1470383
https://blog.csdn.net/user2025/article/details/107746452
题目:
https://leetcode.cn/contest/weekly-contest-294/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值