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/

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
唔,其实里面就是一个工具类,加减乘除、保留两位小数。一共5个方法。。。emmmm.....为啥分这么高呢。因为宝宝想分想疯了。 附代码,有土豪就打赏打赏,没土豪的直接拿去使吧。 package cn.cisdom.base.utils; import java.math.BigDecimal; import java.text.DecimalFormat; public class Calculation { public static final DecimalFormat df = new DecimalFormat("######0.00"); /** * @methodName format2point * @desc 保留两位小数点 * @param value * @return java.lang.String * @author xm * @create 2018/6/7 12:03 **/ public static String format2point(Number value) { return df.format(value); } public static Double add(Number value1, Number value2) { BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue())); BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue())); return b1.add(b2).doubleValue(); } /** * 提供精确的减法运算。 * * @param value1 * 减数 * @param value2 * 被减数 * @return 两个参数的差 */ public static Double sub(Number value1, Number value2) { BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue())); BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue())); return b1.subtract(b2).doubleValue(); } /** * 提供精确的乘法运算。 * * @param value1 * 被乘数 * @param value2 * 乘数 * @return 两个参数的积 */ public static Double mul(Number value1, Number value2) { BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue())); BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue())); return b1.multiply(b2).doubleValue(); } /** * 提供精确的除法运算。 * * @param value1 * 除数 * @param value2 * 被除数 * @return 除数/被除数 */ public static Double div(Number value1, Number value2) { //MathContext mc = new MathContext(2, RoundingMode.HALF_DOWN);//精度为2,舍入模式为大于0.5进1,否则舍弃 BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue())); BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue())); return b1.divide(b2).doubleValue(); } public static void main(String[] args) { Double aDouble=Calculation.add(56.9, 1.67); System.out.println(aDouble); Double bDouble=Calculation.sub(99.2,aDouble); System.out.println(bDouble); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值