Cfdeaf5d2e334f5a778fcecf132bbf1b
浮点数设计及异常原因

浮点数设计及异常原因

上文我们在谈到PyFloatObject的时候,发现其代码实现中有着许多异常捕捉的逻辑,而在之前PyIntObject中却不曾发现。因此我们今天来深挖下浮点数的设计原理,来分析下为什么需要在浮点数的实现中包含异常捕捉。

浮点数在计算机中的表示方式

如无特殊说明,下文中阐述的浮点数在计算机中的表示方式均遵从IEEE-754标准

浮点数相信大家都不会很陌生,C语言中的floatdouble想必大家经常在编程中使用。一般来说float代表32bit的数据长度,而double代表的是64bit

从数据长度上来看,浮点数和整数并无实际区别,都是利用特定的内存大小使用对应长度的指令加载到寄存器中进行运算等操作。但是在实际的存储表征过程中,浮点数却有很大的不同。

下文都以64bit double来进行讲解

以上图为例,我们可以看出,浮点数的组成结构可以分为三个部分:

  • 1bit的符号位,这点和有符号整数是一致
  • 11bit的指数位,即类似科学计数法的指数位,只不过这里以2为基底,而不是10
  • 52bit的尾数,即浮点数的实际表示。

我们来分别解释后两者一下。

对于指数位来说,11bit的大小可以表征数值0-2047,但是,指数不仅仅有正数,也可以有负数,比如科学记数法里面的10^-1。因此,在保存指数的时候,实际上使用了一个1023的偏移,将指数可以实际表示的大小变为了[-1021, 1024](其实这里不准确,下文会继续深究)。

而对于尾数来说,由于我们采用了类似科学记数法的归一化保存形式(10进制下3.14必须保存成3.14 * 10^1,而不是0.314或者说31.4),因此在二进制下,小数点前面的整数部分只能保存1(1 <= 整数部分 < 2)

这意味着什么?意味着对于浮点数来说,52bit的尾数部分我们只需要保存小数点后面的小数部分即可,整数部分永远为1,换句话说,52bit的尾数可以表征53bit的数字大小。

到目前为止,我们可以把浮点数表征为如下形式:

SIGN(1bit) * 1.f(52bit) * 2^e(11bit)。

看起来很不错,哈哈,但是有一个边界条件我们没有思考清楚,0怎么保存 ???

归一化的表征方式是不行了?因此,在保存0的时候,浮点数的设计采用的一种特别的方式,将e的可表征部分从[-1023, 1024]缩小到[-1022, 1023],定义为eminemax。而emin - 1以及emax -1 设计为保留数。这样,0的浮点数表示即为:

  • e = emin - 1,f = 0,此时浮点数表征的就是0。
  • e = emax + 1, f = 0,此时浮点数表征的是无穷。
  • e = emax + 1, f != 0,此时浮点数表征的是NaN。
  • e = emin =1. f != 0,表征非归一化的浮点数,非归一化的浮点数可以表征更大的数值范围,但是精度有丢失。此时所有的非归一化浮点数都表征为0.f * 2^emin

继续,我们可以发现,在归一化的表征形式下,最大的值为1.11111(52个1) * 2^1023(也就是我们通常使用的DBL_MAX),最小值同理,改变符号位即可。但是需要注意,DBL_MIN指的是最小表征的正数:即1.0 * 2^-1022次方。

上述1.11111(52个1) * 2^1023这个数字的计算,有个更简便的方式:1.1111111(52个1)一定是个无穷逼近2的数(因为整数部分为1,转换成十进制就是1 * 2^0 = 1),因此,我们可以用(2 - 2^-52)来近似表征。

而如果是非归一化的形式下的最小正数表征可以是0.0000000xxxxx01(51一个0, 最后第52为1) 乘上 2^-1022。

Python的浮点数数值设定

看完了上述的内容,我们再回过头来看下Python里面关于浮点数的一些数值设定。从[floatobject.c]中我们不难发现如下一段代码:

static PyStructSequence_Field floatinfo_fields[] = {
    {"max",             "DBL_MAX -- maximum representable finite float"},
    {"max_exp",         "DBL_MAX_EXP -- maximum int e such that radix**(e-1) "
                    "is representable"},
    {"max_10_exp",      "DBL_MAX_10_EXP -- maximum int e such that 10**e "
                    "is representable"},
    {"min",             "DBL_MIN -- Minimum positive normalizer float"},
    {"min_exp",         "DBL_MIN_EXP -- minimum int e such that radix**(e-1) "
                    "is a normalized float"},
    {"min_10_exp",      "DBL_MIN_10_EXP -- minimum int e such that 10**e is "
                    "a normalized"},
top Created with Sketch.