Java中浮点数的坑

基本数据类型

image
image

image
image
image
image

浮点数存在误差

浮点数有一个需要特别注意的点就是浮点数是有误差的,比如以下这段代码你觉得输出的什么结果:

public class Demo {
    public static void main(String[] args) {
        System.out.println(0.1+0.2 == 0.3);//输出false
    }
}

这段代码输出值是false,之所以是这个结果那是因为浮点数是存在误差的,也就yi是说0.1在计算机中存储时不是精确的0.1,而有可能是0.1000000001,或者其他数,而0.2或0.3也是如此,所以0.1+0.2和0.3在计算机中是不相等的。

因为浮点数存在这个特性,所以我们在编程中间要尽量避免用浮点数进行比较。

如果非要用浮点数进行比较的话,那可以使用下面这个方法:

public class Demo {
    public static void main(String[] args) {
        float n = (float)1e-6;//表示10的-6次方
        System.out.println(0.1+0.2 - 0.3 < n);//输出true
    }
}

以上代码的输出值是true,该方法的原理是如果两个数相差足够小,小到可以忽略不记的话,这里的界限设置是10的-6次方,那证明比较的这两个数可以认为是相等的,此方法只能在所表示的浮点数的小数点后的位数不是很多的时候使用。

接下来我们再来看一种极端的情况,代码如下:

public class Demo {
    public static void main(String[] args) {
        System.out.println(0.30000000000000001 == 0.3);//输出true
    }
}

以上的代码输出true,但其实我们肉眼可以很直观的看出,这两个数虽然很接近,但他们绝对不相等,像这种极端的数我们是无法用上面的方法进行比较的,所以还是记住这句话:尽量避免对浮点数进行比较。

image
image

BigDecimal类

我们既然知道了浮点数是存在误差的,所以在数据本身需要准确精度存储时,我们是一定不会使用float和double的,比如金钱数额的存储。这时我们通常使用BigDecimal类进行存储,它是一个可以存储准确浮点数的类。

image

  1. BigDecimal类的定义:
   BigDecimal bd = new BigDecimal("123.456");
  1. BigDecimal使用scale()表示小数位数,例如:
   BigDecimal d1 = new BigDecimal("987.65");
   BigDecimal d2 = new BigDecimal("987.6500");
   BigDecimal d3 = new BigDecimal("98765400");
   System.out.println(d1.scale()); // 2,表示两位小数
   System.out.println(d2.scale()); // 4
   System.out.println(d3.scale()); // 0
  1. BigDecimal中的stripTrailingZeros()方法,可以将BigDecimal格式化为去掉数值末尾0的相等的数:
   BigDecimal d1 = new BigDecimal("123.4500");
   BigDecimal d2 = d1.stripTrailingZeros();
   System.out.println(d1+" "+d1.scale()); // 123.4500  4
   System.out.println(d2+" "+d2.scale()); // 123.45  2,因为去掉了00
   
   BigDecimal d3 = new BigDecimal("1234500");
   BigDecimal d4 = d3.stripTrailingZeros();
   System.out.println(d3+" "+d3.scale()); // 1234500  0
   System.out.println(d4+" "+d4.scale()); // 1.2345E+6  -2

BigDecimalscale()返回负数,例如,-2,表示这个数是个整数,并且末尾有2个0。以上的d4就是如此,去掉0后数值没变,只是换了一种表示方法。

  1. BigDecimal可以设置它的scale,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断:
   import java.math.BigDecimal;
   import java.math.RoundingMode;
   
   public class Demo {
    public static void main(String[] args) {
        BigDecimal d1 = new BigDecimal("123.456789");
           BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568
           BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567
           System.out.println(d2);//123.4568
           System.out.println(d3);//123.4567
    }
   }
  1. BigDecimal的加、减、乘、除:
   import java.math.BigDecimal;
   
   public class Demo {
    public static void main(String[] args) {
        BigDecimal d1 = new BigDecimal("124.44");
           BigDecimal d2 = new BigDecimal("12.2");
           System.out.println(d1.add(d2));//d1+d2   136.64
           System.out.println(d1.subtract(d2));//d1-d2  112.24
           System.out.println(d1.multiply(d2));//d1*d2  1518.168
           System.out.println(d1.divide(d2));//d1/d2   10.2
    }
   }
  1. BigDecimal在做加、减、乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时就必须指定精度以及如何进行截断:
   import java.math.BigDecimal;
   import java.math.RoundingMode;
   
   public class Demo {
    public static void main(String[] args) {
        BigDecimal d1 = new BigDecimal("123.456");
        BigDecimal d2 = new BigDecimal("23.456789");
        BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小数并四舍五入
        BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽
    }
   }
  1. 可以对BigDecimal做除法的同时求其余数:
   import java.math.BigDecimal;
   public class Demo {
    public static void main(String[] args) {
        BigDecimal n = new BigDecimal("22.444");
           BigDecimal m = new BigDecimal("0.23");
           BigDecimal[] dr = n.divideAndRemainder(m);
           System.out.println(dr[0]); // 97.0
           System.out.println(dr[1]); // 0.134
    }
   }
  1. 调用divideAndRemainder()方法时,返回的数组包含两个BigDecimal,第一个是商,第二个是余数,商总是整数,余数不会大于余数,我们可以利用该方法判断两个BigDecimal是否是整数倍数:
   BigDecimal n = new BigDecimal("12.34");
   BigDecimal m = new BigDecimal("0.12");
   BigDecimal[] dr = n.divideAndRemainder(m);
   if (dr[1].signum() == 0) {//signum()会基于此BigDecimal返回三个值-1、1、0,分别对应为该数小于0,大于0和等于0
       // n是m的整数倍
   }
  1. 比较两个BigDecimal的值是否相等时,要注意的是,使用equals()方法不但要求两个BigDecimal的值相等,还要求它们的scale()相等:
   BigDecimal d1 = new BigDecimal("123.45");
   BigDecimal d2 = new BigDecimal("123.45000");
   System.out.println(d1.equals(d2)); // false,因为scale不同
   System.out.println(d1.equals(d2.stripTrailingZeros())); // true,因为d2去除尾部0后scale变为2,与d1相同

注意:使用compareTo()来比较两个BigDecimal的值,不要用equals()

  1. 使用compareTo()方法来比较两数大小,它根据两个值的大小分别返回-1、1和0,分别表示小于、大于和等于。
    import java.math.BigDecimal;
    public class Demo {
        public static void main(String[] args) {
            BigDecimal d1 = new BigDecimal("123.45");
            BigDecimal d2 = new BigDecimal("123.45000");
            BigDecimal d3 = new BigDecimal("123.40");
            System.out.println(d1.compareTo(d2)); // 0
            System.out.println(d1.compareTo(d3));//  1
            System.out.println(d3.compareTo(d2));// -1
        }
    }
  1. 查看BigDecimal的源码,可以发现一个BigDecimal是通过一个BigInteger和一个scale来表示的,即BigInteger表示一个完整的整数,而scale表示小数位数:
    public class BigDecimal extends Number implements Comparable<BigDecimal> {
        private final BigInteger intVal;
        private final int scale;
    }

image
image
image
image
image

本文转至微信公众号:【平兄聊Java】

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,302评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,232评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,337评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,977评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,920评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,194评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,638评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,319评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,455评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,379评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,426评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,106评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,696评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,786评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,996评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,467评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,043评论 2 341

推荐阅读更多精彩内容