定点数与浮点数

定点数与浮点数

讲真,以前都没有注意过浮点数运算中存在的问题,以为使用浮点数就可以很好解决精度问题,直到遇到下面这段简单的 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 exponent stored exponent comments
-126 0 special case #1
-126 1  
-125 2  
-124 3  
-123 4  
. .  
. .  
. .  
-1 126  
0 127  
1 128  
2 129  
3 130  
. .  
. .  
. .  
127 254  
128 255 special 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 位)的值域:

  denormalized normalized approximate 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}

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

  denormalized normalized
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 value byte integer stored [sign exp mantissa] floating point
-inf 240 1 111 0000 -inf
-15.5 239 1 110 1111 - 1.9375 * 2^ 3
-15.0 238 1 110 1110 - 1.875 * 2^ 3
-14.5 237 1 110 1101 - 1.8125 * 2^ 3
-14.0 236 1 110 1100 - 1.75 * 2^ 3
-13.5 235 1 110 1011 - 1.6875 * 2^ 3
-13.0 234 1 110 1010 - 1.625 * 2^ 3
-12.5 233 1 110 1001 - 1.5625 * 2^ 3
-12.0 232 1 110 1000 - 1.5 * 2^ 3
-11.5 231 1 110 0111 - 1.4375 * 2^ 3
-11.0 230 1 110 0110 - 1.375 * 2^ 3
-10.5 229 1 110 0101 - 1.3125 * 2^ 3
-10.0 228 1 110 0100 - 1.25 * 2^ 3
-9.5 227 1 110 0011 - 1.1875 * 2^ 3
-9.0 226 1 110 0010 - 1.125 * 2^ 3
-8.5 225 1 110 0001 - 1.0625 * 2^ 3
-8.0 224 1 110 0000 - 1.0 * 2^ 3
-7.75 223 1 101 1111 - 1.9375 * 2^ 2
-7.5 222 1 101 1110 - 1.875 * 2^ 2
-7.25 221 1 101 1101 - 1.8125 * 2^ 2
-7.0 220 1 101 1100 - 1.75 * 2^ 2
-6.75 219 1 101 1011 - 1.6875 * 2^ 2
-6.5 218 1 101 1010 - 1.625 * 2^ 2
-6.25 217 1 101 1001 - 1.5625 * 2^ 2
-6.0 216 1 101 1000 - 1.5 * 2^ 2
-5.75 215 1 101 0111 - 1.4375 * 2^ 2
-5.5 214 1 101 0110 - 1.375 * 2^ 2
-5.25 213 1 101 0101 - 1.3125 * 2^ 2
-5.0 212 1 101 0100 - 1.25 * 2^ 2
-4.75 211 1 101 0011 - 1.1875 * 2^ 2
-4.5 210 1 101 0010 - 1.125 * 2^ 2
-4.25 209 1 101 0001 - 1.0625 * 2^ 2
-4.0 208 1 101 0000 - 1.0 * 2^ 2
-3.875 207 1 100 1111 - 1.9375 * 2^ 1
-3.75 206 1 100 1110 - 1.875 * 2^ 1
-3.625 205 1 100 1101 - 1.8125 * 2^ 1
-3.5 204 1 100 1100 - 1.75 * 2^ 1
-3.375 203 1 100 1011 - 1.6875 * 2^ 1
-3.25 202 1 100 1010 - 1.625 * 2^ 1
-3.125 201 1 100 1001 - 1.5625 * 2^ 1
-3.0 200 1 100 1000 - 1.5 * 2^ 1
-2.875 199 1 100 0111 - 1.4375 * 2^ 1
-2.75 198 1 100 0110 - 1.375 * 2^ 1
-2.625 197 1 100 0101 - 1.3125 * 2^ 1
-2.5 196 1 100 0100 - 1.25 * 2^ 1
-2.375 195 1 100 0011 - 1.1875 * 2^ 1
-2.25 194 1 100 0010 - 1.125 * 2^ 1
-2.125 193 1 100 0001 - 1.0625 * 2^ 1
-2.0 192 1 100 0000 - 1.0 * 2^ 1
-1.9375 191 1 011 1111 - 1.9375 * 2^ 0
-1.875 190 1 011 1110 - 1.875 * 2^ 0
-1.8125 189 1 011 1101 - 1.8125 * 2^ 0
-1.75 188 1 011 1100 - 1.75 * 2^ 0
-1.6875 187 1 011 1011 - 1.6875 * 2^ 0
-1.625 186 1 011 1010 - 1.625 * 2^ 0
-1.5625 185 1 011 1001 - 1.5625 * 2^ 0
-1.5 184 1 011 1000 - 1.5 * 2^ 0
-1.4375 183 1 011 0111 - 1.4375 * 2^ 0
-1.375 182 1 011 0110 - 1.375 * 2^ 0
-1.3125 181 1 011 0101 - 1.3125 * 2^ 0
-1.25 180 1 011 0100 - 1.25 * 2^ 0
-1.1875 179 1 011 0011 - 1.1875 * 2^ 0
-1.125 178 1 011 0010 - 1.125 * 2^ 0
-1.0625 177 1 011 0001 - 1.0625 * 2^ 0
-1.0 176 1 011 0000 - 1.0 * 2^ 0
-0.96875 175 1 010 1111 - 1.9375 * 2^ -1
-0.9375 174 1 010 1110 - 1.875 * 2^ -1
-0.90625 173 1 010 1101 - 1.8125 * 2^ -1
-0.875 172 1 010 1100 - 1.75 * 2^ -1
-0.84375 171 1 010 1011 - 1.6875 * 2^ -1
-0.8125 170 1 010 1010 - 1.625 * 2^ -1
-0.78125 169 1 010 1001 - 1.5625 * 2^ -1
-0.75 168 1 010 1000 - 1.5 * 2^ -1
-0.71875 167 1 010 0111 - 1.4375 * 2^ -1
-0.6875 166 1 010 0110 - 1.375 * 2^ -1
-0.65625 165 1 010 0101 - 1.3125 * 2^ -1
-0.625 164 1 010 0100 - 1.25 * 2^ -1
-0.59375 163 1 010 0011 - 1.1875 * 2^ -1
-0.5625 162 1 010 0010 - 1.125 * 2^ -1
-0.53125 161 1 010 0001 - 1.0625 * 2^ -1
-0.5 160 1 010 0000 - 1.0 * 2^ -1
-0.484375 159 1 001 1111 - 1.9375 * 2^ -2
-0.46875 158 1 001 1110 - 1.875 * 2^ -2
-0.453125 157 1 001 1101 - 1.8125 * 2^ -2
-0.4375 156 1 001 1100 - 1.75 * 2^ -2
-0.421875 155 1 001 1011 - 1.6875 * 2^ -2
-0.40625 154 1 001 1010 - 1.625 * 2^ -2
-0.390625 153 1 001 1001 - 1.5625 * 2^ -2
-0.375 152 1 001 1000 - 1.5 * 2^ -2
-0.359375 151 1 001 0111 - 1.4375 * 2^ -2
-0.34375 150 1 001 0110 - 1.375 * 2^ -2
-0.328125 149 1 001 0101 - 1.3125 * 2^ -2
-0.3125 148 1 001 0100 - 1.25 * 2^ -2
-0.296875 147 1 001 0011 - 1.1875 * 2^ -2
-0.28125 146 1 001 0010 - 1.125 * 2^ -2
-0.265625 145 1 001 0001 - 1.0625 * 2^ -2
-0.25 144 1 001 0000 - 1.0 * 2^ -2
-0.1171875 143 1 000 1111 - 0.9375 * 2^ -3
-0.109375 142 1 000 1110 - 0.875 * 2^ -3
-0.1015625 141 1 000 1101 - 0.8125 * 2^ -3
-0.09375 140 1 000 1100 - 0.75 * 2^ -3
-0.0859375 139 1 000 1011 - 0.6875 * 2^ -3
-0.078125 138 1 000 1010 - 0.625 * 2^ -3
-0.0703125 137 1 000 1001 - 0.5625 * 2^ -3
-0.0625 136 1 000 1000 - 0.5 * 2^ -3
-0.0546875 135 1 000 0111 - 0.4375 * 2^ -3
-0.046875 134 1 000 0110 - 0.375 * 2^ -3
-0.0390625 133 1 000 0101 - 0.3125 * 2^ -3
-0.03125 132 1 000 0100 - 0.25 * 2^ -3
-0.0234375 131 1 000 0011 - 0.1875 * 2^ -3
-0.015625 130 1 000 0010 - 0.125 * 2^ -3
-0.0078125 129 1 000 0001 - 0.0625 * 2^ -3
0.0 0 0 000 0000 0.0
0.0078125 1 0 000 0001 + 0.0625 * 2^ -3
0.015625 2 0 000 0010 + 0.125 * 2^ -3
0.0234375 3 0 000 0011 + 0.1875 * 2^ -3
0.03125 4 0 000 0100 + 0.25 * 2^ -3
0.0390625 5 0 000 0101 + 0.3125 * 2^ -3
0.046875 6 0 000 0110 + 0.375 * 2^ -3
0.0546875 7 0 000 0111 + 0.4375 * 2^ -3
0.0625 8 0 000 1000 + 0.5 * 2^ -3
0.0703125 9 0 000 1001 + 0.5625 * 2^ -3
0.078125 10 0 000 1010 + 0.625 * 2^ -3
0.0859375 11 0 000 1011 + 0.6875 * 2^ -3
0.09375 12 0 000 1100 + 0.75 * 2^ -3
0.1015625 13 0 000 1101 + 0.8125 * 2^ -3
0.109375 14 0 000 1110 + 0.875 * 2^ -3
0.1171875 15 0 000 1111 + 0.9375 * 2^ -3
0.25 16 0 001 0000 + 1.0 * 2^ -2
0.265625 17 0 001 0001 + 1.0625 * 2^ -2
0.28125 18 0 001 0010 + 1.125 * 2^ -2
0.296875 19 0 001 0011 + 1.1875 * 2^ -2
0.3125 20 0 001 0100 + 1.25 * 2^ -2
0.328125 21 0 001 0101 + 1.3125 * 2^ -2
0.34375 22 0 001 0110 + 1.375 * 2^ -2
0.359375 23 0 001 0111 + 1.4375 * 2^ -2
0.375 24 0 001 1000 + 1.5 * 2^ -2
0.390625 25 0 001 1001 + 1.5625 * 2^ -2
0.40625 26 0 001 1010 + 1.625 * 2^ -2
0.421875 27 0 001 1011 + 1.6875 * 2^ -2
0.4375 28 0 001 1100 + 1.75 * 2^ -2
0.453125 29 0 001 1101 + 1.8125 * 2^ -2
0.46875 30 0 001 1110 + 1.875 * 2^ -2
0.484375 31 0 001 1111 + 1.9375 * 2^ -2
0.5 32 0 010 0000 + 1.0 * 2^ -1
0.53125 33 0 010 0001 + 1.0625 * 2^ -1
0.5625 34 0 010 0010 + 1.125 * 2^ -1
0.59375 35 0 010 0011 + 1.1875 * 2^ -1
0.625 36 0 010 0100 + 1.25 * 2^ -1
0.65625 37 0 010 0101 + 1.3125 * 2^ -1
0.6875 38 0 010 0110 + 1.375 * 2^ -1
0.71875 39 0 010 0111 + 1.4375 * 2^ -1
0.75 40 0 010 1000 + 1.5 * 2^ -1
0.78125 41 0 010 1001 + 1.5625 * 2^ -1
0.8125 42 0 010 1010 + 1.625 * 2^ -1
0.84375 43 0 010 1011 + 1.6875 * 2^ -1
0.875 44 0 010 1100 + 1.75 * 2^ -1
0.90625 45 0 010 1101 + 1.8125 * 2^ -1
0.9375 46 0 010 1110 + 1.875 * 2^ -1
0.96875 47 0 010 1111 + 1.9375 * 2^ -1
1.0 48 0 011 0000 + 1.0 * 2^ 0
1.0625 49 0 011 0001 + 1.0625 * 2^ 0
1.125 50 0 011 0010 + 1.125 * 2^ 0
1.1875 51 0 011 0011 + 1.1875 * 2^ 0
1.25 52 0 011 0100 + 1.25 * 2^ 0
1.3125 53 0 011 0101 + 1.3125 * 2^ 0
1.375 54 0 011 0110 + 1.375 * 2^ 0
1.4375 55 0 011 0111 + 1.4375 * 2^ 0
1.5 56 0 011 1000 + 1.5 * 2^ 0
1.5625 57 0 011 1001 + 1.5625 * 2^ 0
1.625 58 0 011 1010 + 1.625 * 2^ 0
1.6875 59 0 011 1011 + 1.6875 * 2^ 0
1.75 60 0 011 1100 + 1.75 * 2^ 0
1.8125 61 0 011 1101 + 1.8125 * 2^ 0
1.875 62 0 011 1110 + 1.875 * 2^ 0
1.9375 63 0 011 1111 + 1.9375 * 2^ 0
2.0 64 0 100 0000 + 1.0 * 2^ 1
2.125 65 0 100 0001 + 1.0625 * 2^ 1
2.25 66 0 100 0010 + 1.125 * 2^ 1
2.375 67 0 100 0011 + 1.1875 * 2^ 1
2.5 68 0 100 0100 + 1.25 * 2^ 1
2.625 69 0 100 0101 + 1.3125 * 2^ 1
2.75 70 0 100 0110 + 1.375 * 2^ 1
2.875 71 0 100 0111 + 1.4375 * 2^ 1
3.0 72 0 100 1000 + 1.5 * 2^ 1
3.125 73 0 100 1001 + 1.5625 * 2^ 1
3.25 74 0 100 1010 + 1.625 * 2^ 1
3.375 75 0 100 1011 + 1.6875 * 2^ 1
3.5 76 0 100 1100 + 1.75 * 2^ 1
3.625 77 0 100 1101 + 1.8125 * 2^ 1
3.75 78 0 100 1110 + 1.875 * 2^ 1
3.875 79 0 100 1111 + 1.9375 * 2^ 1
4.0 80 0 101 0000 + 1.0 * 2^ 2
4.25 81 0 101 0001 + 1.0625 * 2^ 2
4.5 82 0 101 0010 + 1.125 * 2^ 2
4.75 83 0 101 0011 + 1.1875 * 2^ 2
5.0 84 0 101 0100 + 1.25 * 2^ 2
5.25 85 0 101 0101 + 1.3125 * 2^ 2
5.5 86 0 101 0110 + 1.375 * 2^ 2
5.75 87 0 101 0111 + 1.4375 * 2^ 2
6.0 88 0 101 1000 + 1.5 * 2^ 2
6.25 89 0 101 1001 + 1.5625 * 2^ 2
6.5 90 0 101 1010 + 1.625 * 2^ 2
6.75 91 0 101 1011 + 1.6875 * 2^ 2
7.0 92 0 101 1100 + 1.75 * 2^ 2
7.25 93 0 101 1101 + 1.8125 * 2^ 2
7.5 94 0 101 1110 + 1.875 * 2^ 2
7.75 95 0 101 1111 + 1.9375 * 2^ 2
8.0 96 0 110 0000 + 1.0 * 2^ 3
8.5 97 0 110 0001 + 1.0625 * 2^ 3
9.0 98 0 110 0010 + 1.125 * 2^ 3
9.5 99 0 110 0011 + 1.1875 * 2^ 3
10.0 100 0 110 0100 + 1.25 * 2^ 3
10.5 101 0 110 0101 + 1.3125 * 2^ 3
11.0 102 0 110 0110 + 1.375 * 2^ 3
11.5 103 0 110 0111 + 1.4375 * 2^ 3
12.0 104 0 110 1000 + 1.5 * 2^ 3
12.5 105 0 110 1001 + 1.5625 * 2^ 3
13.0 106 0 110 1010 + 1.625 * 2^ 3
13.5 107 0 110 1011 + 1.6875 * 2^ 3
14.0 108 0 110 1100 + 1.75 * 2^ 3
14.5 109 0 110 1101 + 1.8125 * 2^ 3
15.0 110 0 110 1110 + 1.875 * 2^ 3
15.5 111 0 110 1111 + 1.9375 * 2^ 3
inf 112 0 111 0000 + inf

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

8 位浮点数表示的实数

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

浮点数范围

bias

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

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

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

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

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

林宏

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.