Skip to content

Instantly share code, notes, and snippets.

@wangyingsm
Created May 20, 2022 18:45
Show Gist options
  • Save wangyingsm/d0c2ea841fdb77aebe46ee7ce4d59605 to your computer and use it in GitHub Desktop.
Save wangyingsm/d0c2ea841fdb77aebe46ee7ce4d59605 to your computer and use it in GitHub Desktop.
讲得很明白的IEEE754浮点数标准

IEEE 754浮点数表示

floats allocate bits to three different parts: the sign indicator, s, which says whether a number is positive or negative; characteristic or exponent, e, which is the power of 2; and the fraction, f, which is the coefficient of the exponent.

浮点数的表示分为三个部分:

  • 符号位s,指示该浮点数为正数还是负数;
  • 指数位e,表示为2为底的幂;
  • 分数位f,表示幂的系数部分;

所有平台的Python都使用IEEE754标准的64位浮点数表示。最高位为符号位,随后11位为指数位,最后的52位为分数位。

因为有11位作为幂指数位,因此表示范围为0~2047,因为浮点数需要能够表示很高的精度,因此,这个指数需要减去1023,真正的范围为-1023~1024。通常被称为bias

分数位通常是一个1~2的数值,因为使用的是52位二进制表示,因此第一位将永远是1,所以没必要使用一个bit来存储1。

整个浮点数可以使用下面的公式表示:

n = (-1)**s * 2**(e - 1023) * (1 + f)

举例:我们有着如下的一个64bit浮点数,1_10000000010_1000000000000000000000000000000000000000000000000000,按照公式计算:

-1 * 2**(0b10000000010 - 1023) * (1 + .5)

因为二进制中1000000000000000000000000000000000000000000000000000小数表示为

1 * (1 / 2 ** 1) + 0 * (1 / 2 ** 2) # + ... 后面项都是0

因此f是0.5,最终公式计算结果为-12.0。

还可以使用下面的方法来验证结果:

import struct
bytes = 0b1_10000000010_1000000000000000000000000000000000000000000000000000.to_bytes(8, 'little')
struct.unpack('d', bytes)[0]

得到同样的结果-12.0。

反过来,十进制15.0,在IEEE754中将会表达成什么形式?因为是正数,所以s为0,比15小的2的幂是8,因此,e为1023 + 3 = 1026,换成二进制为10000000011,分数部分只剩下15 / 8 = 1.875,所以f为0.875,即0.5 + 0.25 + 0.125,换成二进制为1110000000000000000000000000000000000000000000000000。最终得到的二进制表示为0_10000000010_1110000000000000000000000000000000000000000000000000

可以用下面的方法来验证结果:

bytes = struct.pack('d', 15.0)
assert(bytes == 0b0_10000000010_1110000000000000000000000000000000000000000000000000.to_bytes(8, 'little'))

15.0的上一个浮点数为0_10000000010_1101111111111111111111111111111111111111111111111111,换算成10进制为14.9999999999999982236431605997495353221893310546875。下一个浮点数为0_10000000010_1110000000000000000000000000000000000000000000000001,换算成10进制为15.0000000000000017763568394002504646778106689453125。

两个相邻浮点数之间的距离被称为gap,这个距离不是恒定的,浮点数越大,gap也就越大,这也是可以接受的,因为随着浮点数不断增大,那么gap造成的误差相对于浮点数本身仍然是可忽略的。numpy的spacing函数可以用来获得某个浮点数的gap。

import numpy as np
np.spacing(1e3) # output 1.1368683772161603e-13
np.spacing(1e50) # output 2.076918743413931e+34

幂指数e有两个取值是保留的,0和2047,当e=0时,浮点数被称为次正规数,用来表示一个非常高精度的小数,公式为n = (-1) ** s * 2 ** (-1022) * f,这里有两个变化,第一个是幂指数不是-1023,而是固定的-1022,第二个是分数部分不再加1,而是直接的分数值。例如正规数中二进制0_00000000001_0000000000000000000000000000000000000000000000000000是最小的正浮点数。它的十进制值是2.2250738585072014e-308

b = 0b0_00000000000_0000000000000000000000000000000000000000000000000001.to_bytes(8, 'little')
struct.unpack('d', b)[0]

而次正规数中二进制 0_00000000000_0000000000000000000000000000000000000000000000000001是最小的正浮点数。它的十进制值是5e-324

b = 0b0_00000000000_0000000000000000000000000000000000000000000000000001.to_bytes(8, 'little')
struct.unpack('d', b)[0]

而e=2047时,如果f不为0,那么该浮点数被定义为nan,不是合法的浮点数。如果f为0,按照s分为以下两种情况:

  • s=0,该浮点数为inf,正无穷。
  • s=1,该浮点数为-inf,负无穷。

因此最大非无穷的浮点数二进制为0_11111111110_1111111111111111111111111111111111111111111111111111,它的十进制值是1.7976931348623157e+308。以上结果都可以使用同样的python程序验证。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment