Java浮点数计算精度问题总结

Java浮点数计算精度问题总结

首先看看下面几个简单的加法计算的输出结果:

System.out.println(0.1 + 0.2);  //输出:0.30000000000000004
System.out.println(1.1 + 1.2);  //输出:2.3
System.out.println(0.1f + 0.2f);//输出:0.3

浮点数计算可能出问题的根本原因:

IEEE754的浮点数世界里,0.1(单精度或双精度浮点数)并不是真正的0.1,0.2(单精度或双精度浮点数)也并不是真正的0.2,所以相加的值并不完全等于0.3。

本文讨论下面这些问题:

  • 为什么浮点数计算会存在精度问题?
  • 为什么相同的两个数字相加,float和double计算的结果不一致?
  • 为什么小数位不变,整数位加1,double计算的结果不一致没有出现.30000000000000004?
  • 如何避免精度问题?

浮点数标准

首先简单了解一下浮点数标准,java中浮点数采用的IEEE754标准,该标准的全称为IEEE二进制浮点数算术标准。

存储格式:符号位+指数位偏移+尾数位

image.png

IEEE 754常用的两种表示浮点数值的方式:单精确度(float 32位)、双精确度(double 64位)

image.png

规约形式的浮点数:

如果浮点数中指数部分的编码值在0 < exponent < 2e-2之间,且尾数部分最高有效位(即整数字)是1,那么这个浮点数将被称为规约形式的浮点数。“规约”是指用唯一确定的浮点形式去表示一个值。
由于这种表示下的尾数有一位隐含的二进制有效数字,为了与二进制科学计数法的尾数相区别,IEEE754称之为有效数(significant)。

非规约形式的浮点数:

如果浮点数的指数部分的编码值是0,尾数为非零,那么这个浮点数将被称为非规约形式的浮点数。

IEEE 754标准规定:

非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值大1.例如,最小的规约形式的单精度浮点数的指数部分编码值为1,指数的实际值为-126;而非规约的单精度浮点数的指数域编码值为0,对应的指数实际值也是-126而不是-127。实际上非规约形式的浮点数仍然是有效可以使用的,只是它们的绝对值已经小于所有的规约浮点数的绝对值;即所有的非规约浮点数比规约浮点数更接近0。规约浮点数的尾数大于等于1且小于2,而非规约浮点数的尾数小于1且大于0.

精度

在二进制,第一个有效数字必定是“1”,因此这个“1”并不会存储。
单精和双精浮点数的有效数字分别是有存储的23和52个位,加上最左手边没有存储的第1个位,即是24和53个位。

浮点数的比较

浮点数基本上可以按照符号位、指数域、尾数域的顺序作字典比较。显然,所有正数大于负数;正负号相同时,指数的二进制表示法更大的其浮点数值更大。

指数偏移值

指数偏移值(exponent bias),是指浮点数表示法中的指数域的编码值为指数的实际值加上某个固定的值,IEEE 754标准规定该固定值为:2的e-1次方减1,其中的e为存储指数的位元的长度。

以单精度浮点数为例,它的指数域是8个位元,固定偏移值是:2的7次方-1=127

采用指数的实际值加上固定的偏移值的办法表示浮点数的指数,好处是可以用长度为e个位元的无符号整数来表示所有的指数取值,这使得两个浮点数的指数大小的比较更为容易,实际上可以按照字典序比较两个浮点表示的大小。

浮点数转二进制数

能精确表示的浮点数

哪些小数能被精确表示呢?0.5的倍数,且在精度以内。

方便计算,首先选择可以用浮点数精确表示的数计算:4.25

step1.首先将数字转为2进制:
整数部分4:
4/2=2 余 0
2/2=1 余 0
1/2=0 余 1

小数部分0.25
0.25 * 2 = 0.5 未进位 0
0.50 * 2 = 1 进位整数 1

二进制表示:100.01

step2.将二进制数转为科学计数法表示
科学记数法表示:1.0001 * 2^2

step3.转换为IEEE754格式存储
符号位 0 (正数0 负数1)
指数 2 (float指数+127 double指数+1023)
尾数 0001

单精度float:符号位0 指数位129(10000001) 尾数001
0 10000001 00010000000000000000000

双精度double:符号位0 指数位1025(10000000001) 尾数001
0 10000000001 0001000000000000000000000000000000000000000000000000

不能精确表示的浮点数

举个例子:1/3,十进制就无法精确表示三分之一这个数字。

二进制也有很多很多小数无法精确表示,包括:0.1和0.2,这也是导致计算出现精度问题的根本原因。

下面将0.1和0.2转为2进制表示。

