美文网首页
编码:定点数和浮点数

编码:定点数和浮点数

作者: 汪小鱼 | 来源:发表于2021-11-11 21:12 被阅读0次

    1 前言

      本文是基于《编码》、《穿越计算机的迷雾》两部著作进行读后整理的记录性博客。对书中较为重要的内容进行归纳整理进行二次创作,略去了繁琐的讲述细节,力求简明扼要。


    编码:一种由若干符号和规则组成的系统,用来向计算机表述指令。

    2 正文

    2.1 定点数和浮点数

      数字就是整数、分数以及百分数等各种类型的数字与我们形影不离,它几乎出现在我们生活的所有角落。

      在计算机存储器中,整数和分数之间的转换并不是这么随意。现在我们应该清楚,计算机中的一切数据都是以位的形式存储的,这就意味着所有的数都表示为二进制形式。

      在数学学习过程中我们了解到了很多类型的数,例如正整数、负数、分数、无理数等等。对于最为常见的正整数,我们很容易理解怎么进行表示。但对于其他类型的数就没有那么简单。

      我们习惯于把数字看做连续(continuous)的,任意给出两个有理数,都可以找出一个位于它们之间的数(实数集的稠密性)。实际上,只需要取这两个数的平均值即可。但是,数字计算机对连续数据却无能为力,因为二进制中的每一位非0即1,两者之间没有任何数(0.5)。这一特点决定了数字计算机只能处理离散(discrete)数据。二进制数的位数直接决定了所能表示的离散数值的个数。

      例如,如果你选择的二进制位数是 32,则所能表示的自然数的范围是 0~4,294,967,295。如果想要在计算机中存储 4.5 这个数,则需要选择新的方法并做一些其他方面的改进。

      小数也可以表示为二进制数吗?当然可以,最简单的方法可能就是使用 BCD 码(二进制编码的十进制数)。BCD 码是将十进制数以二进制的形式进行编码,0~9 之间的每一个数都需要用 4 位来表示,如下表所示。

      通常把两个 BCD 数字存放在一个字节,这种方式称为压缩 BCD。压缩 BCD 通常需要增加 1 位用来标识数的正负,该位被称做符号位(sign bit)。用一整个字节保存某个特定的 BCD 数是很方便的,但要为这个短小的符号位牺牲 4 位或 8 位的存储空间。

      让我们来看一个例子,-4,325,120.25 可以表示为下面 5 个字节:
    00010100 00110010 01010001 00100000 00100101

      将每个字节转换成十六进制数,上面的数可以等价地表示成:14h 32h 51h 20h 25h。最左边的半个字节所构成的 1 用来指明该数是负数,这个 1 即符号位。如果这半个字节所构成的数是 0,则说明该数是正数。组成该数的每一个数字都需要用 4 位来表示。

      这种基于二进制的存储和标记方式也被称作定点格式(fixed-point format),所谓的 “定点” 是指小数点的位置总是在数的某个特定位置——在本例中,它位于两位小数之前。值得注意的是,有关小数点位置的计数信息并没有与整个数字一起存储。所以,使用定点小数的程序必须知道小数点的位置。你可以设计有任意小数位的定点小数,并且可以在程序中混合使用它们,但程序中对这些数进行算术运算的部分都需要知道小数点的位置,这样才能正确地对其做各种运算处理。

    如果可以确定程序用到的数字不会大到超过预定的存储空间,并且这些数的小数位不会很多,那么使用定点格式的小数将是一个很好的选择。在表示非常大或非常小的数时,使用定点格式数是绝对不合适的。

      科学家和工程师们喜欢使用一种称为 “科学计数法”(scientific notation)的方法来记录这类较大或较小的数,利用这种计数系统可以更好地在计算机中存储这些数。科学计数法把每个数表示成有效位与 10 的幂的乘积的形式,这种计数方式特别适合表示极大或极小的数。
    490000000000=4.9×{10}^{11},0.0026=2.6×{10}^{-3}

      在上面的两个例子中,4.9 和 2.6 被称做小数部分或者首数,有时候也被称作尾数,在计算机术语中这一部分被称做有效数。采用科学计数法表示的数可以分为两部分,其中指数部分用来表示 10 的几次幂。

    为了便于操作,一般规定有效数的取值范围是大于或等于 1 而小于 10。

      在计算机中,对于小数的存储方式,除了定点格式外还有另外一种选择,它被称做浮点格式(floating-point notation)。因为浮点格式是基于科学计数法的,所以它是存储极大或极小数的理想方式。但计算机中的浮点格式是借助二进制数实现的科学计数法形式,因此我们首先要了解如何用二进制表示小数。

      在十进制数中,小数点右边的数字与 10 的次幂相关联;而在二进制数中,二进制小数点(就是一个简单的句点,看起来同十进制小数点一样)右边的数字和 2 的次幂相关。例如,下面这个二进制数:
    101.1101=1×4+0×2+1×1+1÷2+1÷4+0÷8+1÷16=5.8125

      在十进制的科学计数法中,规范化式的有效数应该大于或等于 1 且小于 10;类似的,在二进制的科学计数法中,规范化式的有效数应该大于或等于 1 且小于 10(即十进制的 2)。因此,在二进制的科学计数法中,上例中的二进制数其用科学计数法表示应该为:
    101.1101=1.011101×2^2

      这个规则暗示了这样一个有趣的现象:在规范化二进制浮点数中,小数点的左边通常只有一个 1,除此之外没有其他数字。

    当代大部分计算机和计算机程序在处理浮点数时所遵循的标准是由IEEE(美国电气和电子工程师协会)于 1985 年制定的 ANSI/IEEE Std 754-1985 称作 IEEE 二进制浮点数算术运算标准。

      IEEE 浮点数标准定义了两种基本的格式:以 4 个字节表示的单精度格式和以 8 个字节表示的双精度格式。

      让我们首先来了解一下单精度格式。它的 4 个字节可以分为三个部分:1 位的符号位(0 代表正数,1 代表负数),8 位用做指数,最后的 23 位用做有效数。下表给出了单精度格式的三部分的划分方式,其中有效数的最低位在最右边。

      三部分共 32 位,也就是 4 个字节。我们刚才提到过,对于二进制科学计数法的规范化式,其有效数的小数点左边有且仅有一个 1,因此在 IEEE 浮点数标准中,这一位没有分配存储空间。在该标准中,仅存储有效数的 23 位小数部分,尽管存储的只有 23 位,但仍然称其精度为 24 位。我们将在下面的内容里体会 24 位精度的含义。

      8 位指数部分的取值范围是 0~255,称为偏移(biased)指数,它的意思是:对于有符号指数,为了确定其实际所代表的值必须从指数中减去一个值——称做偏移量(bias)。对于单精度浮点数,其偏移量为 127(指数部分除去符号位,有效位为 7 位)。

      指数 0 和 255 用于特殊的目的,稍后将简单介绍。如果指数的取值范围是 1~254,那么对于一个特定的数,可以用 s(符号位),e(指数)以及 f(有效数)来描述它:
    {(-1)}^s × 1.f × 2^{e-127}

      表达式的中间部分是 1.f,其含义是:1 的后面是小数点,小数点后面跟着 23 位的有效数。1.f 与 2 的幂相乘,其中指数等于内存中的 8 位的偏移指数减去 127。

      注意,目前为止我们还没有学习如何表达那个经常遇到却又总被遗忘的一个数字:“0”。这是一种特殊的情况,下面我们对其进行说明。

    • 如果 e = 0 且 f = 0,则该数为 0。在这种情况下,通常把 32 位都设置为 0 以表示该数为 0。但是符号位可以设置为 1,这种数可以解释为负 0。负 0 可以用来表示非常小的数,这些数极小以至于不能在单精度格式下用数字和指数来表示,但它们仍然小于 0。
    • 如果 e = 0 且 f ≠ 0,则该数是合法的,但不是规范化的(注意,在有效数中,小数点的左边是 0)。
      {(-1)}^s × 0.f × 2^{e-127}
    • 如果 e = 255 且 f ≠ 0,则该值被解释为 “不是一个数”,通常被缩写为 NaN(not a number)。NaN 用来表示未知的数或非法操作的结果。

    这里是针对单精度浮点数的几种特殊形式进行了补充说明

      单精度浮点格式下,可以表示的规范化的最小正、负二进制数是:
    1.00000000000000000000000_2×{10}^{-126}

      可以表示的规范化的最大正、负二进制数是:
    1.111111111111111111111111_2×{10}^{127}

      在十进制下,这两个数近似地等于 l 和 u(见下所示),这也就是单精度浮点数的有效表示范围。
    l = 1.175494351×{10}^{-38},u=3.402823466×{10}^{38}

      如前面的文章所述,10 位二进制数可以近似地用 3 位十进制数来表示。其含义是,如果把 10 位都置为 1,即十六进制的 3FFh 或十进制的 1023,它近似等于把十进制数的 3 位都置为 9,即 999,可以表示为下面的约等式:
    2^{10}\approx {10}^3

      两者之间的这种关系意味着:单精度浮点数格式存放的 24 位二进制数大体上与 7 位的十进制数相等。因此,可以说单精度浮点格式提供 24 位的二进制精度或者 7 位的十进制精度。

      当我们查看定点数时,其精确度是很明显的。例如,当我们表示钱款时,采用两位定点小数就可以精确到美分。但是对于采用浮点格式的数,就不能如此肯定了。其精确度依赖于指数的值,有时候浮点数可以精确到比美分还小的单位,但有时候其精确度甚至达不到美元。

      这样说可能更合适:单精度浮点数的精度为 1/2^{24},或 1/16777216,或百万分之六,但其真正的含义是什么呢?

      这意味着在单精度浮点格式下,16,777,216 和 16,777,217 将表示成同一个数。不仅如此,处于这两个数之间的所有的数(例如,16,777,216.5)也将被表示成同一个数。

      这也是为什么人们在处理钱款数目时更愿意使用定点数的一个原因。当使用浮点数时,你会发现它存在着一些让人崩溃的小问题。你的程序进行了一系列计算,应该得到的结果为 3.50 的,但由于使用浮点数,你得到的可能是 3.499999999999。这种问题在浮点数运算中经常发生,而且没有一套完整的解决方案。

      如果想在程序中使用浮点格式数,但使用单精度格式又会出现各种问题,这时你可以考虑使用双精度浮点数。这种类型的数需要用 8 个字节来表示,它的结构如下表所示。

      双精度浮点数的指数偏移量是 1023,或十六进制的 3FFh,因此以该格式存储的数可以表示为:
    {(-1)}^s × 1.f × 2^{e-1023}

      上面提到的关于单精度浮点格式下的 0,无穷大(小)和 NaN 的判断规则同样适用于双精度浮点格式。

      和单精度浮点数类似,其所能表示的范围,用十进制可以近似记为:
    2.2250738585072014 × {10}^{-308} \sim 1.7976931348623158 × {10}^{308}

      双精度浮点格式的有效数有 53 位(包括前面没有列出的那一位),大致相当于十进制的 16 位。与单精度浮点格式相比,这已经有了很大的改进了,但仍然不能避免两个不同的数存储为同一个结果的情况。

      根据上面的介绍,我们会发现定义了一种浮点数的存储规范,当然也就面临着浮点数的四则运算等诸多运算问题。浮点数的计算和一般计算不同,需要针对浮点数的存储特点进行设计,如有效数部分和指数部分分开计算,这里我们不针对这部分内容进行展开。


    3 小结

      定点数和浮点数篇介绍了在计算机中如何表示小数,以及介绍了浮点数在计算机中的计算存在误差,因此在某些情况下利用计算机计算得到的是近似值。为了精简内容删减了部分较为详细的书写,仅作为整理总结。

    相关文章

      网友评论

          本文标题:编码:定点数和浮点数

          本文链接:https://www.haomeiwen.com/subject/pcvizltx.html