たれぱんのびぼーろく

わたしの備忘録、生物学とプログラミングが多いかも

sys.float_info.minと配列代入の罠 - numpy float32

sys.float_info.min (とても小さい値) を代入しても何故か0.になる。logが死ぬ。
データ型を疎かにしてたらハマった.

再現コード

float_array = numpy.array([1., 2.,], dtype=np.float32)
tiny = sys.float_info.min
print(float_array)
# [1. 2.]

float_array[1] = tiny
print(float_array)
# [1. 0.]
# 0. …だと…!?

match = float_array[1] == tiny
print(f"float_array[1] == tiny: {match} ({float_array[1]} vs {tiny})")
# float_array[1] == tiny: False (0.0 vs 2.2250738585072014e-308)

原因

Pythonにはfloatの更なる区分 (FP32/floatとFP64/doubleとか) がない.
でもnumpyには浮動小数点精度が複数ある.
sys.float_info.minは内部で利用されてくる精度の値を出してくるけど、numpyは指定したdtypeで値を受け入れる.
float64の最小値をfloat32で表現すると0.なので、0代入が発生しちゃう.

よくあるシチュエーション

外部ライブラリがnumpy arrayを返してくるが、そのデータ型を意識していない.
その状態でarrayの一部をsys.float_info.minで上書きしようとしたのに、なぜか0.になる.
結果logが死ぬ.

解決策

numpyを使うなら、使ってるdtypeの最小値を使えばいい.

float_array = numpy.array([1., 2.,], dtype=np.float32)

false_tiny = sys.float_info.min
true_tiny = np.finfo(float_array.dtype).tiny
print(false_tiny) # 2.2250738585072014e-308
print(true_tiny)  # 1.1754944e-38

float_array[1] = true_tiny
print(float_array) # [1.0000000e+00 1.1754944e-38]
match = float_array[1] == true_tiny
print(f"float_array[1] == tiny: {match} ({float_array[1]} vs {true_tiny})")
# float_array[1] == tiny: True (1.1754943508222875e-38 vs 1.1754943508222875e-38)

その他tips

Pythonのfloat精度は実行環境によるっぽい.
これが原因で「別の環境に持ってったら壊れた!?」とかはありうる.