HN 日本語サマリー

← 一覧へ戻る
プログラミング

NaNの2つのケーススタディ

Two Case Studies of NaN (sebsite.pw)

17 pointsby ryantsuji4 コメント

要約

この記事では、プログラミング言語の設計において、浮動小数点数の特殊値であるNaN(Not a Number)が予期せぬ動作を引き起こす2つのケーススタディを紹介しています。Pythonでは、リストの等価性比較における最適化がNaNの反射律(自分自身と比較して常に等しい)を満たさないために問題が生じる可能性があり、Luaでは数値forループの条件判定がNaNに対して直感的でない動作を引き起こします。これらの例は、NaNの特殊な性質が考慮されていない場合に発生する設計上の落とし穴を示唆しています。

全文翻訳

NaNの2つのケーススタディ 2026-07-09 IEEE-754のNaNは奇妙です。そして、その奇妙さゆえに、しばしば意図せず考慮から漏れてしまいます。私は、これがプログラミング言語の設計に忍び込んだ2つの事例を見つけました。つまり、これらの言語のセマンティクスは、NaNによって破られる暗黙の仮定を持っています。 ケーススタディ1: Python >>> from math import nan >>> nan == nan False >>> [nan] == [nan] True 最適化として、リストを等価性で比較する際、要素はまずアイデンティティ(同一性)で比較されます。アイデンティティが異なる場合のみ、等価性がチェックされます。一般的に、オブジェクトは常に自分自身と等しく比較されると仮定されています。これはPython 3の参照マニュアルに明記されています。 User-defined classes that customize their comparison behavior should follow some consistency rules, if possible: Equality comparison should be reflexive. In other words, identical objects should compare equal: x is y implies x == y これはNaNに対しては真ではありません。そしてそれは問題ありませんが、他のほとんどすべてのものに対してこの仮定が成り立つため、NaNが考慮されない(あるいは少なくともNaNに非常に奇妙な動作を与える)ような設計上の選択が行われます。明確にしておくと、私はPythonのこの動作が間違っている、あるいはNaNが奇妙に動作するからといってこの最適化が悪いと言っているわけではありません。しかし、少なくとも指摘する価値はあると思います。 ケーススタディ2: Lua Luaの数値forループは以下のようになります。 for i = 1, 10 do stuff() end これが機能するはずの方法は、iが初期値(ここでは1)で始まり、各イテレーションでステップ(オプションの3番目のオペランド、デフォルトは1)で増加し、新しい値がリミット(10)より大きくなったときにループが終了するというものです。したがって、このループは1から10(包括)まで10回繰り返されます。 では、NaN(0/0)が混ざるとどうなるでしょうか?リファレンス実装(puc-rio lua)での動作は以下の通りです。 -- executes once for i = 0/0, 10 do print(i) -- nan end -- executes once for i = 0/0, 0/0 do print(i) -- nan end -- never executes for i = 1, 10, 0/0 do print(i) end -- executes once for i = 10, 1, 0/0 do print(i) -- 10.0 end 上記のコードを読んで、何が起こっているのか理解してみてください。これらの結果はすべて非常に直感的ではありません。 起こっていることは次のとおりです。最初の2つは一度だけ実行されます。なぜなら、forループのチェックは limit < init として実装されていますが、後続のイテレーションでは idx <= limit をチェックするからです。したがって、NaNは最初のテストをパスしますが、2番目のテストはパスしません。後の2つの例は、NaNがステップとして使用される場合、math.abs(0/0) を行っても常に負として扱われることを示しています。これは、ステップが正であるかのチェックが 0 < step であり、NaNに対しては当然常に偽となるためです。 ちなみに、これらは何も文書化されていないため、これはおそらく実際の見落としです。しかし、それは本当に興味深いことです。NaNを使用すると、実装によって使用される特定の比較演算子がインタプリタの動作に漏れ出します。ここでの簡単な解決策は、数値forループでNaNをチェックして禁止することです。Luaはすでに0をステップとして使用した場合にエラーを発生させるので、NaNに対するそのようなチェックがないのは興味深いです。 結論 Chris Siebenmannも以前、GoでのNaNをマップキーとしての奇妙な動作について書いていました。したがって、これはNaNの奇妙さに関する3つのケーススタディになるでしょう。NaNは奇妙です。そしてそれは、他のものがそれと奇妙に動作することを意味します。