定点数与浮点数

定点数与浮点数

实数在计算机中的表达

讲真,以前都没有注意过浮点数运算中存在的问题,以为使用浮点数就可以很好解决精度问题,直到遇到下面这段简单的 JavaScript 代码:

var x = 0.3;
var y = 0.2;
var z = 0.1;

console.log((x - y) == (y - z));  \\ => false
console.log((y + z) == x);        \\ => false

上面示例代码中的两个 false 输出让我这个新手有点难以接受,而一番搜索之后发现这个问题并不是只在 JavaScript 中存在,只要是采用 IEEE-754 表示浮点数的编程语言都会有这个问题,而几乎所有现代编程语言都采用这个标准。

所以找来 CSC231 An Introduction to Fixed- and Floating-Point Numbers 这篇文章深入研读一下,顺便尝试翻译留作笔记。当然,这篇文章是先从定点数开始的……

定点数

计算机处理整数和实数是完全不同的,在计算机系统的发展过程中,曾经提出过多种方法表达实数(常见实数如 π=3.14159265...\pi = 3.14159265...e=2.71828...e = 2.71828...),浮点数 (Floating Point Number) 和定点数 (Fixed Point Number) 就是其中两种。在定点数表达方式中,小数点固定的位于实数所有数字中间的某个位置。货币的表达就可以使用这种方式,比如 99.00 或者 00.99 可以用于表达具有四位精度 (Precision)、小数点后有两位数的货币值。由于小数点位置固定,所以可以直接用四位数值来表达相应的数值。

定点数表示法的缺点在于其形式过于僵硬,固定的小数点位置决定了固定位数的整数部分和小数部分,不利于同时表达特别大的数或者特别小的数。最终,绝大多数现代的计算机系统采纳了所谓的浮点数表达方式。这种表达方式利用科学计数法来表达实数,即用一个尾数 (Mantissa),一个基数 (Base),一个指数 (Exponent) 以及一个表示正负的符号来表达实数。

十进制表示

先来看看实数在我们熟悉的十进制系统下是怎么表示的吧,例如实数 123.45:

123.45=1×102+2×101+3×100+4×101+5×102123.45 = 1 \times {10^2} + 2 \times {10^1} + 3 \times {10^0} + 4 \times {10^{ - 1}} + 5 \times {10^{ - 2}}

小数点之前,10 的幂每一位减小 1,而小数点后也一样,每一位的权重是前一位的 110\frac {1}{10}

二进制数表示

与十进制的表示方式类似,在二进制中,将十进制中的底数 10 换作 2 即可表示无符号的二进制数,例如 1101.11:

1101.11=1×23+1×22+0×21+1×20+1×21+1×221101.11 = 1 \times {2^3} + 1 \times {2^2} + 0 \times {2^1} + 1 \times {2^0} + 1 \times {2^{ - 1}} + 1 \times {2^{ - 2}}

将其换算为十进制数即是:8+4+1+0.5+0.25=13.758 + 4 + 1 + 0.5 + 0.25 = 13.75

所以,假设我们知道一个二进制数中小数点的位置,就可以很容易得到其表达的真实数值。

当固定二进制数中的小数点的位置,那么该点左侧的每一位则可以通过 2k{2^k} 来权量 (k 为正),而右侧的每一位同样可用 2n{2^n} 权量 (n 为负)。那么,一个 16 位的数就可以采用下面的方式表示(注意其中小数点的位置):

b7b6b5b4b3b2b1b0.b1b2b3b4b5b6b7b8{b_7}{b_6}{b_5}{b_4}{b_3}{b_2}{b_1}{b_0}.{b_{ - 1}}{b_{ - 2}}{b_{ - 3}}{b_{ - 4}}{b_{ - 5}}{b_{ - 6}}{b_{ - 7}}{b_{ - 8}}

定义

通过上面的介绍,我们就可以定义一个无符号的二进制定点数:有 a 个整数位(小数点左边的位数),b 个小数位(小数点右边的位数),则可以使用 U(a,b) 来表示这个数。

例如,一个隐含小数点位于中间的 16 位数就可以用 U(8,8) 表示。

同时,对于一个 N 位的二进制数 U(a,b) 其真实值可通过下式计算:

x=(1/2b)n=0N12nxnx=(1/2^b)\sum_{n=0}^{N-1}2^nx_n

来看个简单的例子,一个 8 位无符号定点数 U(a,b) = U(4,4),比如说 x = 1011.1111 = 0xBF (hex) = 191d (dec),其真实值便是:

