师哥用两幅图,给你说明浮点数的底层存储方式

  • 作者
  • KING

背景

前两天,师哥熬夜为群里面的同学,肝了equals和hashcode重写相关的两篇文章。今早,师哥很嚣张地到群里去吹牛了。


师哥:我连续两天晚上,熬夜到凌晨为亲爱的你们,写了详尽,易懂,实用性强的文章。
师哥:你们还不快去看,关注,点赞呀。
师哥:你们要是,再不去。我就把你们,都T出群了,换一批主动点的群友。

这嚣张气焰,我自己都看不下去了。但你懂的,一般装X,都会遭雷劈的。

装X,被雷劈
装X,被雷劈


师弟:师哥,我看你今天有点膨胀呀。你信不信,我分分钟,就让你又得熬个夜。
师哥:我还就不信。
师弟:师哥,为什么?System.out.println(1.0-0.1f);不等于0.9....而等于0.8999999985098839
师哥:额。。。你赢了,兄弟,你专业劈雷的吧?
师哥:我又要去熬夜了。记得晚点给我关注,点赞。
师哥:我要去熬夜写文章了。

分析

我们先来看下这段代码:

还原现场

public static void main(String[] args) {
  System.out.println(1.0-0.1f);
}

这段代码很简单,计算1.0-0.1。我不闭眼,都知道是0.9。But。。。这段代码给我的答案,却是0.8999999985098839。

1.0-0.1f,结果不等于0.9小朋友,你是否有很多问号?

师哥解答

Java语言中float与double被称为浮点数。浮点数在计算机系统中,采用科学计数法进行存储。科学计数法,是以一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数(计算机系统为2)的整数次幂得到。这种方法本身存储就是一个近似值,

所以浮点数,是无法提供完全精确的结果。它们主要为了科学计算与工程计算而生的。它们只提供较为精确的快速近似计算。在需要得到精确结果的业务场景,不能使用浮点数,比如像涉及金钱计算的银行业务等商业场景。

到底是如何存储的?

计算机只能识别二进制的0和1,那小数点要如何识别呢?一种简单的办法,将一个32位的float进行规划,前20位表示整数,后面10位表示小数部分。

但如果每个语言,各自定义一套自己的规则,那结果将不言而喻。Java语言针对浮点数存储,主要是依据于IEEE 754标准(该标准,可自行百度)。

提到标准,大家觉得必然晦涩难读的,事实也确实如此。该标准如何规定的呢?师哥自己在结合理论进行实践之后,将其概括总结为以下几点:

1.首先规定二进制小数转十进制小数算法如下(以float为例):

计算方法
计算方法

2.规定float,double各自的符号位,指数位,及尾数长度:

float,double位规则float,double位规则
float,double位规则

3.规定float,douoble在计算中各自的偏移量分别为127,和1023

解决

到此,我们已经知道为什么float与double计算不准确的原因了。作为一个优秀的程序员,在知道问题之后,必须要给出解决方案。师哥在此抛砖引玉,给出两个方案。

使用BigDecimal

BigDecimal类位于java.math包。其主要作用是,对大数进行精确计算。在使用BigDecimal进行计算时,下面这三个地方,需要注意。

1、不要直接使用浮点数进行对象构造。

float fnum = 0.6f;
BigDecimal bigde = new BigDecimal(Float.toString(fnum));

2、BigDecimal是不可变的,每一次运算过后,需要重新对象进行存储。

public static void main(String[] args) {
  BigDecimal big1 = new BigDecimal("1.1");
  BigDecimal big2 = new BigDecimal("1.1");
  big1.add(big2);
  BigDecimal big3 = new BigDecimal("1.1");
  BigDecimal big4 = big1.add(big3);
  System.out.println("错误的值:"+big4);
  //正确做法
  BigDecimal temp = big1.add(big2);
  big4 = temp.add(big3);
  System.out.println("正确的值:"+big4);
}
-------------输出-------
错误的值:2.2
正确的值:3.3

运用单位,拒绝浮点数

比如钱的运算,单位是元,则会有小数。我们可以将单位,设置为分即可。师哥工作如果遇到百分比,就会想办法将其转换为万分比,以此消除小数。

