自己紹介
- Shu OGAWARA(@expajp )
- リンカーズ株式会社
- Rails歴は2年弱くらい
- 出身は鳥取で大学は神戸
- 趣味は合唱
正規表現についての話
最近Reredosというgemをリリースしました

詳しくは「その正規表現、異議あり!〜ReDoSについて」で検索
RubyKaigi 2019お疲れ様でした
弊社もスポンサーブースを出してました
ところで
今回はクイズを行った企業さんが複数ありました
- 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'が返ってくる
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
が見つからない……
起こっていること(推測)
-
Float::NAN
どうしを数値として比較すると、false
- でも、同じオブジェクトなのでオブジェクトIDを比較すると、
true
- 配列を比較するとき、各要素が一致するかどうかは数値より先にオブジェクトIDを比較している
- aもbも
Float::NAN
を代入しているので、中身は同じオブジェクト - だから、配列に入れて比較すると
true
になる
- aもbも
確認
a.__id__ == b.__id__ # a.equal?(b)でもよい
# => true
起こっていること(推測)
- Float::NANに対して演算を行うと、NaNの入ったオブジェクトが別に作られる
- NaNどうしなので数値として比較すると
false
- 別々のオブジェクトになったのでオブジェクトIDの比較も
false
- NaNどうしなので数値として比較すると
→ [a] == [b]
もfalseになった
結論
NaNの挙動はよくわからない
まとめ
- NaNという概念がある
- NaNを含む、数値としての比較は必ずfalse
- でも、NaNが入っているオブジェクトどうしを比較すると
true
になることがある
- でも、NaNが入っているオブジェクトどうしを比較すると
- C言語を追うのは大変