x=1011.1111=8+2+1+0.5+0.25+0.125+0.0625=11.9375x = 1011.1111 = 8 + 2 + 1 + 0.5 + 0.25 + 0.125 + 0.0625 = 11.9375

另一种得到真实值的方法就是用其无小数点十进制值 (191d) 除以 2b{2^b}。1011.1111 中,b 为 4,所以也可以这样表示:

x=191/24=191/16=11.9375x = 191/{2^4} = 191/16 = 11.9375

再如下面这些 16 位无符号数:

  • 0000000100000000 = 00000001 . 00000000 = 1d (1 decimal)
  • 0000001000000000 = 00000010 . 00000000 = 2d
  • 0000001010000000 = 00000010 . 10000000 = 2.5d

有符号定点数

对于有符号数,其最高位 (most significant bit, MSB) 的权值是 2N12^{N - 1},补码则是 2N1- {2^{N - 1}}。所以处理 N 位有符号数时,我们会单独取一个符号位,然后是 a 个整数位,b 个小数位,用 A(a,b) 来表示。在 U(a,b) 中,N = a+b,而在 A(a,b) 中,N = 1+a+b

NA(a,b) 有符号二进制数的真实值可以通过下式表示:

x=(1/2b)[2N1xN1+n=0N22nxn]x=(1/2^b)\Big[-2^{N-1}x_{N-1}+\sum_{n=0}^{N-2}2^nx_n\Big]

例如下列 16 位有符号定点数 A(7,8)

  • 00000000100000000 = 00000001 . 00000000 = 1d
  • 10000000100000000 = 10000001 . 00000000 = -128 + 1 = -127d
  • 00000001000000000 = 00000010 . 00000000 = 2d
  • 10000001000000000 = 10000010 . 00000000 = -128 + 2 = -126d
  • 00000001010000000 = 00000010 . 10000000 = 2.5d
  • 10000001010000000 = 10000010 . 10000000 = -128 + 2.5 = -125.5d

特性

先大致看一下:

  • 无符号数 U(a,b) 的取值:0x2a2b0 \leqslant x \leqslant 2^a-2^{-b}
  • 有符号数 A(a,b) 的取值:2ax2a2b-2^a \leqslant x \leqslant 2^a-2^{-b}
  • 两个不同格式的数相加时,需要将小数点位对齐再执行加法
  • 两个 A(a,b) 相加结果形如 A(a+1,b),而 U(a,b) 相加结果形如 U(a+1,b)
  • U(a,b)U(c,d) 相乘结果形如 U(a+c,b+d)
  • A(a,b)A(c,d) 相乘结果形如 A(a+c+1,b+d)

精度

精度 (precision) 有时有不同定义,根据 Randy Yates1 的定义,定点数的精度就是它的字长,例如 A(13,2) 就是 16 位精度(我比较倾向于这种定义)。

而根据 Wikibooks2 上的定义,定点数的精度为小数位的长度,也就是 U(a,b)A(a,b) 中的 b。

值域

值域 (range,暂且用这个词吧) 就是可取最大值与最小值之差。例如 A(13,2) 的取值在 -8192 到 +8191.75 之间,则其值域就是 16383.75。

同样地,U(8,8) 取值在 282^{-8}28282^8-2^{-8}间,则其值域可以这样表示:

  ---+-----------+-----------+-----------+-----------+----------   ----------+--------------------
     0         2^-8        2.2^-8      3.2^-8      4.2^-8       ...       2^8-2^-8
                 |                                                           |
           smallest number representable                                largest one

分辨率

分辨率 (resolution) 就是可表示的最小非零数的大小,例如 A(13,2) 的分辨率为 1/22=0.251/2^2 = 0.25。这个值也是两个值间的间隔,对 U(8,8) 可表示为:

  ---+-----------+-----------+-----------+-----------+----------   ----------+--------------------
     0         2^-8        2.2^-8      3.2^-8      4.2^-8       ...       2^8-2^-8
     |<--------->|           |<--------->|
      resolution              resolution

准确度

看到这里我是有点懵*的,都不知道用什么词翻译比较准确了,反正我就这么理解了,以后看到准确翻译再 refresh 吧,自己就是这么弱……

这里的准确度 (accuracy) 呢就是定点数与其表示的真实值之间的最大差值(这样看来像是一个误差的感觉)。例如,A(13,2) 的准确度是 1/8。它与精度的关系为:

Accuracy(F)=Resolution(F)/2Accuracy(F) = Resolution(F)/2

