【Ruby】NaNのNaNa不思議

これは [Ruby Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ruby)の18日目の記事です。[Otemachi\.rb \#17 \- connpass](https://enechange-meetup.connpass.com/event/129264/)で話した内容の大幅加筆修正版です。

**実用性は皆無**ですので、求めている方はブラウザバックしてください。

## NaN (Not a Number) とは
みなさん **NaN** ってご存知でしょうか？

NaNとは **Not a Number** の略で、日本語では非数といいます。主に浮動小数点演算の結果が不定形だったり未定義だったりするときに返ってきます。

[constant Float::NAN \(Ruby 2\.6\.0 リファレンスマニュアル\)](https://docs.ruby-lang.org/ja/2.6.0/method/Float/c/NAN.html)

その数がNaNであるかどうかは、`Float#nan?`で調べることができます。

NaNの仕様は[IEEE 754](https://ieeexplore.ieee.org/document/4610935)で規定されているので、大抵のプログラミング言語には実装されています。

JavaScriptだとたまに見かけますが、RubyだとNaNを返すのではなく例外を発生させるケースが多い[^1]ため、ほぼ見かけないですね。

[^1]: 整数どうしのゼロ除算`0/0`や、負数の平方根`Math.sqrt(-1)`など

## NaNが返ってくるパターン
他にもあったらコメント欄などで教えて下さい。

### `Float::NAN`を代入する
```ruby
irb(main):001:0> nan = Float::NAN
# => NaN
```
そりゃそうだ

### NaNと四則演算をする
```ruby
Float::NAN + 1
# => NaN
Float::NAN - 1
# => NaN
Float::NAN * 1
# => NaN
Float::NAN / 1
# => NaN
```

上記のような四則演算に限らず、NaNと計算した結果はNaNになります。

つまり、**NaNは感染します**。

### 浮動小数点で不定形を計算する
「不定形」という言葉の意味、高校で理系数学をやった方はわかると思いますが、これを計算しようとするとNaNになります。

#### `0/0.0`

```ruby
0/0.0
# => NaN
```

こうなるのは浮動小数点数だけで、整数で `0/0`とすると`ZeroDivisionError`が返ってきます。

この違いは、両者がもつ意味の違いではないかと思います。

整数の `0`は正確に0である一方で、浮動小数点数で`x = 0.0`とすると`0 <= x < 0.05`を満たす数を表す、という意味をもつ、という違いがあります。

`0/0.0`はゼロ除算ではないのかもしれないけど、値を計算できないから非数となるのですね。

#### `INFINITY/INFINITY`
```ruby
Float::INFINITY / Float::INFINITY
# => NaN
```

`Float`クラスの中では「無限大」を表す `Float::INFINITY`が定義されています。

ただ無限大を無限大で割ってもどの値に収束するかわかりませんね。これもNaNです。

#### `INFINITY - INFINITY`
```ruby
Float::INFINITY - Float::INFINITY
# => NaN
```

こちらもNaNになります。

## NaNのNaNa不思議
ここからは、NaNのNaNa不思議、もとい変わった性質や挙動を見ていきましょう。

### 1. 自分どうしを`==`で比較すると`false`が返ってくる
```ruby
Float::NAN == Float::NAN
# => false
```

Rubyでこの挙動をする唯一の値ではないでしょうか。

これを利用した問題が、RubyKaigi 2019のエムスリーさんのブースで出題されてました。

[難読Rubyコードクイズ問題と解説 in RubyKaigi 2019 \- エムスリーテックブログ](https://www.m3tech.blog/entry/2019/04/25/123843)（Day3-3）

### 2. `truthy`である
```ruby
Float::NAN ? 'hoge' : 'piyo'
# => "hoge"

!!Float::NAN
# => true
```

「`nil`と`false`以外はすべて`truthy`」というRubyのルールにしたがって、NaNも真と判定されます。

これはそこまで変わった性質じゃないですが、**どんな値と比べてもfalseになるのに、単独だときっちり真と判定される**というのは直感的にはなんだか不思議です。

### 3. 複素数に変換できる
```ruby
Float::NAN.to_c
# => (NaN+0i)
```

お役所仕事という感じがします。

### 4. 複素数どうしで`0.0/0.0`をすると実部も虚部もNaNになる
```ruby
Complex(0.0, 0.0) / Complex(0.0, 0.0)
# => (NaN+NaN*i)
```
複素数つながりでもう一個。

複素数の割り算は分母の共役複素数を分母分子にかけて分母を実数にする、と教わりましたが、分母のゼロになにをかけてもやっぱりゼロです。

ちなみに、コメント欄で @scivola さんから指摘があったとおり、`0.0/0.0`以外でも同じ結果になる場合があります。

```ruby
1/(0.0i) 
# => (NaN+NaN*i)
```

### 5. ~~数値なのにsingletonではない~~ singletonとして振る舞わない数値である
Rubyの数値（Numeric）のうち、IntegerとFloatの一部はsingletonとして振る舞います。

```ruby
n = 1
m = 2
n.object_id == (m-1).object_id
# => true
```

計算しても、同じ値なら同じオブジェクトになります。

```ruby
0xff.object_id == 255.object_id
# => true

(1.2e-0).object_id == (0.12*10).object_id
# => true
```

このように、表記が違ったり整数でなかったりしても同じ数値は同じオブジェクトです。処理系全体で同じ数値のオブジェクトは1つしか存在しないんですね。

一方で、コメント欄で @Nabetani さんから指摘があったように、そうでない値もあります。

```ruby
1e77.object_id == 1e77.object_id
#=> true
1e78.object_id == 1e78.object_id
#=> false

(10**18).object_id == (10**18).object_id
#=> true
(10**19).object_id == (10**19).object_id
#=> false
```

他にも、こんな値がsingletonではありません。

```ruby
0.0.object_id == 0.0.object_id
# => true
0.0.next_float
# => 5.0e-324
0.0.next_float.object_id == 0.0.next_float.object_id
# => false
```

これだけでは、どんな値がsingletonになっているか断定することはできません[^2]。

[^2]: 総当たりで調べるには量が多すぎて無理

しかし、少なくともあまりに小さい値や大きな値はsingletonではないことから、目的はキャッシュなのでしょう。

NaNもこちらに該当します。NaNに1足してもNaNですが、

```ruby
nan.object_id == (nan+1).object_id
# => false
```

わざわざ定数が用意されていますが、キャッシュはされていないようです。


### 6. ハッシュのキーになることができるが、値が取り出せなくなることがある
```ruby
h = {}

h[:a] = 1
h[:a]
# => 1

h[0/0.0] = 2
h[0/0.0]
# => nil
```

こうなると、普通の方法ではこの「2」は取り出せません。

取り出す方法は色々あると思いますが、一例。

```ruby
h.to_a
# => [[:a, 1], [NaN, 2]]

h.to_a[1][1]
# => 2
```

ただし、`Float::NAN`をキーに使った場合や変数にNaNを入れた場合はちゃんと取り出すことができます。

```ruby
h = {}
h[Float::NAN] = 3
h[Float::NAN]
# => 3

n = 0/0.0
h[n] = 4
h[n]
# => 4
```

ハッシュのキーを比較するときには `Float#eql?`メソッドが使われているはず（参考： [るりまのObject#eql?](https://docs.ruby-lang.org/ja/latest/method/Object/i/eql=3f.html)）なのですが、前述の通り **NaNとの比較は無条件でfalseを返す** ことになっている[^4]ので、謎です。

[^4]: [ruby/numeric\.c at master · ruby/ruby](https://github.com/ruby/ruby/blob/master/numeric.c#L1654)

見た感じオブジェクトIDを比較している、つまり`BasicObject#equal?`で比較しているのですが、それらしいソースコードは見つけられず……

なお、出典はこちらの記事です。

[HashのキーをNaNにすると何が起きるか \- Qiita](https://qiita.com/HMMNRST/items/d749e2899d2752704f5c)

### 7. 配列に入れると`==`で`true`を返す
```ruby
Float::NAN == Float::NAN
# => false

[Float::NAN] == [Float::NAN]
# => true
```

配列以外のコンテナでも同様の挙動をします。

```ruby
{ Float::NAN => 0 } == { Float::NAN => 0 }
# => true
```

こちらも上記同様に`BasicObject#equal?`で比較していると思われる怪奇現象です。情報求む。

なお、出典はこちらの記事です。

[\`Float::NAN\` についての重箱の隅 \- Qiita](https://qiita.com/Nabetani/items/942abe870ee000607368)


## まとめ
NaNの挙動はよくわかりません。

それではみなさん、良いお年を。

## バージョン情報
```
$ ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19]
```

## 参考文献
* [NaN \- Wikipedia](https://ja.wikipedia.org/wiki/NaN)
* [754\-2008 \- IEEE Standard for Floating\-Point Arithmetic \- IEEE Standard](https://ieeexplore.ieee.org/document/4610935)
* [難読Rubyコードクイズ問題と解説 in RubyKaigi 2019 \- エムスリーテックブログ](https://www.m3tech.blog/entry/2019/04/25/123843)
* [instance method Object\#eql? \(Ruby 2\.6\.0 リファレンスマニュアル\)](https://docs.ruby-lang.org/ja/latest/method/Object/i/eql=3f.html)
* [HashのキーをNaNにすると何が起きるか \- Qiita](https://qiita.com/HMMNRST/items/d749e2899d2752704f5c)
* [\`Float::NAN\` についての重箱の隅 \- Qiita](https://qiita.com/Nabetani/items/942abe870ee000607368)