师哥用两幅图,给你说明浮点数的底层存储方式

背景

前两天,师哥熬夜为群里面的同学,肝了equals和hashcode重写相关的两篇文章。今早,师哥很嚣张地到群里去吹牛了。


师哥:我连续两天晚上,熬夜到凌晨为亲爱的你们,写了详尽,易懂,实用性强的文章。
师哥:你们还不快去看,关注,点赞呀。
师哥:你们要是,再不去。我就把你们,都T出群了,换一批主动点的群友。

这嚣张气焰,我自己都看不下去了。但你懂的,一般装X,都会遭雷劈的。

装X,被雷劈
装X,被雷劈


师弟:师哥,我看你今天有点膨胀呀。你信不信,我分分钟,就让你又得熬个夜。
师哥:我还就不信。
师弟:师哥,为什么?System.out.println(1.0-0.1f);不等于0.9....而等于0.8999999985098839
师哥:额。。。你赢了,兄弟,你专业劈雷的吧?
师哥:我又要去熬夜了。记得晚点给我关注,点赞。
师哥:我要去熬夜写文章了。

分析

我们先来看下这段代码:

还原现场

public static void main(String[] args) {
  System.out.println(1.0-0.1f);
}

这段代码很简单,计算1.0-0.1。我不闭眼,都知道是0.9。But。。。这段代码给我的答案,却是0.8999999985098839。

1.0-0.1f,结果不等于0.9小朋友,你是否有很多问号?

师哥解答

Java语言中float与double被称为浮点数。浮点数在计算机系统中,采用科学计数法进行存储。科学计数法,是以一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数(计算机系统为2)的整数次幂得到。这种方法本身存储就是一个近似值,

所以浮点数,是无法提供完全精确的结果。它们主要为了科学计算与工程计算而生的。它们只提供较为精确的快速近似计算。在需要得到精确结果的业务场景,不能使用浮点数,比如像涉及金钱计算的银行业务等商业场景。

到底是如何存储的?

计算机只能识别二进制的0和1,那小数点要如何识别呢?一种简单的办法,将一个32位的float进行规划,前20位表示整数,后面10位表示小数部分。

但如果每个语言,各自定义一套自己的规则,那结果将不言而喻。Java语言针对浮点数存储,主要是依据于IEEE 754标准(该标准,可自行百度)。

提到标准,大家觉得必然晦涩难读的,事实也确实如此。该标准如何规定的呢?师哥自己在结合理论进行实践之后,将其概括总结为以下几点:

1.首先规定二进制小数转十进制小数算法如下(以float为例):

计算方法
计算方法

2.规定float,double各自的符号位,指数位,及尾数长度:

float,double位规则float,double位规则
float,double位规则

3.规定float,douoble在计算中各自的偏移量分别为127,和1023

解决

到此,我们已经知道为什么float与double计算不准确的原因了。作为一个优秀的程序员,在知道问题之后,必须要给出解决方案。师哥在此抛砖引玉,给出两个方案。

使用BigDecimal

BigDecimal类位于java.math包。其主要作用是,对大数进行精确计算。在使用BigDecimal进行计算时,下面这三个地方,需要注意。

1、不要直接使用浮点数进行对象构造。

float fnum = 0.6f;
BigDecimal bigde = new BigDecimal(Float.toString(fnum));

2、BigDecimal是不可变的,每一次运算过后,需要重新对象进行存储。

public static void main(String[] args) {
  BigDecimal big1 = new BigDecimal("1.1");
  BigDecimal big2 = new BigDecimal("1.1");
  big1.add(big2);
  BigDecimal big3 = new BigDecimal("1.1");
  BigDecimal big4 = big1.add(big3);
  System.out.println("错误的值:"+big4);
  //正确做法
  BigDecimal temp = big1.add(big2);
  big4 = temp.add(big3);
  System.out.println("正确的值:"+big4);
}
-------------输出-------
错误的值:2.2
正确的值:3.3

运用单位,拒绝浮点数

比如钱的运算,单位是元,则会有小数。我们可以将单位,设置为分即可。师哥工作如果遇到百分比,就会想办法将其转换为万分比,以此消除小数。