0.1

0.10 * 2 = 0.20 未进位 0
0.20 * 2 = 0.40 未进位 0
0.40 * 2 = 0.80 未进位 0
0.80 * 2 = 1.60 进位 1
0.60 * 2 = 1.20 进位 1
0.20 * 2 = 0.40 未进位 0
0.40 * 2 = 0.80 未进位 0
0.80 * 2 = 1.60 进位 1
0.60 * 2 = 1.20 进位 1
0.20 * 2 = 0.40 未进位 0
0.40 * 2 = 0.80 未进位 0
0.80 * 2 = 1.60 进位 1
0.60 * 2 = 1.20 进位 1
0.20 * 2 = 0.40 未进位 0
0.40 * 2 = 0.80 未进位 0
0.80 * 2 = 1.60 进位 1
0.60 * 2 = 1.20 进位 1
0.20 * 2 = 0.40 未进位 0
无限循环...
二进制表示0.1:
0.00011001100110011001100110011001100110011001100110011001...
科学记数表示:
1.1001100110011001100110011001100110011001100110011001... * 2^-4

转换为IEEE754格式存储:
符号位 0 (正数0 负数1)
指数 -4 (float指数+127 double指数+1023)
尾数 1001100110011001100110011001100110011001100110011001...

float 单精度浮点数,尾数只能存储23位,多余位数四舍五入:
0 01111011 10011001100110011001101

double 双精度浮点数,尾数只能存储52位,多余位数四舍五入:
0 01111111011 1001100110011001100110011001100110011001100110011010

0.2

0.20 * 2 = 0.40 未进位 0
0.40 * 2 = 0.80 未进位 0
0.80 * 2 = 1.60 进位 1
0.60 * 2 = 1.20 进位 1
0.20 * 2 = 0.40 未进位 0
0.40 * 2 = 0.80 未进位 0
0.80 * 2 = 1.60 进位 1
0.60 * 2 = 1.20 进位 1
0.20 * 2 = 0.40 未进位 0
0.40 * 2 = 0.80 未进位 0
0.80 * 2 = 1.60 进位 1
0.60 * 2 = 1.20 进位 1
0.20 * 2 = 0.40 未进位 0
0.40 * 2 = 0.80 未进位 0
0.80 * 2 = 1.60 进位 1
0.60 * 2 = 1.20 进位 1
0.20 * 2 = 0.40 未进位 0
无限循环...
二进制表示0.2:
0.00110011001100110011001100110011001100110011001100110011...
科学记数表示:
1.10011001100110011001100110011001100110011001100110011... * 2^-3

转换为IEEE754格式存储:
符号位 0 (正数0 负数1)
指数 -3 (float指数+127 double指数+1023)
尾数 10011001100110011001100110011001100110011001100110011...

float 单精度浮点数,尾数只能存储23位,多余位数四舍五入:
0 01111100 10011001100110011001101

double 双精度浮点数,尾数只能存储52位,多余位数四舍五入:
0 01111111100 1001100110011001100110011001100110011001100110011010

二进制浮点数相加

小数点对其,两数相加。

单精度浮点数:0.1f + 0.2f

   1.10011001100110011001101 2^-4
+ 11.00110011001100110011010 2^-4
=100.11001100110011001100111 2^-4
=  1.00110011001100110011010 2^-2
=  0.0100110011001100110011010 * 

计算结果:
符号位:0
指数位:-2+127 = 125
尾数:00110011001100110011010
ieee754: 0 01111101 00110011001100110011010
转换为十进制数:0.300000011920928955078125
转为float结果:0.3

双精度浮点数:0.1 + 0.2

   1.1001100110011001100110011001100110011001100110011010 2^-4
+ 11.0011001100110011001100110011001100110011001100110100 2^-4  
=100.1100110011001100110011001100110011001100110011001110 2^-4
=  1.0011001100110011001100110011001100110011001100110100 2^-2
=  0.010011001100110011001100110011001100110011001100110100 *

计算结果:
符号位:0
指数位:-2+1023 = 1021
尾数:0011001100110011001100110011001100110011001100110100
ieee754: 0 01111111101 0011001100110011001100110011001100110011001100110100
转换为十进制数:0.3000000000000000444089201865780
转换为double结果:0.30000000000000004

image.png

浮点数计算

要避免浮点数计算问题,可以通过BigDecimal来计算。

//正确的姿势:
System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));//输出:0.3

//错误的姿势:
System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));    //输出:0.3000000000000000166533453693773481063544750213623046875

下一篇文章来分析BigDecimal如何对浮点数进行计算。

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

推荐阅读更多精彩内容