Brief                              

  本来只打算理解JS中0.1 + 0.2 == 0.30000000000000004的原因,但发现自己对计算机的数字表示和运算十分陌生,于是只好恶补一下。

 本篇我们一起来探讨一下基础——浮点数的表示方式和加减乘除运算。

  在深入前有两点我们要明确的:

  1. 在同等位数的情况下,浮点数可表示的数值范围比整数的大;

  2. 浮点数无法精确表示其数值范围内的所有数值,只能精确表示可用科学计数法m*2e表示的数值而已;

     (如0.5的科学计数法是2-1,则可被精确存储;而0.2则无法被精确存储)

  3. 浮点数不仅可表示有限的实数,还可以表示有限的整数。

 

Encode                            

  20世纪80年代前每个计算机制造商都自定义自己的表示浮点数的规则,及浮点数执行运算的细节。而且不太关注运算的精确性,而是更多地关注速度和简便性。

  1985年左右推出IEEE 754标准的浮点数表示和运算规则,才让浮点数的表示和运算均有可移植性。(IEEE,读作Eye-Triple-Eee,电器与电子工程师协会)

  上述的IEEE 754称为IEEE 754-1985 Floating point,直到2008年对其进行改进得到我们现在使用的IEEE 754-2008 Floating point标准。

  IEEE 754的二进制编码由3部分组成,分别是sign-bit(符号位),biased-exponent(基于偏移的阶码域)和significant(尾数/有效数域)。

     Sign-bit:0表示正,1表示负。占1bit;

     Biased-exponent:首先exponent表示该域用于表示指数,也就是数值可表示数值范围,而biased则表示它采用偏移的编码方式。那么什么是采用偏移的编码方式呢?也就是位模式中没有设立sign-bit,而是通过设置一个中间值作为0,小于该中间值则为负数,大于改中间值则为正数。IEEE 754中规定bias = 2^e-1 - 1,e为Biased-exponent所占位数;
     Significant:表示有效数,也就是数值可表示的精度。(注意:Significant采用原码编码;假设有效数位模式为0101,那么其值为0*2-1+1*2-2+0*2-3+1*2-4,即有效数域的指数为负数)
     另外IEEE 754还提供4个精度级别的浮点数定义(单精度、双精度、延生单精度和延生双精度),单精度和双精度具体定义如下:

Level Width(bit) Range at full precision Width of biased-exponent(bit) Width of significant(bit)
Single Precision 32 1.18*10-38 ~ 3.4*1038 8 23
Double Precision 64 2.23*10-308 ~ 1.80*10308 11 52

     为了简便,下面以Single Precision来作叙述。
     基础野:细说浮点数

     现在我们了解到32bit的浮点数由3部分组成,那么它们具体又有怎样的组合规则呢?具体分为4种:

     Normalized(规格化)

        编码规则

          1. biased-exponent != 0 && biased-exponent != 2e-1;

          2. exponent = biased-exponent - Bias,其中exponent是指实际的指数值;

          3. significant前默认含数值为1的隐藏位(implied leading 1),即若significant域存储的是0001,而实际值是10001。

     Denormalized(非规格化)

        用于表示非常接近0的数值和+0和-0。

    +0的位模式为:0-00000000-00000000000000000000000

    -0的位模式为: 1-00000000-00000000000000000000000

    编码规则

     1. biased-exponent == 0 ;

          2. exponent = 1 - Bias,其中exponent是指实际的指数值;

          3. significant前默认含数值为0的隐藏位(implied leading 1),即若significant域存储的是0001,而实际值是00001。

     Infinity(无限大)

        用于表示溢出。

        编码规则

     1. biased-exponent == 2e-1 ;

          2. significant == 0。

        运算结果为Infinity的表达式

           1. Infinity == N/0,其中N为任意正数;

           2. -Infinity == N/-0,其中N为任意正数;

    3. Infinity == Infinity * N,其中N为任意正数。

     NaN(非数值)

   用于表示 结果既不是 实数 又不是 无穷。

        编码规则

     1. biased-exponent == 2e-1 ;

          2. significant != 0。

   运算结果为NaN的表达式

     1. Math.sqrt(-1)

     2. Infinity - Infinity

     3. Infinity * 0

       4. 0/0

        注意

    1. NaN与任何数值作运算,结果均为NaN;

    2. NaN != NaN;

    3. 1 == Math.pow(NaN, 0)。

  Rounding modes(aka Rounding scheme,舍入模式)

      由于浮点数无法精确表示所有数值,因此在存储前必须对数值作舍入操作。具体分为以下5种舍入模式

  1. Round to nearest, ties to even(IEEE 754默认的舍入模式)

    舍入到最接近且可以表示的值,当存在两个数一样接近时,取偶数值。(如2.4舍入为2,2.6舍入为3;2.5舍入为2,1.5舍入为2。)

         Q:为什么会当存在两个数一样接近时,取偶数值呢?

      A:由于其他舍入方式均令结果单方向偏移,导致在运算时出现较大的统计偏差。而采用这种偏移则50%的机会偏移两端方向,从而减少偏差。

  2. Round to nearest, ties to zero

    舍入到最接近且可以表示的值,当存在两个数一样接近时,取离0较远的数值

      3. Round to infinity

    向正无穷方向舍入

      4. Round to negative infinity

    向负无穷方向舍入

  5. Round to zero

    向0方向舍入

 

