浮点数加法运算的困惑

缘起:

公司一小姑娘群发邮件求助,邮件内容是关于浮点数之间运算结果莫名其妙的多了一些小数位,小姑娘想不通所以想问问有人知道原因不?涉及的代码是下面的样子

function addTwoFloatString(stra , strb){
    return parseFloat(stra) + parseFloat(strb);
}

addTwoFloatString('2222222','269567.53');
->2491789.5300000003

大家看到这个问题后献计献策,有说不应该那样,应该这样

Number( 123.5678.toFixed(2) )
还有说应该用BigDecimal做浮点数相加运算的。

看到这个求助后,直觉告诉我这个和浮点数的表示法肯定有关系,至于为什么,详细的答案是真说不好。如果非要搪塞一下,我会说浮点数之间的运算会有精度问题,如果深究为什么,那就糗了。

验证

首先看看是否其他语言中也存在类似的问题,ok,java中也存在同样的问题,那么基本可以断定这个现象和编程语言关系不大。

接着需要确认的问题是,JavaScript中浮点数是如何定义的:

JavaScript中的浮点数采用IEEE-754格式的规定。更具体的说是一个双精度格式,这意味着每个浮点数占64位。虽然它不是二进制表示浮点数的唯一途径,但它是目前最广泛使用的格式,另外,JavaScript中的数值不区分整数值和浮点数值,所有数字都采用的是64位浮点数表示法。

似乎看到了希望,IEEE754

wiki中对IEEE754中64位浮点数有下面的描述,总长度占用64位,其中1为符号位,标明浮点数是正数还是负数,11位为指数为,52位为尾数位。

知道这些现在还不能准确的表述一个数,得有一个约定,约定尾数的形式,不然同一个数字就有多种表示,这个过程称为规格化,形象化的描述就是一个数需要表示成这样的格式: 1.M* 2E,这样的数叫做规格化数。

另外,IEEE还规定了指数的存储形式,64位浮点数指数部分以偏正值形式表示,偏正值为实际的指数大小与1023的和。

到此,就可以将文章开头的两个数用IEEE754的定义表示出来。

对2222222做规格化, 符号位为0,表示是一个正数,2222222对应的二进制是1000011110100010001110,有22位,满足规格化需小数点左移21为,指数就为21 +1023 = 1044,尾数为1.000011110100010001110

最终2222222 的64位二进制浮点数就表示为

0 10000010100 0000111101000100011100000000000000000000000000000000

269567.53的64位浮点数就表示为

0 10000010001 0000011100111111111000011110101110000101000111101100

Java中有对应的函数可以将根据IEEE 754规定,返回指定浮点值的表示形式

Long.toBinaryString(Double.doubleToRawLongBits(2222222))

接下来就是浮点数加减法运算步骤了,如下

  1. 对阶,使两个数的小数点位置对齐

  2. 尾数求和,将运算后的两个尾数求和

  3. 将求和后的尾数规格化

  4. 舍入,考虑尾数右移时的丢失的数值位

  5. 判断结果是否溢出

  • 对阶的原则是小阶向大阶看齐,阶码较小的数尾数向右移,每移一位,阶码加一,在269567.53 + 2222222中,前者向后者看齐,前者尾数右移三位。对阶后两数分别为

     010000010100 0010000011100111111111000011110101110000101000111101100
    
     010000010100 0000111101000100011100000000000000000000000000000000
    
  • 尾数相加

     0100000101000010000011100111111111000011110101110000101000111101100
    
     0100000101000000111101000100011100000000000000000000000000000000
    
     0100000101000011000000101100011011000011110101110000101000111111100
    
  • 规格化

满足规格化

  • 舍入

      0100000101000011000000101100011011000011110101110000101000111111100
    

浮点数舍入的方式有多种,但是不可避免的,都可能会发生数位的丢失,这个就是直接导致浮点数之间运算后出现精度问题的直接原因。

java和javascript中最终舍入后的表示形式为

0100000101000011000000101100011011000011110101110000101000111110

结论

由于二进制表示本身的缺陷,我们不得不面对一个充满舍入误差的规范,进而导致计算结果超乎预期。

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

推荐阅读更多精彩内容