为什么 JavaScript 中的 0.1 + 0.2 不等于 0.3

在使用 JavaScript 处理运算时,有时会碰到数字运算结果不符合预期的情况,比如经典的 0.1 + 0.2 不等于 0.3。当然这种问题不只存在于 JavaScript,不过编程语言的一些原理大致相通,我以 JavaScript 为例解释这种问题,并说明前端如何尽可能保证数字精确。

1. 计算机数字如何存储

计算机的一切信息都是二进制,数字也不例外,所有数字都是一段二进制。

在 JavaScript 中存储数字的二进制有 64 位,即我们常说的 64 位双精度浮点型数字。每个数字对应的 64 位二进制分为三段:符号位、指数位、尾数位。

其中符号位在六十四位的第一位,0 表示正数,1 表示负数。符号位之后的 11 位是指数位,决定了数字的范围。指数位之后的 52 位是尾数位,决定了数字的精度。

在 JavaScript 中,双精度浮点型的数转化成二进制的数保存,读取时根据指数位和尾数位的值转化成双精度浮点数。

比如说存储 8.8125 这个数,它的整数部分的二进制是 1000,小数部分的二进制是 1101。这两部分连起来是 1000.1101,但是存储到内存中小数点会消失,因为计算机只能存储 0 和 1。

1000.1101 这个二进制数用科学计数法表示是 1.0001101 * 2^3,这里的 3 (二进制是 0011)即为指数。

现在我们很容易判断符号位是 0,尾数位就是科学计数法的小数部分 0001101。指数位用来存储科学计数法的指数,此处为 3。指数位有正负,11 位指数位表示的指数范围是 -1023~1024,所以指数 3 的指数位存储为 1026(3 + 1023)。

可以判断 JavaScript 数值的最大值为 53 位二进制的最大值: 2^53 -1。

PS:科学计数法中小数点前的 1 可以省略,因为这一位永远是 1。比如 0.5 二进制科学计数为 1.00 * 2^-1。

2. 为什么会产生小数精度问题

首先补充一下小数的二进制的计算方法:

十进制小数转为二进制与整数想反,需要每次乘以 2
8.8125
o.8125*2 = 1.625  => 1
0.625*2 = 1.25      =>1
0.25*2 = 0.5          =>0
0.5*2 = 1                 =>1
小数部分为 1101
二进制小数转为十进制
1*2^-1 + 1*2^-2 + 0*2^-3 + 1*2^-4

在了解数字的存储后,很容易理解小数精度问题,因为十进制有 Π 这种无限循环数字,二进制也有循环数字。比如让 0.1 变为二进制,按照二进制转换永远会有余数,所以会是一个无限循环的二进制 0.0001 1001 1001 1001...(1100循环)。0.2 也是同理 0.0011 0011 0011 0011...(0011循环)。

所以当两个浮点数相加时,结果会有一些误差。比如 0.1 + 0.2 ,实际上是 0.0001 1001 1001...(1001循环) + 0.0011 0011 0011...(0011循环),如果截取于第 52 位,就会得到一个有误差的结果,转为十进制为0.30000000000000004,与 0.3 不相等。

3. 前端如何保证小数准确

首先出于安全性及准确性考虑,重要的数字计算应该交给服务端负责,相对于前端,服务端有更成熟稳定的数字处理方法,安全性也会更高。

当然前端有时也需要一些精确的数字计算,比如一些动画处理、定时器处理以及一些条件判断等。我简单列举几种方法供大家参考:

  • toFixed 指定小数位数
    这种方法比较简单,不过有个点要注意,这个方法是四舍五入,但有时候看上去并不会,比如 2.55.toFixed(1) 显示的结果是 2.5 而不是 2.6。这是因为 2.55 二进制存储的值并不精确,调用 2.55.toPrecision(100) 可以看到这个数的实际值是 2.5499..... ,所以截取一位四舍五入是 2.5。再举一个例子 (2.449999999999999999).toFixed(1) = 2.5,因为这个数与 2.45 的差值小于 Number.EPSILON。

  • 将小数转为整数计算
    这个方法的问题是转换会增加额外的复杂度和计算量,在某些场景下,可能会导致数值溢出问题。

  • 第三方库
    精确计算推荐使用成熟的库,像 BigNumber.js、decimal.js ,进行高精度的浮点数计算。原理是把数字计算变为字符串计算。

JavaScript 的计算比较复杂,由于没有细分数字类型,底层计算以二进制进行,存储值、计算值都有可能因为精度丢失而不准确,而显示值可能会因为浏览器等宿主环境不同而有差别,所以一定要注意经常产生精度丢失的地方。

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

推荐阅读更多精彩内容