其中的 F 表示数据格式。对 U(8,8) 来说,其准确度可表示为:

  ---+-----------+-----------+-----|-----+-----------+----------   ----------+--------------------
     0         2^-8        2.2^-8  |   3.2^-8      4.2^-8       ...       2^8-2^-8
                                   |
                                   | real value we need to represent
                              <--->
                            Accuracy is the largest such difference

所以,这个 accuracy 是针对具体要表示的数来说的,是这个实数与计算机能表示的数之间的最大差值。

定点数部分就到这里吧。

浮点数

UC Berkeley 有个网页介绍 IEEE 浮点数标准的历史3,有兴趣可以去看看,我暂且不看了,有时间当小说阅读吧。

先来看看常见的一些浮点数(十进制):6.02×10236.02 \times 10^{23}0.000001-0.0000011.23456789×10191.23456789 \times 10^{-19}1.0-1.0

所谓浮点数,也就是那个小数点的位置可以浮动咯,比如说这个:

1.23456789×1019=12.3456789×1020=0.000000000000000000123456789×1001.23456789\times10^{-19}=12.3456789\times10^{-20}=0.000 000 000 000 000 000 123 456 789\times10^0

由于是 10 的指数,小数点可以方便的移动。

IEEE 标准浮点数

由于现代编程语言大多采用 IEEE 标准的浮点数,所以我们这里也主要集中于 IEEE 标准。

格式

正如前面所说,浮点数利用科学计数法来表达实数,即用一个尾数 (Mantissa),一个基数 (Base),一个指数 (Exponent) 以及一个表示正负的符号来表达实数。

对于 IEEE 浮点数,有不同的字长,32 位、64 位、80 位等等,但都可以通过以下形式将一个实数转为 IEEE 浮点数形式:

x=±1.bbbbbb...bbb×2bbb...bbx=\pm1.bbbbbb...bbb\times2^{bbb...bb}
  • b(s) 表示各个位
  • ±\pm 是符号位,通常由第一位表示,0 标志该数为正,1 标志该数为负
  • 1.bbbbbb…bbb 即为尾数
  • 2 为基数
  • 指数上的 bbb…bb 即为指数
  • 这是规范化的数,小数点左侧只有一位
  • 由于第一位总是 1,所以计算机中不需要存储这个位,它是一个隐含位

位编码

拿到任意一个二进制表示的实数时,将其转为 IEEE 格式需要

  1. 先将其规范化
  2. 然后调整其指数

例如数

y=+1000.100111y=+1000.100111

先将其规范化为:

y=+1.000100111×23y=+1.000100111\times2^3

上面的指数 3 表示了我们将小数点位置向左移动了三位,在二进制中也就是将原来的尾数 1000.100111 除以了 8,因此在指数部分再把 8 乘回来,也就是 ×23\times2^3

如果要把上面的数 yy 完全用二进制表示,就要把 2 转换成二进制的 10、3 转换成二进制的 11,最后得到

y=+1.000100111×1011y=+1.000100111\times10^{11}

所以,在计算机世界中,我们只需要存储三部分的信息就可以表示这个实数:

  1. 0 表示符号位 +
  2. 000100111 表示尾数(小数点左侧的 1 上面已经说过是隐含位,不用存储)
  3. 然后需要存储指数 11

用 32 位表示上面的数 yy,各位的分布就是这个样子的:

   31 30      23 22                    0
 +---+----------+-----------------------+
 | s | exponent |       mantissa        | 
 | 1 |    8     |          23           |
 +---+----------+-----------------------+
  • MSB 是符号位,占 1 位
  • 接下来的 8 位存储指数信息
  • 接下来的 23 位存储尾数

所以 32 位的 yy 就长这个样子:

y = 0 bbbbbbbb 0001001110000000000000

哈哈,你可能注意到了上面的指数信息还没有用二进制表示出来。那是因为 IEEE 起草的标准不使用 2 的补码来表示指数部分的正负信息,而是采用一个 bias 来表示,参见下面表格(弄表格其实我内心是拒绝的,markdown 表格好麻烦呀)。

real exponentstored exponentcomments
-1260special case #1
-1261 
-1252 
-1243 
-1234 
.. 
.. 
.. 
-1126 
0127 
1128 
2129 
3130 
.. 
.. 
.. 
127254 
128255special case #2

所以,数 yy 的实数指数是 3,然后加上 127 得到 130 (参考上表),转为二进制则为 1000 0010,这就是实际存储的指数部分的值。也就是说 yy 最终的 32 位 IEEE 表示为:

y = 0 10000010 0001001110000000000000

