浮動小数点数を理解する

# はじめに
浮動小数点数について説明します。
浮動小数点数はコンピュータ内部で小数点数を扱う方式の1つです。
IEEE 754で規格化されています。

# 表記
ここで用いる表記を説明します。

10進数と2進数を区別するため、2進数の場合、後ろにbをつけます。
例. 11b = 3

記号の下付き数値は対応するbitを表します。
例. $f_0$はfを表すbitの0番目です。

# format
浮動小数点数を表すbit列を符号s、指数e、有効数字f の3つの部分に分けます。
e / f の bit lengthに応じていくつかのformatがあります。biasは後で説明します。

| format | s | e | f | s+e+f | bias |
|:-:|:-:|:-:|:-:|:-:|:-:|
| half | 1 | 5 | 10 | 16 | 15 |
| single | 1 | 8 | 23 | 32 | 127 |
| double | 1 | 11 | 52 | 64 | 1023 |
| quad | 1 | 15 | 112 | 128 | 16383 |

format の違いは e / f のbit length の違いのみであり、
考え方は同じため 以降は singleについてのみ説明します。

s / e / f の bit割り当ては次の通りです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/84205/608847f0-e782-b70f-38d5-38206f3cf6f9.png)

次の式を使ってbit列と小数点数を対応付けます。2種類の方式があります。
それぞれ**正規化数、非正規化数**といいます。127はbiasです。

$(-1)^s(1.f_{22}f_{21}...f_0)_b 2^{(e-127)}$　... 正規化数

$(-1)^s(0.f_{22}f_{21}...f_0)_b 2^{-126}$ ... 非正規化数

sは符号です。
s = 0 は正の数、s = 1 は負の数
を表します。

e / f の組み合わせによってbit列の意味が変化します。
対応関係を以下に示します。e = 0 / e = 255は特別な意味を持ちます。

| e | f | bit列の意味 |
|:--|:--|:--|
| 0  | 0 | +0 or -0を表します |
| 0  | !0 | 非正規化数を表します |
| 255 | 0 | +inf or -infを表します |
| 255 | !0 | NaNを表します |
| 1~254 | any | 正規化数を表します |

fは有効数字です。
1.5の場合は f = 100 0000 0000 0000 0000 0000 b となります。

# 具体例
具体例で浮動小数点数表現を理解しましょう。
10進数の12.375を浮動小数点数で表します。

10進数を2進数にします。
12.375 = 12 + 0.375 = 1100b + 0.011b = 1100.011b

正規化数にします。つまり1.xxxxの形にします。
1100.011b = 1.100011b * 2^11b と変形します

よって
f = 100011b

eについては bias = 127 = 111 1111b であり、
11b = e - 127 のため、
移項して e = 11b + 127 = 11b + 111 1111b = 1000 0010b となります。

最終的には次のバイナリになります
0 10000010 10001100000000000000000 b
0100 0001 0100 0110 0000 0000 0000 0000 b = 0x41460000

# sample code
プログラムで浮動小数点数の表現を確認します。

```c:test.c
#include <stdio.h>
#include <stdint.h>

void print_binary(uint32_t val)
{
    int i;
    for (i=0; i<32; i++) {
        uint32_t tmp = ((val>>(31-i))&1);
        printf("%01d", tmp);
        if (i == 0 || i == 8) {
            printf(" ");
        }
    }
}

void print(float f)
{
    float _f = f;
    uint32_t* p = (uint32_t*)&_f;
    print_binary(*p);
    printf(" = 0x%08x = %e ", *p, _f);
    printf("\n");
}

int main()
{
    float f;
    uint32_t* p = (uint32_t*)&f;
    printf("s eeeeeeee fffffffffffffffffffffff b\n");
    print(12.375f);
    print(1.f);
    print(-1.f);
    // +0 s=0 e=0 f=0
    *p = 0x00000000;
    print(f);
    // +inf s=0 e=ff f=0
    *p = 0x7f800000;
    print(f);
    // NaN s=0 e=ff f=1
    *p = 0x7f800001;
    print(f);
    // largest  s=0 e=fe f=7fffff
    *p = 0x7f7fffff;
    print(f);
    // smallest s=0 e=00 f=000001
    *p = 0x00000001;
    print(f);

    return 0;
}
```

```bash:console
gcc test.c && ./a.out
s eeeeeeee fffffffffffffffffffffff b
0 10000010 10001100000000000000000 = 0x41460000 = 1.237500e+01
0 01111111 00000000000000000000000 = 0x3f800000 = 1.000000e+00
1 01111111 00000000000000000000000 = 0xbf800000 = -1.000000e+00
0 00000000 00000000000000000000000 = 0x00000000 = 0.000000e+00
0 11111111 00000000000000000000000 = 0x7f800000 = inf
0 11111111 00000000000000000000001 = 0x7f800001 = nan
0 11111110 11111111111111111111111 = 0x7f7fffff = 3.402823e+38
0 00000000 00000000000000000000001 = 0x00000001 = 1.401298e-45  
```