x == x + 1 なる x


x == x + 1 - ellaneous 経由で、さらに http://d.hatena.ne.jp/nishiohirokazu/20071129/1196348472n == n + 1 - 西尾泰和のはてなダイアリー を経て知った、2007-11-25 - プログラミング日記 より。


大きな数の精度を超えた小さな数を足しても変化は感知されないというからくり。


Squeak Smalltalk では、

| x |
x := 1e16.
^x == x + 1   "=> error: MessageNotUnderstood: True>>+"

なんだってー! 嘘です。わざとです。ごめんなさい。Smalltalk では #== も #+ もメソッドで、それらのコールに際して優先順位はないので、これだと (x == x) + 1 と解釈されてしまいます。それで (x == x) で返された true に #+ なんてしらねぇんだけど…ってぼやかれます。なので括弧を付けます。

| x |
x := 1e16.
^x == (x + 1)   "=> false "

なんだってー! 嘘です。わざとです。ごめんなさい。Smalltalk では等価チェックは #= で #== は同一性チェックです。

| x |
x := 1e16.
^x = (x + 1)   "=> false "

なんだってー! 嘘です。わざとです。ごめんなさい。Squeak Smalltalk では、1e16 は、 LargePositiveInteger に属する整数になってしまうので浮動小数点数にするためには、1.0e16 と書かないといけません。ただし、GNU Smalltalk では 1e8 あるいは 1d16、1q32 で問題ありません。

| x |
x := 1.0e16.
^x = (x + 1)   "=> true "

できました。落とし穴っぽいところをあえてすべて踏んでみました(^_^;)。


あと、元サイトの解答にもあるように、無限大を使うパターンもありますね。無限大は Squeak Smalltalk では Float infinity で得られます。

| x |
x := Float infinity.
^x = (x + 1)   "=> true "


念のため GNU Smalltalk では FloatE、FloatD、FloatQ への infinity の送信です(GNU Smalltalk の Float class には #infinity はないようです)。

"GNU Smalltalk"
| result |
result := OrderedCollection new.
Smalltalk allBehaviorsDo: [:class |
    (class selectors includes: #infinity) ifTrue: [result add: class]].
result asArray print!   "=> (FloatQ class  FloatE class  FloatD class) "
"GNU Smalltalk"
| x |
x := FloatE infinity.
(x = (x + 1)) print!  "=> true "


あと、Cincom Smalltalk (VisualWorks) では、この種の無限大を扱うために MetaNumeric なるパーセルをロードする必要があるようです(System -> Load Parcels Named...)。さらに無限大は、このパーセルにより追加されるクラス Infinity のインスタンスとして実装されていて、Infinity への positive の送信で得られます。しかし、#= は使えないようなので #== する必要があります。

"Cincom Smalltalk"
| x |
x := Infinity positive.
^x == (x + 1)   "=>  true "


NaN については、Squeak Smalltalk では、Float への nan の送信で得られます。Float infinity と同じように + 1 しても相変わらず Float nan ですが、Float infinity と違って Float nan 同士は等価チェックは false を返します。

Float nan = Float nan   "=> false "
Float nan   "=> NaN "
Float nan + 1   "=> NaN "


配列結合操作の穴(?)をついて、強制的に配列に変換してから比較してやると、Float nan 同士が保持する情報については等価であることがわかります。

(#(), Float nan)  = (#(), Float nan)   "=> true "
(#(), Float nan)  = (#(), (Float nan + 1))   "=> true "