Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 浮点数陷阱及解法 #9

Open
camsong opened this Issue Oct 9, 2017 · 48 comments

Comments

Projects
None yet
@camsong
Copy link
Owner

camsong commented Oct 9, 2017

JavaScript 浮点数陷阱及解法

原发于知乎专栏:https://zhuanlan.zhihu.com/ne-fe

众所周知,JavaScript 浮点数运算时经常遇到会 0.0000000010.999999999 这样奇怪的结果,如 0.1+0.2=0.300000000000000041-0.9=0.09999999999999998,很多人知道这是浮点数误差问题,但具体就说不清楚了。本文帮你理清这背后的原理以及解决方案,还会向你解释JS中的大数危机和四则运算中会遇到的坑。

浮点数的存储

首先要搞清楚 JavaScript 如何存储小数。和其它语言如 Java 和 Python 不同,JavaScript 中所有数字包括整数和小数都只有一种类型 — Number。它的实现遵循 IEEE 754 标准,使用 64 位固定长度来表示,也就是标准的 double 双精度浮点数(相关的还有float 32位单精度)。计算机组成原理中有过详细介绍,如果你不记得也没关系。

这样的存储结构优点是可以归一化处理整数和小数,节省存储空间。

64位比特又可分为三个部分:

  • 符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数
  • 指数位E:中间的 11 位存储指数(exponent),用来表示次方数
  • 尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零

64 bit allocation

实际数字就可以用以下公式来计算:

latex expression

注意以上的公式遵循科学计数法的规范,在十进制是为0<M<10,到二进行就是0<M<2。也就是说整数部分只能是1,所以可以被舍去,只保留后面的小数部分。如 4.5 转换成二进制就是 100.1,科学计数法表示是 1.001*2^2,舍去1后 M = 001。E是一个无符号整数,因为长度是11位,取值范围是 0~2047。但是科学计数法中的指数是可以为负数的,所以再减去一个中间数 1023,[0,1022]表示为负,[1024,2047] 表示为正。如4.5 的指数E = 1025,尾数M为 001。

最终的公式变成:

latex expression

所以 4.5 最终表示为(M=001、E=1025):

4.5 allocation map

