概要
前回の続き↓
JavaScript さん「うちには NaN ってのがありましてね。 Not-a-Number って意味なんですが、それの型が実は number でしてね」
緑さん「ガハハ。それは難儀ですねえ、うちの Python にはそんなのありませんよ」
Python さん「あるで」
キタキタ。プログラミングで遊んでて、こういうのが一番楽しいんだから。1日1 Python のお時間です。
ビビることに、 Python にも NaN がある
マジで?!
print(float('NaN')) # nan
print(float('nan')) # nan
# JavaScript では Not-a-Number is a number っていうクスッとする仕様だったが、
# Python では Not-a-Number is a float という仕様。
print(type(float('NaN'))) # <class 'float'>
float()
へ、文字を渡しても良いパターンがあったとは! 今回は無関係だから省いているけれど、 'inf'
とかも渡せるらしいぜ。
float ってことはマイナスもいけるの?
以前こんなことがあったよな。
緑さん「ええ? 無いだろ? ゼロは負の数でも正の数でも無いでしょ」 Python さん「あるで」 float 型のゼロにはマイナスがある
今回の NaN が float 型ってことは、マイナスもあり得るのか? というのが自然な発想だ。 Not-a-Number is a float and it can be negative. なんてことにもなるのか?
# float といえば、マイナスゼロがあることで有名。
# これでも作れるし、
print(float('-0.0')) # -0.0
# これでも作れる。
print(0.0 * -1) # -0.0
print(0.0 / -1) # -0.0
# じゃあ NaN もマイナスになるの?
# なんない。
print(float('-NaN')) # nan
print(float('NaN') * -1) # nan
print(float('NaN') / -1) # nan
なんないかー、……と思いきや!
# 通りすがりの名探偵「待ちたまえ。バイナリ状態で観察してみたのかね?」
import struct
print(struct.pack(">d", float('NaN'))) # b'\x7f\xf8\x00\x00\x00\x00\x00\x00'
print(struct.pack(">d", float('-NaN'))) # b'\xff\xf8\x00\x00\x00\x00\x00\x00'
# ん違うぅ?! 最初の \x7f と \xff 何ィ?!
なんか違うんだけど!
こないだのマイナスゼロ記事の最後で、こんなこと↓を書いたよな。
float
、つまり浮動小数点数は、以下の構造から出来ている。
- 符号 (正か負か)
- 仮数部
- 指数部
NaN は float 型なので、これらに分解することができるはず。やってみよう。
# float 状態
nan = float('NaN')
minus_nan = float('-NaN')
# bytes 状態
nan_bytes = struct.pack(">d", nan)
minus_nan_bytes = struct.pack(">d", minus_nan)
# int 状態
nan_int = int.from_bytes(nan_bytes, "big")
minus_nan_int = int.from_bytes(minus_nan_bytes, "big")
# 2進数状態
nan_bits = f"{nan_int:064b}"
minus_nan_bits = f"{minus_nan_int:064b}"
# ようやく float を 符号, 仮数部, 10**指数部 へ分解できる。
print(f"NaN: {nan_bits[0]} {nan_bits[1:12]} {nan_bits[12:]}")
print(f"-NaN: {minus_nan_bits[0]} {minus_nan_bits[1:12]} {minus_nan_bits[12:]}")
# NaN: 0 11111111111 1000000000000000000000000000000000000000000000000000
# -NaN: 1 11111111111 1000000000000000000000000000000000000000000000000000
# 最初のパーツだけ違う。
やっぱ NaN
と -NaN
は違う! いちおう普通の float でも確認してみよう。
# 比較のために 1.234 と -1.234 も分解してみる。
sample = 1.234
minus_sample = -1.234
sample_bytes = struct.pack(">d", sample)
minus_sample_bytes = struct.pack(">d", minus_sample)
sample_int = int.from_bytes(sample_bytes, "big")
minus_sample_int = int.from_bytes(minus_sample_bytes, "big")
sample_bits = f"{sample_int:064b}"
minus_sample_bits = f"{minus_sample_int:064b}"
print(f"1.234: {sample_bits[0]} {sample_bits[1:12]} {sample_bits[12:]}")
print(f"-1.234: {minus_sample_bits[0]} {minus_sample_bits[1:12]} {minus_sample_bits[12:]}")
# 1.234: 0 01111111111 0011101111100111011011001000101101000011100101011000
# -1.234: 1 01111111111 0011101111100111011011001000101101000011100101011000
# 最初のパーツだけ違くて、他が同じだから、最初のが + と - を表しているのは確か。
よって、 “NaN は float だからマイナスをつけられる!” Not-a-Number is a float and it can be negative!
おしまい
面白すぎるだろこれ。