上面说的这个 bias 我也不知道怎么翻译好,但它的用处就是确定指数部分的正负。对于上面的 32 位浮点数来说,这个 bias 就是 127(111 1111),实数指数加上这个 bias 转换为无符号二进制数来表示指数部分。当所存储的指数大于 127 时,这个指数就为正,而小于就为负,等于就为 0。而为什么 IEEE 要这样安排呢?我在 StackOverflow4 上找到一个解答,简要来说就是让这个指数部分更容易区分大小,有兴趣可以去仔细看看(下文也会展开讨论)。

特例

正如上面表格中的 comments,这里有几个特例需要注意。

零值

由于 0 这个数的二进制表示中不存在 1,我们将不能将其规范化,按上面的介绍也不能将其转成 IEEE 格式。所以我们的第一个特例就是这个 0,其 32 位 IEEE 格式表示为:

0.0 = 0 00000000 0000000000000000000000

极小值

按上面表格,当存储的指数部分为 0 时,对应的实数指数为 -126,也就是尾数需要乘以 21262^{-126},这是一个非常小的值。这种情况下,IEEE 约定尾数部分就不包含隐含的那位 1。

也就是说,当指数部分存储的是 0,而尾数部分又不为 0 的时候(例如 0001000…0),实际的尾数就变成 0.0001000…0,小数点左侧不再为 1,这样就可以用来存储更小的实数了。

举个例子就更清晰了,以 IEEE 浮点数 0 00000000 00100000000000000000000 为例:

  • 指数部分为 0,也就是对应的实数指数为 -126
  • 尾数部分不再包含隐含的 1,而变为 0.001000…00,对应的十进制数为 0.125
  • 这个二进制浮点数对应的实数则为 0.125×2126=1.4693679e390.125\times2^{-126}=1.4693679e^{-39}

极大值

极大值又有两种情况:无穷大和 NaN。

先来看无穷的情况,当存储的指数信息为 255 时,对应的实数指数就是 127,尾数就需要乘以这个最大的 21272^{127}。这种情况下,当尾数为 0 时,IEEE 就规定这个数表示的是无穷大(infinity)。所以当采用浮点数计算并得到的值超过所用位数所能存储的值时,IEEE 就提供了一个特殊值 ‘infinity’ 或 ‘∞’ 来表示。因为符号位可能为 0 可能为 1,所以也就有 ‘+∞’ 和 ‘-∞’:

+∞ = 0 11111111 00000000000000000000000

-∞ = 1 11111111 00000000000000000000000

另外一种情况是 NaN (Not a Number)5。当指数部分存储的是 255,而尾数部分不全为 0 时, IEEE 规范给出的值就是 NaN。之前在很多程序语言的教程中都看到过 NaN,现在终于看到它的庐山真面目了。

下面这段 java 代码来自 StackOverflow.com6,看看 java 中有些什么情况会得到 NaN:

import java.util.*;
import static java.lang.Double.NaN;
import static java.lang.Double.POSITIVE_INFINITY;
import static java.lang.Double.NEGATIVE_INFINITY;

public class NaN {
    public static void main(String args[]) {
        double[] allNaNs = {
            0D/0D,
            POSITIVE_INFINITY / POSITIVE_INFINITY,
            POSITIVE_INFINITY / NEGATIVE_INFINITY,
            NEGATIVE_INFINITY / POSITIVE_INFINITY,
            NEGATIVE_INFINITY / NEGATIVE_INFINITY,
            0 * POSITIVE_INFINITY,
            0 * NEGATIVE_INFINITY,
            Math.pow(1, POSITIVE_INFINITY),
            POSITIVE_INFINITY + NEGATIVE_INFINITY,
            NEGATIVE_INFINITY + POSITIVE_INFINITY,
            POSITIVE_INFINITY - POSITIVE_INFINITY,
            NEGATIVE_INFINITY - NEGATIVE_INFINITY,
            Math.sqrt(-1),
            Math.log(-1),
            Math.asin(-2),
            Math.acos(+2),
        };
        System.out.println(Arrays.toString(allNaNs));
        // prints "[NaN, NaN...]"
        System.out.println(NaN == NaN); // prints "false"
        System.out.println(Double.isNaN(NaN)); // prints "true"
    }
}

浮点数值域

值域,也就是从 -infinity 到 +infinity 之间的“空间大小”,下表列出了非规范化和规范化单精度(32 位)及双精度(64 位)的值域:

 denormalizednormalizedapproximate decimal
