tianzhich / blog Goto Github PK
View Code? Open in Web Editor NEWMy Blog
Home Page: https://tianzhich.github.io/blog
My Blog
Home Page: https://tianzhich.github.io/blog
之前面试遇到过一个问题:为什么JavaScript里面0.1+0.2 !== 0.3。当时我就回答到了浮点数精度有误差,显然面试官不满意,这不谁都知道吗。
小红书上也强调过不要进行0.1+0.2 === 0.3的判断,仅仅提到如下理由:
关于浮点数值计算会产生舍入误差的问题,有一点需要明确:这是使用基于IEEE754数值的浮点计算的通病,ECMAScript并非独此一家;其他使用相同数值格式的语言也存在这个问题。
因此,也仅仅能答出这是因为ECMAScript使用了基于IEEE754标准的浮点数存储导致,但是深入计算机层面,又是为什么?
计算机中数的表示有定点数和浮点数
对于小数来说,转换为二进制是乘2取整操作
运算过程 | 取整 | 小数部分 |
---|---|---|
0.1 * 2 = 0.2 | 0 | 0.2 |
0.2 * 2 = 0.4 | 0 | 0.4 |
0.4 * 2 = 0.8 | 0 | 0.8 |
0.8 * 4 = 1.6 | 1 | 0.6 |
0.6 * 2 = 1.2 | 1 | 0.2 |
... | ... | ... |
基于IEEE74标准的双精度浮点数(尾数最多52位),我们可以得到如下结果:
规格化处理
规格化定义(r表示阶基值,这里为2,S表示尾数)
$$
\frac{1}{2} \leq \vert{S}\vert\ < 1
$$
对于负数形式的补码,规格化的定义不适用(下面负数补码表示-1,不满足规格化定义)
S>0 | 规格化形式 | S<0 | 规格化形式 |
---|---|---|---|
真值 | 0.1XX...X | 真值 | -0.1XX...X |
原码 | 0.1XX...X | 原码 | 1.1XX...X |
补码 | 0.1XX...X | 补码 | 1.0XX...X |
反码 | 0.1XX...X | 反码 | 1.0XX...X |
因此通常来说
对于原码,不论整数,负数,第一数位为1即为规格化
对于补码,符号位和第一数位不同为规格化
在计算机中我们通常使用异或电路,当符号位和第一数位不同时,表示规格化完成
符号位 S | 阶码 | 尾数 | 总位数 | |
---|---|---|---|---|
短实数(单精度) | 1 | 8 | 23 | 32 |
长实数(双精度) | 1 | 11 | 52 | 64 |
临时实数 | 1 | 15 | 64 | 80 |
上面我们求出了0.1的二进制表示,同理可以求出0.2的(52表示尾数为52位)
$$
\begin{aligned}
0.1 = 0.0001\quad&1001100110011001100110011001100110011001100110011001 (52)\
0.2 = 0.001\quad&1001100110011001100110011001100110011001100110011001 (52)
\end{aligned}
$$
计算机中浮点数阶码(P)一般使用移码表示,尾数(S)使用补码表示,小数点前一个1做隐含处理。求得0.1和0.2的阶码和尾数如下
$$
\begin{aligned}
S(0.1) = 1.10011001100...1 (1100\times12)\
S(0.2) = 1.100110011...001 (0011\times12)\
P(0.1) = 1,0000000100(-4)\
P(0.2) = 1,0000000011(-3)
\end{aligned}
$$
将阶数使用移码表示,存入计算机
$$
\begin{aligned}
0.1 \Rightarrow 0:01111111100:1001100110011001100110011001100110011001100110011010\
0.2 \Rightarrow 0:01111111101:1001100110011001100110011001100110011001100110011010\
more clear:\
0.1 = 2^{-4} \times [1].1001100110011001100110011001100110011001100110011010\
0.2 = 2^{-3} \times [1].1001100110011001100110011001100110011001100110011010
\end{aligned}
$$
首先需要対阶,小阶向大阶看齐(小阶的尾数减小,只需右移,损失精度而不会造成错误),这里阶差为1
注意这里作为小阶的0.1右移添补的是隐含的“1”,而不是默认右移添0
$$
\begin{aligned}
0.1 = 2^{-3}\times&0.1100110011001100110011001100110011001100110011001101(0)\
0.2 = 2^{-3}\times&1.1001100110011001100110011001100110011001100110011010\
sum = 2^{-3}\times1&0.0110011001100110011001100110011001100110011001100111\
\end{aligned}
$$
IEEE754标准对浮点数进行舍入时,一共定义了四种模型
Round to Nearest - roundTiesToEven (Default):向最近的数靠近,最近的数需满足最低有效位为0或者偶数
Round toward 0:向0靠近
Round toward +∞:向正无穷靠近
Round toward −∞:向负无穷靠近
第一种模型解决了50%的舍入情况,还有一种模型叫做Round to Nearest - tiesAwayFromZero,也就是靠近最低有效位为奇数的值,例子如下:
Example of rounding to integers using the IEEE 754 rules
Mode / Example Value | +11.5 | +12.5 | −11.5 | −12.5 |
---|---|---|---|---|
to nearest, ties to even(默认模型) | +12.0 | +12.0 | −12.0 | −12.0 |
toward 0 | +11.0 | +12.0 | −11.0 | −12.0 |
toward +∞ | +12.0 | +13.0 | −11.0 | −12.0 |
toward −∞ | +11.0 | +12.0 | −12.0 | −13.0 |
to nearest, ties away from zero(非浮点数舍入模型) | +12.0 | +13.0 | −12.0 | −13.0 |
这时候我们再来看sum规格化后,是如何使用上述标准进行舍入的
首先,sum做规格化,并隐含1后如下(sum此时位于a,b之间):
$$
\begin{aligned}
a = 2^{-2}\times&1.0011001100110011001100110011001100110011001100110011(0)\
sum = 2^{-2}\times&1.0011001100110011001100110011001100110011001100110011(1)\
b=2^{-2}\times&1.0011001100110011001100110011001100110011001100110100(0)\
\end{aligned}
$$
按照上述第一个舍入模型,a的最低有效位为1,b的最低有效位为0,sum将使用b,然后存入计算机中。最后,我们将存入计算机中的0.3和sum进行一个比较
$$
\begin{aligned}
more clear:\
0.3 = 2^{-2}\times&1.0011001100110011001100110011001100110011001100110011\
sum=2^{-2}\times&1.0011001100110011001100110011001100110011001100110100\
\
0.1 + 0.2 = 0:01111111101&:0011001100110011001100110011001100110011001100110[100]\
0.3 = 0:01111111101&:0011001100110011001100110011001100110011001100110[011]
\end{aligned}
$$
最终结果相差了$2^{-2}\times2^{-52} = 2^{-54}$!!!
如果再将上面两个数转换成我们熟悉的十次方
$$
\begin{aligned}
0.1 + 0.2 = &0.300000000000000044408920985006...\
0.3 = &0.299999999999999988897769753748...
\end{aligned}
$$
控制台输出一下
var ll = 0.300000000000000044408920985006; // 中间有15个0
var lll = 0.299999999999999988897769753748;
console.log(ll,lll); //answer: 0.30000000000000004 0.3
一道涉及JS基础数据结构—浮点数的问题,深入探究起来,复习了一波计算机组成原理知识
当时书上对IEEE754标准说得也很少,甚至不知道IEEE754的舍入标准。以至于之前的笔记我都认为sum的舍入使用的是学过的0舍1入法。直到写下这篇文章,揭开IEEE 754的面纱,才发现没那么简单
作为自己的第一篇文章,今后还有很多值得学习和努力的地方
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.