(图片由此生成 http://www.binaryconvert.com/convert_double.html)

下面再以 0.1 例解释浮点误差的原因, 0.1 转成二进制表示为 0.0001100110011001100(1100循环),1.100110011001100x2^-4,所以 E=-4+1023=1019;M 舍去首位的1,得到 100110011...。最终就是:

0.1 allocation map

转化成十进制后为 0.100000000000000005551115123126,因此就出现了浮点误差。

为什么 0.1+0.2=0.30000000000000004

计算步骤为:

// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111

// 转成十进制正好是 0.30000000000000004

为什么 x=0.1 能得到 0.1

恭喜你到了看山不是山的境界。因为 mantissa 固定长度是 52 位,再加上省略的一位,最多可以表示的数是 2^53=9007199254740992,对应科学计数尾数是 9.007199254740992,这也是 JS 最多能表示的精度。它的长度是 16,所以可以使用 toPrecision(16) 来做精度运算,超过的精度会自动做凑整处理。于是就有:

0.10000000000000000555.toPrecision(16)
// 返回 0.1000000000000000,去掉末尾的零后正好为 0.1

// 但你看到的 `0.1` 实际上并不是 `0.1`。不信你可用更高的精度试试:
0.1.toPrecision(21) = 0.100000000000000005551

大数危机

可能你已经隐约感觉到了,如果整数大于 9007199254740992 会出现什么情况呢?
由于 E 最大值是 1023,所以最大可以表示的整数是 2^1024 - 1,这就是能表示的最大整数。但你并不能这样计算这个数字,因为从 2^1024 开始就变成了 Infinity

> Math.pow(2, 1023)
8.98846567431158e+307

> Math.pow(2, 1024)
Infinity

那么对于 (2^53, 2^63) 之间的数会出现什么情况呢?

  • (2^53, 2^54) 之间的数会两个选一个,只能精确表示偶数
  • (2^54, 2^55) 之间的数会四个选一个,只能精确表示4个倍数
  • ... 依次跳过更多2的倍数

下面这张图能很好的表示 JavaScript 中浮点数和实数(Real Number)之间的对应关系。我们常用的 (-2^53, 2^53) 只是最中间非常小的一部分,越往两边越稀疏越不精确。
fig1.jpg

在淘宝早期的订单系统中把订单号当作数字处理,后来随意订单号暴增,已经超过了
9007199254740992,最终的解法是把订单号改成字符串处理。

要想解决大数的问题你可以引用第三方库 bignumber.js,原理是把所有数字当作字符串,重新实现了计算逻辑,缺点是性能比原生的差很多。所以原生支持大数就很有必要了,现在 TC39 已经有一个 Stage 3 的提案 proposal bigint,大数问题有望彻底解决。在浏览器正式支持前,可以使用 Babel 7.0 来实现,它的内部是自动转换成 big-integer 来计算,要注意的是这样能保持精度但运算效率会降低。

toPrecision vs toFixed

数据处理时,这两个函数很容易混淆。它们的共同点是把数字转成字符串供展示使用。注意在计算的中间过程不要使用,只用于最终结果。

不同点就需要注意一下:

  • toPrecision 是处理精度,精度是从左至右第一个不为0的数开始数起。
  • toFixed 是小数点后指定位数取整,从小数点开始数起。

两者都能对多余数字做凑整处理,也有些人用 toFixed 来做四舍五入,但一定要知道它是有 Bug 的。

如:1.005.toFixed(2) 返回的是 1.00 而不是 1.01

原因: 1.005 实际对应的数字是 1.00499999999999989,在四舍五入时全部被舍去!

解法:使用专业的四舍五入函数 Math.round() 来处理。但 Math.round(1.005 * 100) / 100 还是不行,因为 1.005 * 100 = 100.49999999999999。还需要把乘法和除法精度误差都解决后再使用 Math.round。可以使用后面介绍的 number-precision#round 方法来解决。

解决方案

回到最关心的问题:如何解决浮点误差。首先,理论上用有限的空间来存储无限的小数是不可能保证精确的,但我们可以处理一下得到我们期望的结果。

数据展示类

当你拿到 1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示,如下:

parseFloat(1.4000000000000001.toPrecision(12)) === 1.4  // True

封装成方法就是:

function strip(num, precision = 12) {
  return +parseFloat(num.toPrecision(precision));
}

为什么选择 12 做为默认精度?这是一个经验的选择,一般选12就能解决掉大部分0001和0009问题,而且大部分情况下也够用了,如果你需要更精确可以调高。

数据运算类

对于运算类操作,如 +-*/,就不能使用 toPrecision 了。正确的做法是把小数转成整数后再运算。以加法为例:

/**
 * 精确加法
 */
function add(num1, num2) {
  const num1Digits = (num1.toString().split('.')[1] || '').length;
  const num2Digits = (num2.toString().split('.')[1] || '').length;
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
}

以上方法能适用于大部分场景。遇到科学计数法如 2.3e+1(当数字精度大于21时,数字会强制转为科学计数法形式显示)时还需要特别处理一下。

能读到这里,说明你非常有耐心,那我就放个福利吧。遇到浮点数误差问题时可以直接使用
https://github.com/dt-fe/number-precision

完美支持浮点数的加减乘除、四舍五入等运算。非常小只有1K,远小于绝大多数同类库(如Math.js、BigDecimal.js),100%测试全覆盖,代码可读性强,不妨在你的应用里用起来!

参考

当然写这篇文章是为了招聘!!!

阿里巴巴大数据前端部门诚招前端攻城狮。不要犹豫,万一通过了呢。
简历发过来 neosoyn@gmail.com

@YingshanDeng

This comment has been minimized.

Copy link

YingshanDeng commented Oct 13, 2017

@camsong 感谢作者的分享,文章很不错 👍 其中 大数字危机 一节中:

由于 M(应该是笔误 ) 最大值是 1023,所以最大可以表示的整数是 2^1024 - 1。这就是能表示的最大整数。

这句有点困惑,指数位最大值为 2047,减去 1023 后应该是 1024 吧,所以最大能表示的数为 2^1024 - 1 ?

JavaScript能表示并进行精确算术运算的整数范围为:正负2的53次方;超过范围的,无法给出精确计算结果,您文章给出的配图: JavaScript 中浮点数和实数(Real Number)之间的对应关系 也解释了这一点。

Math.pow(2, 53) 
-> 9007199254740992
Math.pow(2, 53) + 1
-> 9007199254740992
Math.pow(2, 53) + 2
-> 9007199254740994

这个段代码也验证了:(2^53, 2^54) 之间的数会两个选一个,只能精确表示偶数。而这一点应该也可以作为回答知乎问题的理由之一吧:javascript 里最大的安全的整数为什么是2的53次方减一?

@camsong

This comment has been minimized.

Copy link
Owner Author

camsong commented Oct 13, 2017

@YingshanDeng typo fixed。能解释,我也正是想说明这个问题。

@stoneyong stoneyong referenced this issue Oct 13, 2017

Closed

2017-10-16 #27

@Jenny-O

This comment has been minimized.

Copy link

Jenny-O commented Oct 16, 2017

666,感谢分享干货,已推荐到 SegmentFault 头条 (๑•̀ㅂ•́)و✧
链接如下:https://segmentfault.com/p/1210000011570610

@natee

This comment has been minimized.

Copy link

natee commented Oct 16, 2017

赞好文。
补充一个基本知识点,解释了64位中的“符号”、“指数”、“尾数”分别是什么,从而得到双精度浮点数实际值的公式。
qq20171016-153108 2x

双精度浮点数

@camsong

This comment has been minimized.

Copy link
Owner Author

camsong commented Oct 16, 2017

@natee 本来就加了,只是 Github 不支持 Latex,已换成截图

@shoung6

This comment has been minimized.

Copy link

shoung6 commented Oct 18, 2017

感觉只要strip转换一下最终的计算结果,计算就正确了。是不是不需要精确的加减乘除

@camsong

This comment has been minimized.

Copy link
Owner Author

camsong commented Oct 18, 2017

@shoung6 strip 只能用于最终结果,不要对中间结果进行处理,否则会刚开始差之毫厘,结果谬以千里。而后面的加减乘除都是精确的计算

@shoung6

This comment has been minimized.

Copy link

shoung6 commented Oct 18, 2017

嗯嗯,那什么情况是strip实现不了,必须用精确加减乘除的吗?我感觉我能想到的计算需求,只要计算完成之后strip一下就正确了,就不需要加减乘除那几个函数了~

@camsong

This comment has been minimized.

Copy link
Owner Author

camsong commented Oct 18, 2017

@shoung6 外部传入的“异常”数据需要展现的时候。如后端接口返回 3.4500000001,前端要格式化后展示的情况。浮点数异常对 Java、Python、Ruby 等语言都适用。

@shoung6

This comment has been minimized.

Copy link

shoung6 commented Oct 18, 2017

这个是用strip,我是说必须用精确加减乘除的需求

@camsong

This comment has been minimized.

Copy link
Owner Author

camsong commented Oct 18, 2017

文中 strip 会把精度降到 10^12,但 JS 本身的精度可以到 2^53。当你做多次计算时当初的小误差,结果可能就有大不同。还是看场景吧,如果你数字很小,最后 strip 一下也是可以的

@shoung6

This comment has been minimized.

Copy link

shoung6 commented Oct 18, 2017

嗯嗯,谢谢解答~

@mmmmmaster

This comment has been minimized.

Copy link

mmmmmaster commented Oct 19, 2017

想请教个问题:
文中提到,10进制的0.1,转成二进制结果是0.0001100110011001100(无限循环),
既然是无限循环,根据二进制转十进制的公式,
结果应该是:0+0+0+1/16 +1/32+0+0+1/256+1/512+......+.........,
最终结果应该小于0.1才对(因为后面肯定加不完,等于少加了一些),但结果确是0.100000000000000005551115123126,
这个数字明显大于0.1,很不解,是我理解的不对吗,求解啊

@mmmmmaster

This comment has been minimized.

Copy link

mmmmmaster commented Oct 19, 2017

还有个问题:
Math.pow(2,53)+0 >>9007199254740992
Math.pow(2,53)+1 >>9007199254740992
以上可以说明2^53-2^54之间,每两个选一个,但接下来:
Math.pow(2,53)+2 >>9007199254740994(注意这个值)
Math.pow(2,53)+3 >>9007199254740996
Math.pow(2,53)+4 >>9007199254740996
这就有问题了,+3时应该和+2保持一致啊,但是程序的结果却很意外,并不是每2个选一个,
求解,先感激下!

@YingshanDeng

This comment has been minimized.

Copy link

YingshanDeng commented Oct 20, 2017

@mmmmmaster 我来回答一下这两个问题吧 😋
① 十进制的 0.1 转换成二进制后,结果的确是 0.0001100110011001100(无限循环),这个二进制再进行 64 位浮点数存储得到:0011111110111001100110011001100110011001100110011001100110011010 ,然后再将这个64位二进制转换回十进制的时候,得到的就是:0.100000000000000005551115123126。这个转换可以通过在线网站:http://www.binaryconvert.com/convert_double.html 进行

②首先我们要知道 [-2^53, +2^53] 这个范围是称为 safe integers,超出这个范围的数字,就是 unsafe integers。对于对于 (2^53, 2^63) 之间的数会出现什么情况呢?

(2^53, 2^54) 之间的数会两个选一个,只能精确表示偶数
(2^54, 2^55) 之间的数会四个选一个,只能精确表示4个倍数
... 依次跳过更多2的倍数

所以我们看到 (2^53, 2^54) 范围的数字,都是间隔 2 的。

然后我们还要了解到不是 safe integers 的数字,计算结果不能确保其正确性。所以你提到的那几个计算中有些正确,有些不正确。

第二个问题关键在于不要混淆这两个概念即可。

最后,@camsong 这么理解对吧 😂

@mmmmmaster

This comment has been minimized.

Copy link

mmmmmaster commented Oct 20, 2017

@YingshanDeng
感谢大哥解答!
第一个问题,从在线网站上转完,确实结果是0.100000000000000005551115123126,但是如果从我们人类的角度来计算,这个结果应该是偏小的(毕竟无限循环加不完啊),不知道误差原因所在。。
第二个问题,如果从超出安全范围则计算结果不准确来考虑,程序吐出那样的结果确实是情有可原的,可理解!
最后, @camsong ,这么理解对吧 😂

@YingshanDeng

This comment has been minimized.

Copy link

YingshanDeng commented Oct 23, 2017

@mmmmmaster 根据我的理解,来解释一下你提到的 0.1 “误差偏大”问题 😀
我们知道十进制 0.1 转换成二进制的时候,小数点后是 0011 循环,然后我们再看看 0.1 用64位二进制表示成:0011111110111001100110011001100110011001100110011001100110011010 注意到尾数最后八位:10011010,我们把正常循环写出来是 100110011,对比之下很明显,有一个四舍五入进位,所以这就导致误差偏大

@mmmmmaster

This comment has been minimized.

Copy link

mmmmmaster commented Oct 23, 2017

@YingshanDeng ,还真是,之前没注意有进位,多谢!

@Rainsho

This comment has been minimized.

Copy link

Rainsho commented Oct 24, 2017

终于把我关于 toFixed 的疑惑解释清楚了,可惜公司看不到图片,回家在看一遍储存的问题。感觉 number-precision 就是我第一版修正计算精度的思路,不过我们测试还是不满意效果,最终引入了一个大小和精度介于 number-precision 和 bignumber.js 之间的库 big.js

@WangYang-Rex

This comment has been minimized.

Copy link

WangYang-Rex commented Nov 1, 2017

@guitong

This comment has been minimized.

Copy link

guitong commented Nov 21, 2017

因为 mantissa 固定长度是 52 位,再加上省略的一位,最多可以表示的数是 2^53=9007199254740992

大神,省略的一位是什么意思呢

@camsong

This comment has been minimized.

Copy link
Owner Author

camsong commented Nov 21, 2017

@guitong

下面再以 0.1 例解释浮点误差的原因, 0.1 转成二进制表示为 0.0001100110011001100(1100循环),1.100110011001100x2^-4,所以 E=-4+1023=1019;M 舍去首位的1,得到 100110011...。

非0数用十进制的科学计数法表示时首位为 1~9,用二进制表示时首位只能是 1,所以就约定把首位这个 1 省去。

@robberfree

This comment has been minimized.

Copy link

robberfree commented Jan 24, 2018

科学计数法的话,10进制的M应该是1=<M<10吧。

@camsong

This comment has been minimized.

Copy link
Owner Author

camsong commented Jan 25, 2018

对整数为言,1=<M<10 与文中的 0<M<10 对等。

@WuHuaJi0

This comment has been minimized.

Copy link

WuHuaJi0 commented Mar 20, 2018

有一个疑问,为何C语言中,同样64位双精度的0.1 + 0.2 能计算到结果呢?不知博主是否知道
举例:

double a = 0.1;
double b = 0.2;
printf("%lf",a+b); // 0.3
@WuHuaJi0

This comment has been minimized.

Copy link

WuHuaJi0 commented Mar 21, 2018

@robberfree 解惑了,非常感谢 :)