single precision±2149\pm2^{-149} to (1223)×2126(1-2^{-23})\times2^{-126}±2126\pm2^{-126} to (2223)×2127(2-2^{-23})\times2^{127}±1044.85\pm\sim10^{-44.85} to 1038.53\sim10^{38.53}
double precision±21074\pm2^{-1074} to (1252)×21022(1-2^{-52})\times2^{-1022}±21022\pm2^{-1022} to (2252)×21023(2-2^{-52})\times2^{1023}±10323.3\pm\sim10^{-323.3} to 10308.3\sim10^{308.3}

上面这个表格数字比较难记住,如果想简单确认一下可用的值,可记下这个简化的表格:

 denormalizednormalized
single precision±(2223)×2127\pm(2-2^{-23})\times2^{127}±1038.53\sim\pm10^{38.53}
double precision±(2252)×21023\pm(2-2^{-52})\times2^{1023}±10308.25\sim\pm10^{308.25}

间隔

与定点数不同,浮点数有个指数项,这个指数越大,那么这个浮点数与相邻两数间的差距越大。

下面以一个 8 位浮点数为例,1 位是其符号位,3 位存储指数值(因此能存储的最大指数为 7,bias 为 3),另外 4 位存储尾数。

下面这个表格列出了所有采用这种格式能表示的实数值(哈哈,这次这个表格不用手动输入了,用这个 fakeFloatingPoint.py gist 直接输出 markdown 表格,😁,当然要稍作修改)。

可以看到,随着数值变大,相继两个数的差值也变大,例如 -15.5 和 -15.0,相差 0.5,这两个数之间的其他任何实数,想用这样的 8 位浮点数表示都是无能为力的。而随着数值变小,例如 0 附近的 0.0078125 和 0.015625,差值变得很小,能表示的实数也越多。

