LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

【Ruby】不思議の国のNaN

Last updated at Posted at 2019-05-15
1 / 40

自己紹介

  • Shu OGAWARA(@expajp )
    • リンカーズ株式会社
    • Rails歴は2年弱くらい
    • 出身は鳥取で大学は神戸
    • 趣味は合唱

正規表現についての話


最近Reredosというgemをリリースしました

スクリーンショット 2019-05-15 17.12.02.png

詳しくは「その正規表現、異議あり!〜ReDoSについて」で検索


RubyKaigi 2019お疲れ様でした

D4ZQMlXUwAATodH.jpg-large.jpeg


弊社もスポンサーブースを出してました

D4ZZVC9U4AA4pwG.jpg-large.jpeg


ところで


今回はクイズを行った企業さんが複数ありました

  • Cookpad
  • エムスリー
  • 他にも?

エムスリーさんのクイズでこんなものが

irb> a = 0.0/0; a == a ? a : irb.quit
# 直後に何が発生しますか?

選択肢:
* ZeroDivisionError
* undefined local variable
* irbが終了する
* irbが起動する


答えは「irbが起動する」


irb> a = 0.0/0; a == a ? a : irb.quit
# 直後に何が発生しますか?

この問題の要点は、
* a == aが何を返すか
* irb.quitを実行するとどういう挙動をするか


後者の詳しい解説はエムスリーさんのテックブログを読んでいただくとして


前者のみ抜き出したコード

irb> a = 0.0/0; a == a ? 'hoge' : 'piyo'
# どうなりますか?

選択肢
* 'hoge'が返ってくる
* 'piyo'が返ってくる
* ZeroDivisionError が発生
* undefined local variable


正解は


'piyo'が返ってくる :baby_chick:


a = 0.0/0のとき、

  • ZeroDivisionErrorは起きない
  • a == aはfalsy

なぜなのか?


実際にやってみた


Q. NaNってなに

A. Not a Number

JavaScriptだと割とよく見る


Not a Number

  • 浮動小数点数が出ることを期待して行った計算の結果だが、計算できなかったときの結果
  • 略称はNaN
  • IEEE 754 で定義されている
  • RubyではFloat::NANに定数として定義されている
  • Rubyだと不定形(0/0, ∞/∞)以外はほぼ出ない
    • 他の言語だとマイナスの平方根を取ろうとしたり文字列を演算しようとしたりしても出る

NaNの性質

  • 比較できない
  • 感染する

NaNは比較できない

実際にやってみよう

  • NaNの比較
  • NaNのソート

じゃあこれはうまく動かないんじゃ?

a = 0/0.0
if(a == Float::NAN) put "a is NaN"

NaNであることの判定にはnan?を使う

a = 0/0.0
if(a.nan?) put "a is NaN"

NaNは感染する

  • NaNとの計算はNaNになる
  • NaNが含まれていると、どこかでエラーが起こるので気付く
    • RubyはNaNで例外が上がることが多いので恵まれている
    • JSだとどこがNaNになったのか追いかけることが割とある

CRubyのNaNの不思議な性質


ハッシュ編


ハッシュのキーになることができる

a = 0/0.0
h[a] = 1

のに、比較ができないから普通には値が取り出せない

h[Float::NAN]
# => nil

さらに、複数のNaNをキーに持つことができる

b = 0/0.0
h[b] = 2
h
# => {NaN=>1, NaN=>2}

配列編


配列に入れると比較の仕方が変わる

a = Float::NAN
b = Float::NAN
a == b
# => false

配列に入れると比較の仕方が変わる

[a] == [b]
# => true

配列に入れると比較の仕方が変わる

b = b + 1
# => NaN
[a] == [b]
# => false

!?


思わず実装を見に行った

static VALUE
rb_ary_equal(VALUE ary1, VALUE ary2)
{
    if (ary1 == ary2) return Qtrue;
    if (!RB_TYPE_P(ary2, T_ARRAY)) {
    if (!rb_respond_to(ary2, idTo_ary)) {
        return Qfalse;
    }
    return rb_equal(ary2, ary1);
    }
    if (RARRAY_LEN(ary1) != RARRAY_LEN(ary2)) return Qfalse;
    if (RARRAY_CONST_PTR_TRANSIENT(ary1) == RARRAY_CONST_PTR_TRANSIENT(ary2)) return Qtrue;
    return rb_exec_recursive_paired(recursive_equal, ary1, ary2, ary2); // ここ
}

rb_exec_recursive_paired が見つからない…… :thinking:


起こっていること(推測)

  • Float::NANどうしを数値として比較すると、false
  • でも、同じオブジェクトなのでオブジェクトIDを比較すると、true
  • 配列を比較するとき、各要素が一致するかどうかは数値より先にオブジェクトIDを比較している
    • aもbもFloat::NANを代入しているので、中身は同じオブジェクト
    • だから、配列に入れて比較するとtrueになる

確認

a.__id__ == b.__id__ # a.equal?(b)でもよい
# => true

起こっていること(推測)

  • Float::NANに対して演算を行うと、NaNの入ったオブジェクトが別に作られる
    • NaNどうしなので数値として比較するとfalse
    • 別々のオブジェクトになったのでオブジェクトIDの比較もfalse

[a] == [b]もfalseになった


結論

NaNの挙動はよくわからない


まとめ

  • NaNという概念がある
  • NaNを含む、数値としての比較は必ずfalse
    • でも、NaNが入っているオブジェクトどうしを比較するとtrueになることがある
  • C言語を追うのは大変

参考文献

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up