@aflext

This comment has been minimized.

Copy link

aflext commented Apr 26, 2018

10进制0.1转换为二进制存储为啥是10无限循环?

@roro4ever

This comment has been minimized.

Copy link

roro4ever commented May 2, 2018

最大安全整数
是指在浮点数存储机制中,利用“尾数的有限的2进制位”能够与实数建立一对一映射的最大整数。

JS中,最大安全整数是 2^53 - 1。证明如下:

2^53的2进制科学计数法表示:1E2^53,尾数M是52个0,指数E=53。

2^53 的保存形式:S=0 E=53 M=1.000000...0000(小数点后一共53个0)
注意:小数点后53个0,但M只有52位,因此最后一个0会被丢弃,所以
2^53 的保存形式:S=0 E=53 M=1.000000...000(小数点后一共52个0,丢失一位)
2^53-1的保存形式:S=0 E=52 M=1.111111....111(小数点后52个1)
2^53-2的保存形式:S=0 E=52 M=1.0111111....110(小数点后51个1,一个0)

依次倒推,可以一直推到0,因此,实数整数和2进制位存在11映射关系。

但当数字超过2^53时,如何表示呢?
只能通过扩大指数E来实现。
假设要表示 2^53+1这个数。

2^53+1的保存形式:S=0 E=53 M=1.000...001(小数点后52个0,,1个1)

