半精度浮動小数点型について

ちょっとまだ普及したと言うには程遠いのではありますが。

# そもそもこれは何か

概説は[ウィキペ](https://ja.wikipedia.org/wiki/%E5%8D%8A%E7%B2%BE%E5%BA%A6%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0)でも見てください。
IEEE 754-2008 には、16ビットを使って浮動小数点数を表現する新しい型 binary16 が導入されました。

もともと IEEE 754-2008 での言い方では interchange format といってデータ交換用で演算用ではなかったのですけれど、近年は画像処理のためGPGPUで半精度浮動小数点数の演算ができるものが現れ（NVIDIA Pascal以降らしい)、機械学習で活用されるようになりました。
CPUでもいまどきSIMD命令がありますから、ARM (A64fx) や x86_64 (Sapphire Rapids) でも半精度浮動小数点数の演算ができるものが出てきています。

すると、さまざまな局面で使えないのかと気になってきますね。とりあえず現状をまとめておきます。

# 言語処理系の対応

残念ながら x86 の Fortran で使えないというのが現況です。

## x86_64 C

まずは GCC 12.3 以降で対応しています。Sapphire Rapids のネイティブ命令で高速に計算するためには -mavx512f16 オプション等を指定します。そうでなければエミュレーションを行う関数が呼ばれるようになって、それは libgcc 12 以降に含まれています。

また Intel OneAPI でも LLVM ベースの新コンパイラ（icx）ではサポートしています。GCCと同様に -mavx512f16 でネイティブコードが出ます。どうやらエミュレーションは libgcc に丸投げしている模様。

## x86_64 Fortran

まだ対応している処理系については知りません。

[2023-12-20追記: 使ったことはありませんが、NAG Fortran が対応しているらしいです（演算ではなく変換のみ）。KIND値も独特です。

https://www.nag-j.co.jp/nagfor/np70_manual/compiler_2_12.html

]

## A64fx C

TCS C では Clang モード（ -Nclang 指定時）に _Float16 型がサポートされます。

## A64fx Fortran

TCS Fortran では -Nclang 指定の有無にかかわらず、 REAL(kind=2) 型がサポートされます。

# データフォーマットの対応

netCDF ではまだ対応していないんだけれど議論はしているようです。

https://github.com/Unidata/netcdf-c/issues/2359

# 数値的特性

## ビット配置図

ビット配置図を wikimedia から引用掲示します。IEEE 754 単精度や倍精度の幅を短くした形になっています。

![半精度浮動小数点型のビット配置図 - wikimedia](https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/IEEE_754r_Half_Floating_Point_Format.svg/320px-IEEE_754r_Half_Floating_Point_Format.svg.png)

## 値の範囲と精度

主要な値は次の通り。なお、quiet/signaling の区別と indefinite はインテル x86 での場合。

| 十六進表記 | 値 | 意義 |
|:-:|-:|:-|
| 7FFF ... 7E00 | NaN | quiet NaN |
| 7DFF ... 7C01 | NaN | signaling NaN |
| 7C00 | Inf | 正の無限大 |
| 7BFF | 65504 | 正の有限数の最大 |
| 7800 | $2^{15} =$ 32768 | ２のべきの最大 |
| 4000 | 2.0 | |
| 3C00 | 1.0 | |
| 0400 | $2^{-14} =$ 6.10352e-05 | 正の有限数の最小 |
| 03FF | 6.09756e-05 | 正の非正規化数の最大 |
| 0001 | $2^{-24} =$ 5.96046e-08 | 正の非正規化数の最小 |
| 0000 | 0.0 | 正のゼロ |
| 8000 | -0.0 | 負のゼロ |
| 8001 ... 83FF | -5.96046e-08 ... -6.09756e-05 | 負の非正規化数 |
| 8400 ... FBFF | -6.10352e-05 ... -65504 | 負の有限数 |
| FC00 | -Inf | 負の無限大 |
| FC01 ... FDFF | NaN | signaling NaN |
| FE00 ... FFFF | NaN | quiet NaN |
| FFFF | NaN | quiet NaN indefinite |

また、有限数・非正規化数について、次のような精度になっている。

| 絶対値 | 値刻み | 注記 |
|:-:|-:|:-|
| 32768 ... 65504 | $2^5$ = 32 | 一番精度が粗くなる場合 |
| ... | | |
| 1024 ... 2048 | $2^{0}$ = 1 | 2048 までの整数が正確に表現できる |
| 512.0 ... 1024.0 | $2^{-1}$ = 0.5 | |
| 256.0 ... 512.0 | $2^{-2}$ = 0.25 | |
| 128.0 ... 256.0 | $2^{-3}$ = 0.125 | |
| 64.0 ... 128.0 | $2^{-4}$ = 0.0625 | |
| ... | | |
| 2.0 ... 4.0 | $2^{-9}$ = 0.001953125 | |
| 1.0 ... 2.0 | $2^{-10}$ = 0.000976562 | |
| 0.5 ... 1.0 | $2^{-11}$ = 0.000488281 | |
| ... | ||
| $2^{-14}$ ... $2^{-13}$ | $2^{-24} =$ 5.96046e-08 | 有限数の最小 |
| $2^{-24}$ ... $2^{-14}$ | $2^{-24} =$ 5.96046e-08 | 非正規数は等間隔 |

## C プログラミングとしての注意点

* -2048 ... 2048 の範囲の整数が正確に表現できる。
* 8ビット整数（C の unsigned char または signed char）を _Float16 に変換しても値は変わらない。
* 16ビット符号付き整数（通常は C の signed short）を _Float16 に変換すると、絶対値が2048より大きい場合値が丸められて誤差を生じるが、オーバーフロー（例外トラップまたは値 Inf の発生）は起こらない。
* 16ビット符号なし整数（通常は C の unsigned short）を _Float16 に変換すると、絶対値が2048より大きい場合値が丸められて誤差を生じるし、65504 ... 65535 の範囲についてはオーバーフロー（例外トラップまたは値 Inf の発生）が起こる。

## 気象データの伝達保存形式として考えると

GRIB や NuSDaS の諸問題に類似するわけですが：

* 浮動小数点値で -100.0 ... 100.0 程度の範囲に収まるものは、刻み 0.1 以下で表現できる。摂氏温度、雲量、相対湿度などが挙げられ、このような目的ではかなり満足できる。
* 絶対温度は 256.0 ... 512.0 の範囲となり、刻みは 0.25 となる。いくぶん精度が物足りない場合が多かろうから、摂氏温度への変換が定石となるだろう。仮数部の丸めの挙動などを気にするのであれば、摂氏温度よりも「絶対温度 - 256K」などのほうが好ましいかもしれないが、見慣れない表現は誤りのもとで避けたい。
* 海面更正気圧[hPa]は 1024 を超えることがあるので、刻みは 1 となる。これは実用上耐え難いので、摂氏温度と似た発想で「気圧 - 1000 hPa」などの値を保存することにならざるを得まい。
* ジオポテンシャル高度についても同様で、高度により異なるバイアスが避けられないだろう。
* 降水量 [kg/m2] については 1024 以下で 0.5 刻みなので、まあまあ満足できそう。しかも 65504 までオーバーフローしないので安心できる。