real valuebyte integerstored [sign exp mantissa]floating point
-inf2401 111 0000-inf
-15.52391 110 1111- 1.9375 * 2^ 3
-15.02381 110 1110- 1.875 * 2^ 3
-14.52371 110 1101- 1.8125 * 2^ 3
-14.02361 110 1100- 1.75 * 2^ 3
-13.52351 110 1011- 1.6875 * 2^ 3
-13.02341 110 1010- 1.625 * 2^ 3
-12.52331 110 1001- 1.5625 * 2^ 3
-12.02321 110 1000- 1.5 * 2^ 3
-11.52311 110 0111- 1.4375 * 2^ 3
-11.02301 110 0110- 1.375 * 2^ 3
-10.52291 110 0101- 1.3125 * 2^ 3
-10.02281 110 0100- 1.25 * 2^ 3
-9.52271 110 0011- 1.1875 * 2^ 3
-9.02261 110 0010- 1.125 * 2^ 3
-8.52251 110 0001- 1.0625 * 2^ 3
-8.02241 110 0000- 1.0 * 2^ 3
-7.752231 101 1111- 1.9375 * 2^ 2
-7.52221 101 1110- 1.875 * 2^ 2
-7.252211 101 1101- 1.8125 * 2^ 2
-7.02201 101 1100- 1.75 * 2^ 2
-6.752191 101 1011- 1.6875 * 2^ 2
-6.52181 101 1010- 1.625 * 2^ 2
-6.252171 101 1001- 1.5625 * 2^ 2
-6.02161 101 1000- 1.5 * 2^ 2
-5.752151 101 0111- 1.4375 * 2^ 2
-5.52141 101 0110- 1.375 * 2^ 2
-5.252131 101 0101- 1.3125 * 2^ 2
-5.02121 101 0100- 1.25 * 2^ 2
-4.752111 101 0011- 1.1875 * 2^ 2
-4.52101 101 0010- 1.125 * 2^ 2
-4.252091 101 0001- 1.0625 * 2^ 2
-4.02081 101 0000- 1.0 * 2^ 2
-3.8752071 100 1111- 1.9375 * 2^ 1
-3.752061 100 1110- 1.875 * 2^ 1
-3.6252051 100 1101- 1.8125 * 2^ 1
-3.52041 100 1100- 1.75 * 2^ 1
-3.3752031 100 1011- 1.6875 * 2^ 1
-3.252021 100 1010- 1.625 * 2^ 1
-3.1252011 100 1001- 1.5625 * 2^ 1
-3.02001 100 1000- 1.5 * 2^ 1
-2.8751991 100 0111- 1.4375 * 2^ 1
-2.751981 100 0110- 1.375 * 2^ 1
-2.6251971 100 0101- 1.3125 * 2^ 1
-2.51961 100 0100- 1.25 * 2^ 1
-2.3751951 100 0011- 1.1875 * 2^ 1
-2.251941 100 0010- 1.125 * 2^ 1
-2.1251931 100 0001- 1.0625 * 2^ 1
-2.01921 100 0000- 1.0 * 2^ 1
-1.93751911 011 1111- 1.9375 * 2^ 0
-1.8751901 011 1110- 1.875 * 2^ 0
-1.81251891 011 1101- 1.8125 * 2^ 0
-1.751881 011 1100- 1.75 * 2^ 0
-1.68751871 011 1011- 1.6875 * 2^ 0
-1.6251861 011 1010- 1.625 * 2^ 0
-1.56251851 011 1001- 1.5625 * 2^ 0
-1.51841 011 1000- 1.5 * 2^ 0
-1.43751831 011 0111- 1.4375 * 2^ 0
-1.3751821 011 0110- 1.375 * 2^ 0
-1.31251811 011 0101- 1.3125 * 2^ 0
-1.251801 011 0100- 1.25 * 2^ 0
-1.18751791 011 0011- 1.1875 * 2^ 0
-1.1251781 011 0010- 1.125 * 2^ 0
-1.06251771 011 0001- 1.0625 * 2^ 0
-1.01761 011 0000- 1.0 * 2^ 0
-0.968751751 010 1111- 1.9375 * 2^ -1
-0.93751741 010 1110- 1.875 * 2^ -1
-0.906251731 010 1101- 1.8125 * 2^ -1
-0.8751721 010 1100- 1.75 * 2^ -1
-0.843751711 010 1011- 1.6875 * 2^ -1
-0.81251701 010 1010- 1.625 * 2^ -1
-0.781251691 010 1001- 1.5625 * 2^ -1
-0.751681 010 1000- 1.5 * 2^ -1
-0.718751671 010 0111- 1.4375 * 2^ -1
-0.68751661 010 0110- 1.375 * 2^ -1
-0.656251651 010 0101- 1.3125 * 2^ -1
-0.6251641 010 0100- 1.25 * 2^ -1
-0.593751631 010 0011- 1.1875 * 2^ -1
-0.56251621 010 0010- 1.125 * 2^ -1
-0.531251611 010 0001- 1.0625 * 2^ -1
-0.51601 010 0000- 1.0 * 2^ -1
-0.4843751591 001 1111- 1.9375 * 2^ -2
-0.468751581 001 1110- 1.875 * 2^ -2
-0.4531251571 001 1101- 1.8125 * 2^ -2
-0.43751561 001 1100- 1.75 * 2^ -2
-0.4218751551 001 1011- 1.6875 * 2^ -2
-0.406251541 001 1010- 1.625 * 2^ -2
-0.3906251531 001 1001- 1.5625 * 2^ -2
-0.3751521 001 1000- 1.5 * 2^ -2
-0.3593751511 001 0111- 1.4375 * 2^ -2
-0.343751501 001 0110- 1.375 * 2^ -2
-0.3281251491 001 0101- 1.3125 * 2^ -2
-0.31251481 001 0100- 1.25 * 2^ -2
-0.2968751471 001 0011- 1.1875 * 2^ -2
-0.281251461 001 0010- 1.125 * 2^ -2
-0.2656251451 001 0001- 1.0625 * 2^ -2
-0.251441 001 0000- 1.0 * 2^ -2
-0.11718751431 000 1111- 0.9375 * 2^ -3
-0.1093751421 000 1110- 0.875 * 2^ -3
-0.10156251411 000 1101- 0.8125 * 2^ -3
-0.093751401 000 1100- 0.75 * 2^ -3
-0.08593751391 000 1011- 0.6875 * 2^ -3
-0.0781251381 000 1010- 0.625 * 2^ -3
-0.07031251371 000 1001- 0.5625 * 2^ -3
-0.06251361 000 1000- 0.5 * 2^ -3
-0.05468751351 000 0111- 0.4375 * 2^ -3
-0.0468751341 000 0110- 0.375 * 2^ -3
-0.03906251331 000 0101- 0.3125 * 2^ -3
-0.031251321 000 0100- 0.25 * 2^ -3
-0.02343751311 000 0011- 0.1875 * 2^ -3
-0.0156251301 000 0010- 0.125 * 2^ -3
-0.00781251291 000 0001- 0.0625 * 2^ -3
0.000 000 00000.0
0.007812510 000 0001+ 0.0625 * 2^ -3
0.01562520 000 0010+ 0.125 * 2^ -3
0.023437530 000 0011+ 0.1875 * 2^ -3
0.0312540 000 0100+ 0.25 * 2^ -3
0.039062550 000 0101+ 0.3125 * 2^ -3
0.04687560 000 0110+ 0.375 * 2^ -3
0.054687570 000 0111+ 0.4375 * 2^ -3
0.062580 000 1000+ 0.5 * 2^ -3
0.070312590 000 1001+ 0.5625 * 2^ -3
0.078125100 000 1010+ 0.625 * 2^ -3
0.0859375110 000 1011+ 0.6875 * 2^ -3
0.09375120 000 1100+ 0.75 * 2^ -3
0.1015625130 000 1101+ 0.8125 * 2^ -3
0.109375140 000 1110+ 0.875 * 2^ -3
0.1171875150 000 1111+ 0.9375 * 2^ -3
0.25160 001 0000+ 1.0 * 2^ -2
0.265625170 001 0001+ 1.0625 * 2^ -2
0.28125180 001 0010+ 1.125 * 2^ -2
0.296875190 001 0011+ 1.1875 * 2^ -2
0.3125200 001 0100+ 1.25 * 2^ -2
0.328125210 001 0101+ 1.3125 * 2^ -2
0.34375220 001 0110+ 1.375 * 2^ -2
0.359375230 001 0111+ 1.4375 * 2^ -2
0.375240 001 1000+ 1.5 * 2^ -2
0.390625250 001 1001+ 1.5625 * 2^ -2
0.40625260 001 1010+ 1.625 * 2^ -2
0.421875270 001 1011+ 1.6875 * 2^ -2
0.4375280 001 1100+ 1.75 * 2^ -2
0.453125290 001 1101+ 1.8125 * 2^ -2
0.46875300 001 1110+ 1.875 * 2^ -2
0.484375310 001 1111+ 1.9375 * 2^ -2
0.5320 010 0000+ 1.0 * 2^ -1
0.53125330 010 0001+ 1.0625 * 2^ -1
0.5625340 010 0010+ 1.125 * 2^ -1
0.59375350 010 0011+ 1.1875 * 2^ -1
0.625360 010 0100+ 1.25 * 2^ -1
0.65625370 010 0101+ 1.3125 * 2^ -1
0.6875380 010 0110+ 1.375 * 2^ -1
0.71875390 010 0111+ 1.4375 * 2^ -1
0.75400 010 1000+ 1.5 * 2^ -1
0.78125410 010 1001+ 1.5625 * 2^ -1
0.8125420 010 1010+ 1.625 * 2^ -1
0.84375430 010 1011+ 1.6875 * 2^ -1
0.875440 010 1100+ 1.75 * 2^ -1
0.90625450 010 1101+ 1.8125 * 2^ -1
0.9375460 010 1110+ 1.875 * 2^ -1
0.96875470 010 1111+ 1.9375 * 2^ -1
1.0480 011 0000+ 1.0 * 2^ 0
1.0625490 011 0001+ 1.0625 * 2^ 0
1.125500 011 0010+ 1.125 * 2^ 0
1.1875510 011 0011+ 1.1875 * 2^ 0
1.25520 011 0100+ 1.25 * 2^ 0
1.3125530 011 0101+ 1.3125 * 2^ 0
1.375540 011 0110+ 1.375 * 2^ 0
1.4375550 011 0111+ 1.4375 * 2^ 0
1.5560 011 1000+ 1.5 * 2^ 0
1.5625570 011 1001+ 1.5625 * 2^ 0
1.625580 011 1010+ 1.625 * 2^ 0
1.6875590 011 1011+ 1.6875 * 2^ 0
1.75600 011 1100+ 1.75 * 2^ 0
1.8125610 011 1101+ 1.8125 * 2^ 0
1.875620 011 1110+ 1.875 * 2^ 0
1.9375630 011 1111+ 1.9375 * 2^ 0
2.0640 100 0000+ 1.0 * 2^ 1
2.125650 100 0001+ 1.0625 * 2^ 1
2.25660 100 0010+ 1.125 * 2^ 1
2.375670 100 0011+ 1.1875 * 2^ 1
2.5680 100 0100+ 1.25 * 2^ 1
2.625690 100 0101+ 1.3125 * 2^ 1
2.75700 100 0110+ 1.375 * 2^ 1
2.875710 100 0111+ 1.4375 * 2^ 1
3.0720 100 1000+ 1.5 * 2^ 1
3.125730 100 1001+ 1.5625 * 2^ 1
3.25740 100 1010+ 1.625 * 2^ 1
3.375750 100 1011+ 1.6875 * 2^ 1
3.5760 100 1100+ 1.75 * 2^ 1
3.625770 100 1101+ 1.8125 * 2^ 1
3.75780 100 1110+ 1.875 * 2^ 1
3.875790 100 1111+ 1.9375 * 2^ 1
4.0800 101 0000+ 1.0 * 2^ 2
4.25810 101 0001+ 1.0625 * 2^ 2
4.5820 101 0010+ 1.125 * 2^ 2
4.75830 101 0011+ 1.1875 * 2^ 2
5.0840 101 0100+ 1.25 * 2^ 2
5.25850 101 0101+ 1.3125 * 2^ 2
5.5860 101 0110+ 1.375 * 2^ 2
5.75870 101 0111+ 1.4375 * 2^ 2
6.0880 101 1000+ 1.5 * 2^ 2
6.25890 101 1001+ 1.5625 * 2^ 2
6.5900 101 1010+ 1.625 * 2^ 2
6.75910 101 1011+ 1.6875 * 2^ 2
7.0920 101 1100+ 1.75 * 2^ 2
7.25930 101 1101+ 1.8125 * 2^ 2
7.5940 101 1110+ 1.875 * 2^ 2
7.75950 101 1111+ 1.9375 * 2^ 2
8.0960 110 0000+ 1.0 * 2^ 3
8.5970 110 0001+ 1.0625 * 2^ 3
9.0980 110 0010+ 1.125 * 2^ 3
9.5990 110 0011+ 1.1875 * 2^ 3
10.01000 110 0100+ 1.25 * 2^ 3
10.51010 110 0101+ 1.3125 * 2^ 3
11.01020 110 0110+ 1.375 * 2^ 3
11.51030 110 0111+ 1.4375 * 2^ 3
12.01040 110 1000+ 1.5 * 2^ 3
12.51050 110 1001+ 1.5625 * 2^ 3
13.01060 110 1010+ 1.625 * 2^ 3
13.51070 110 1011+ 1.6875 * 2^ 3
14.01080 110 1100+ 1.75 * 2^ 3
14.51090 110 1101+ 1.8125 * 2^ 3
15.01100 110 1110+ 1.875 * 2^ 3
15.51110 110 1111+ 1.9375 * 2^ 3
inf1120 111 0000+ inf