但因为M只能保存52位,最后的这个1保存不了,会丢失。因此

2^53+1最终的保存形式:S=0 E=53 M=1.000000...000(小数点后一共53个0)

这样一来,2^53和2^53+1的保存形式完全一样了!从而从2^53开始,实数和2进制位的不再是11对应关系。

在展开这个2进制位时,JS知道有1位被丢弃,因此会在最后的结果末尾添加一个0(对于2^54就是在末尾加两个0)。由此,对于2^53+n的数而言,只有那些2进制形式的1可以进位到52位以内的数可以被表示——也就是末尾是0的数,其余末尾是1的数都无法表示。

演示如下:
假设我们要表示(2^53)+1---(2^53)+5的数:

    > Math.pow(2, 53)+0
    9007199254740992 --> E=53,M=1.000...0000(53个0,实际存储52个0)
    > Math.pow(2, 53)+1
    9007199254740992 --> E=53,M=1.000...0001(52个0+1个1,实际存储52个0)
    
    > Math.pow(2, 53)+2
    9007199254740994 --> E=53,M=1.000...0010(51个0+1个1+1个0,实际存储51个0+1个1)
    > Math.pow(2, 53)+3
    9007199254740994 --> E=53,M=1.000...0011(51个0+2个1,实际存储51个0+1个1)
    
    > Math.pow(2, 53)+4
    9007199254740996 --> E=53,M=1.000...0100(50个0+1个1+2个0,实际存储50个0+1个1+1个0)
    > Math.pow(2, 53)+5
    9007199254740996 -->E=53,M=1.000...0101(50个0+1个1+1个0,实际存储50个0+1个1+1个0)