Overflow                            

  到这里我们对浮点数的表示方式已经有一定程度的了解了,也许你会迫不及待想了解它的运算规则,但在这之前我想大家应该要想理解溢出和如何判断溢出,不然无法理解后续对运算的讲解。

  Q1:何为溢出?

  A1:溢出,就是运算结果超出可表示的数值范围。注意:进位、借位不一定会产生溢出。

发生溢出:
4位有符号整数 7+7
  0111
+ 0111
  1110 => -2
只有最高数值位发生进位,因此发生溢出

没有发生溢出:
4位有符号整数 7-2
  0111
+ 1110
 10101 => 取模后得到5
符号位和最高数值位均发生进位,因此没有发生溢出

  Q2:如何判断溢出?

  A2:有两种方式判断溢出,分别是 进位判断法 和 双符号判断法。

         进位判断法

      前提:采用单符号位,即+4的二进制位模式为0100。

    无溢出:符号位 和 数值域最高位 一起进位或一起不发生进位。

           溢出:符号位 或 数值域最高位 其中一个发生进位。

         双符号判断法

    前提:采用双符号位,即+4的二进制位模式为00100。

           无溢出:两符号位相同。

           上溢出:两符号位为01。

           下溢出:两符号位为10。

  Q3:溢出在有符号整数和浮点数间的区别?

  A3:对于有符号整数而言,溢出意味着运算结果将与期待值不同从而导致错误;

        对于浮点数而言,会对上溢出和下溢出进行特殊处理,从而返回一个可被IEEE 754表示的浮点数。

        因此对于有符号整数的运算我们采用进位判断法判断溢出即可,而对于浮点数则需要采用双符号判断法了。

  Q4:浮点数运算中上溢出和下溢出具体的特殊处理是什么啊?

  A4:首先浮点数运算中仅对阶码进行溢出判断,当阶码出现下溢出时运算结果为0(符号取决于符号位);当阶码出现上溢出时运算结果为Infinity(符号取决于符号位)。

 

  PS:发生溢出时,当前程序会被挂起,并发送溢出中断信号量,此时溢出中断处理程序接收信号量后会做对应的处理

 

Addition & Subtraction                    

  恭喜你还没被上述的前置知识搞晕而选择X掉网页,下面我们终于可以着手处理运算问题,顺便验证一下自己对上述内容是否真的理解了。

  步骤:

    1. 对0、Infinity和NaN操作数作检查

    若有一个操作数为NaN则直接返回NaN;

    若有一个操作数为0则直接返回另一个操作数;

    若有一个操作数为Infinity,

      若另一个操作数也是Infinity且符号相同则返回第一个操作数;

      若另一个操作数也是Infinity且符号不同则返回NaN;

      若其他情况则返回Infinity。

        2. 对阶,若两操作数的阶码不相等,则对阶(小数点对齐)。

     由于尾数右移所损失的精度比左移的小,因此小阶向大阶看齐。

        3. 符号位+尾数(包含隐藏位)执行加减法。

            按有符号整数加减法进行运算,并采用双符号法判断是否溢出。

        4. 规格化。

            向右规格化:若步骤3出现溢出,则尾数右移1位,阶码+1;

            向左规格化:若步骤3没有出现溢出,且数值域最高位与符号位数值相同,则尾数左移1位且阶码-1,直到数值域最高位为1为止。

        5. 舍入处理

   6. 溢出判断

     由于规格化时可能会导致阶码发生溢出

              若无发生溢出,则运算正常结束。

              若发生上溢出,则返回Infinity。

              若发生下溢出,则返回0。

       示例1,0.75+(-0.75) = 0:

0.75+(-0.75) = 0

以8位存储,尾数采用原码编码
0.75  存储位模式 0-0110-100
-0.75 存储位模式 1-0110-100

1. 对阶
阶码一样跳过。

2. 符号位+尾数(含隐藏位)相加
由于尾数以有符号数的方式进行运算,因此要对尾数进行取补操作
  00-1100
+11-0100
 100-0000
 对符号位截断后得到00-0000

3. 规格化 
根据符号位相同,则执行左规格化操作,也就是尾数不断向左移而阶码不断减1,直到尾数最高位为1为止。

4. 舍入处理
000

5.溢出判断 
  在执行规格化时发生阶码下溢出,整体结果返回0-0000-000.
View Code

相关文章:

  • 2022-12-23
  • 2022-01-15
  • 2022-12-23
  • 2021-12-09
  • 2022-12-23
  • 2021-05-11
  • 2021-11-24
  • 2021-11-02
猜你喜欢
  • 2021-11-17
  • 2021-12-22
  • 2021-11-05
  • 2022-12-23
  • 2021-12-05
  • 2022-01-01
  • 2022-12-23
相关资源
相似解决方案