通过下面这张图也能清楚看到这个 8 位浮点数能表示的实数。

8 位浮点数表示的实数

另外,这张图表示了更宽范围的浮点数的误差7

浮点数范围

bias

上面其实走神了,没看到这部分就先去查了查为什么要用 bias 而不用 2 的补码来表示指数部分。也好,增加了阅历,这里就来看个例子加深一下印象(直接拷贝过来的,懒):

0.00000005=0 01100110 10101101011111110010101
1=0 01111111 00000000000000000000000
65536.5=0 10001111 00000000000000001000000

这三个数是按大小排列的,观察其指数部分(蓝色),也是从小到大的。也就是说,如果你有两个正浮点数,简单比较一下其指数部分就能直观得到孰大孰小。同样的,两个负浮点数,除开符号位,其指数部分数值越大,这个数越负。甚至可以用来比较正负浮点数的绝对值大小!:-)

到这里我想已经很清楚文章开头的问题了,JavaScript 采用浮点数类型,表示实数的时候自然会存在那样的问题。

题外话:最近看了一个关于自我表达的一个视频,我觉得现在社会就需要有用的自我表达,写博客就是其中一个呀……

Ads by Google

林宏

Frank Lin

Hey, there! This is Frank Lin (@flinhong), one of the 1.4 billion 🇨🇳. This 'inDev. Journal' site holds the exploration of my quirky thoughts and random adventures through life. Hope you enjoy reading and perusing my posts.

YOU MAY ALSO LIKE

Using Liquid in Jekyll - Live with Demos

Web Notes

2016.08.20

Using Liquid in Jekyll - Live with Demos

Liquid is a simple templating language that Jekyll uses to process pages on your site. With Liquid you can output an modify variables, have logic statements inside your pages and loop over content.

Practising closures in JavaScript

JavaScript Notes

2018.12.17

Practising closures in JavaScript

JavaScript is a very function-oriented language. As we know, functions are first class objects and can be easily assigned to variables, passed as arguments, returned from another function invocation, or stored into data structures. A function can access variable outside of it. But what happens when an outer variable changes? Does a function get the most recent value or the one that existed when the function was created? Also, what happens when a function invoked in another place - does it get access to the outer variables of the new place?

初识 ES6

JavaScript Notes

2018.03.08

初识 ES6

虽然 ES8 已经发布,但是以我的基础来说还是从 ES6 (ECMAScript 2015) 开始,此番就跟着 JavaScript by Example 这本书来学习,随手记点笔记,以期长点记性。