可以看到,正是因为M是有限的,导致从2^53开始所有2进制形式中以1结尾的数与它前一个数的保存形式完全相同,从而形成这种现象。

结论:JS的最大安全整数是 2^53-1

@Rennzh

This comment has been minimized.

Copy link

Rennzh commented Jun 9, 2018

@roro4ever 感谢分享,但是我在v8(chrome和node的REPL环境下)下测试了Math.pow(2, 53) + 3的到的结果是9007199254740996,而不是9007199254740994。开始我以为是v8的处理方式不同,我又在python v2.7下测试,得到的结果还是9007199254740996,在java中也是得到9007199254740996
于是,我按照真实计算得出的结果继续算下去:

> Math.pow(2, 53) + 6
9007199254740996 --> E=53,M=1.000...0110 (50个0+2个1+1个0,实际存储50个0+2个1)
> Math.pow(2, 53) + 7
9007199254741000 --> E=53,M=1.000...0111 (50个0+2个1+1个1,实际存储49个0+1个1+2个0)
> Math.pow(2, 53) + 8
9007199254741000 --> E=53,M=1.000...1100 (49个0+2个1+2个0,实际存储49个0+2个1+1个0)
> Math.pow(2, 53) + 9
9007199254741000 --> E=53,M=1.000...1101 (49个0+2个1+1个0+1个1,实际存储49个0+2个1+1个0)
> Math.pow(2, 53) + 10
9007199254741002 --> E=53,M=1.000..01110 (49个0+3个1+1个0,实际存储49个0+3个1)
> Math.pow(2, 53) + 11
9007199254741004 --> E=53,M=1.000..01111 (49个0+3个1+1个1,实际存储48个0+1个1+3个0)

我觉得算到这里大概可以猜测原因是这样的:
当最后一位是1并且多出的第53位是1时,会进1位,而不是直接舍弃。

@hesenkang

This comment has been minimized.

Copy link

hesenkang commented Aug 13, 2018

谢谢。

@dr2009

This comment has been minimized.

Copy link

dr2009 commented Oct 20, 2018

function strip(num, precision = 12) {
  return +parseFloat(num.toPrecision(precision));
}

这个加号是不是多余的??? parseFloat本身就是返回数字

@likecreep

This comment has been minimized.

Copy link

likecreep commented Oct 23, 2018

function strip(num, precision = 12) {
  return +parseFloat(num.toPrecision(precision));
}

这个加号是不是多余的??? parseFloat本身就是返回数字

同问 我也觉得不需要+ parseFloat得到的就是Number类型啊 不需要转换了

@Chastrlove

This comment has been minimized.

Copy link

Chastrlove commented Oct 31, 2018

image

@camsong @YingshanDeng 感谢大神分享,
有个疑惑,按照公式,指数最大应该是1024,最大的数不应该是2^2014*1.111111(小数后52个1, 2进制)吗?

@Si3ver

This comment has been minimized.

Copy link

Si3ver commented Nov 1, 2018

写了段代码测试下。

Number(0.1+0.2).toString(2)   \\ "0.0100110011001100110011001100110011001100110011001101"
Number(0.3).toString(2)          \\"0.010011001100110011001100110011001100110011001100110011"
0.1 + 0.2                                    \\ 0.30000000000000004
Number(0.1+0.2).toString(2).length  \\54
@GoToBoy

