以 GNU C
為例,它遵循 IEEE 754-2008
標準中制定的浮點表示規范。在該規范中定義了 5
種不同大小的基礎二進制浮點格式,包括:16
位,32
位,64
位,128
位,256
位。其中,32
位的格式被用作標準 C
類型的 float
,64
位的格式被用作標準 C
類型的 double
,128
位的格式被用作標準 C
類型的 long double
。
每種浮點格式都由三部分組成,包括:1
個比特的符號位、若干比特的指數位、若干比特的小數位,如下圖所示:
以 C
語言中 float
類型(32
位浮點)為例,它的符號位占 1
位,指數位占 8
位,小數位占 23
位,如下圖所示:
值得注意的是,
1)指數位編碼的指數以 2
為底數,若要表示很小的數,例如 1/(2^n) == 2^(-n)
,則指數位會出現負數 (-n)
,那么還需要專門的符號位來表示,因此指數位是以 127
為偏移的,也就是說,一個數在用浮點數來表示時,其指數位的值是實際指數值加上 127
。對于 64
位浮點數,它的指數位是 11
個比特,以 1023
為偏移。
2)浮點數的小數部分是將該浮點數表示為 2^n * 1.m
后,0.m
那部分的數值,也就是說,整數部分始終是 1
,且被忽略了(不用保存),這樣就能省出一位來表示更高的精度。
舉例來說,
1)0.5 = 2^(-1) * 1.0
,其指數位為 (-1 + 127)
,小數位為 0.0
,其二進制是:0 01111110 00000000000000000000000
,也就是 0x3F000000
。
2)5 = 2^(2) * 1.25
,其指數位為 (2 + 127)
,小數位為 0.25
,其二進制是:0 10000001 01000000000000000000000
, 也就是 0x40A00000
。
測試代碼如下:
#include <stdio.h>int main(void)
{float a = 0.5;unsigned int *b = (unsigned int*)&a;printf("b = 0x%X\n", *b);a = 5;printf("b = 0x%X\n", *b);
}
程序運行結果如下:
$ gcc -o main main.c
$ ./main
b = 0x3F000000
b = 0x40A00000
從 32
位浮點的指數寬度來看,理論上,它可以表示很大的數,比如:2^(128) * 1.9999999
,這比我們 32
位整型能表示的范圍(2^(32) - 1)
大的多,但受限于浮點小數位的寬度,有些 32
位整型數是無法用浮點數來表示的,也就是會出現空洞,原因是浮點數的小數部分精度有限,當小數部分比 1/(2^23)
還小時,就無法準確表示該整型數,例如:2^(24) + 1
這個數用 32
位整型來表示是沒有問題的,但用 32
位浮點來表示,則會被舍入為:2^24
。
測試代碼如下:
#include <stdio.h>int main(void)
{float a = 1 << 24;unsigned b = 1 << 24;printf("a = %f, b = %u\n", a, b);a += 1;b += 1;printf("a = %f, b = %u\n", a, b);return 0;
}
程序運行結果如下:
$ gcc -o main main.c
$ ./main
a = 16777216.000000, b = 16777216
a = 16777216.000000, b = 16777217
參考資料:
- https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Floating-Representations.html
- https://docs.nvidia.com/cuda/floating-point/index.html