This comment has been minimized.

Copy link

GoToBoy commented Nov 14, 2018

真大神....

@dengnan123

This comment has been minimized.

Copy link

dengnan123 commented Nov 15, 2018

学习了

@mingyun mingyun referenced this issue Nov 18, 2018

Open

weibo #125

@mengQ99

This comment has been minimized.

Copy link

mengQ99 commented Nov 26, 2018

1025.88*100 // 102588.00000000001
add(1025.88, 3.74) //1029.6200000000001
qwq

@532604872

This comment has been minimized.

Copy link

532604872 commented Dec 17, 2018

function strip(num, precision = 12) {
  return +parseFloat(num.toPrecision(precision));
}

这个加号是不是多余的??? parseFloat本身就是返回数字

同问 我也觉得不需要+ parseFloat得到的就是Number类型啊 不需要转换了

我猜测是避免后续操作需要转换
例 :+parseFloat((6.35 * 1.5).toPrecision(12)) // 9.525
+parseFloat((6.35 * 1.5).toPrecision(12)).toFixed(2) // 9.53

@stupidehorizon

This comment has been minimized.

Copy link

stupidehorizon commented Dec 24, 2018

为什么 Number.MAX_VALUE 最多只有 2^1023

Math.pow(2, 1023) 
> 8.98846567431158e+307
Math.pow(2, 1024) 
> Infinity
Number.MAX_VALUE
> 1.7976931348623157e+308

根据下图,我认为最大因该是 1023(最大的 E) + 53 = 1076 次方

image

@songyule

This comment has been minimized.

Copy link

songyule commented Jan 2, 2019

为什么 Number.MAX_VALUE 最多只有 2^1023

Math.pow(2, 1023) 
> 8.98846567431158e+307
Math.pow(2, 1024) 
> Infinity
Number.MAX_VALUE
> 1.7976931348623157e+308

根据下图,我认为最大因该是 1023(最大的 E) + 53 = 1076 次方

image

公式中的M要符合科学计数法,也就是在二进制下M只能是1≤|M|<2的数

@stupidehorizon

This comment has been minimized.

Copy link

stupidehorizon commented Jan 3, 2019

为什么 Number.MAX_VALUE 最多只有 2^1023

Math.pow(2, 1023) 
> 8.98846567431158e+307
Math.pow(2, 1024) 
> Infinity
Number.MAX_VALUE
> 1.7976931348623157e+308

根据下图,我认为最大因该是 1023(最大的 E) + 53 = 1076 次方
image

公式中的M要符合科学计数法,也就是在二进制下M只能是1≤|M|<2的数

thanks, 明白了

Math.pow(2, 1023) * 1.999999999999999
> 1.797693134862315e+308

@wbpmrck wbpmrck referenced this issue Jan 18, 2019

Open

好文收藏 #1

@Iwouldliketobeapig

This comment has been minimized.

Copy link

Iwouldliketobeapig commented Mar 7, 2019

学习

@sanshuiwang

This comment has been minimized.

Copy link

sanshuiwang commented Mar 15, 2019

厉害

@kricsleo

This comment has been minimized.

Copy link

kricsleo commented Mar 17, 2019

@camsong 楼主采用toPrecision而不是toFixed来解决问题,但是toFixed出现的舍入问题在toPrecision一样会出现啊,比如1.005.toFixed(2)结果是"1.00",但是1.005.toPrecision(3)的结果也同样是"1.00",能说一下为什么抛弃toFixed而采用toPrecision吗?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.