Perl では整数を扱うことができるが、とても大きな値 (あるいはとても小さな負の値) になると、自動的に浮動小数点数に切り替わる。
では、整数のまま扱える値の範囲はどこからどこまでなのか。
符号付き? 符号なし?
多くのプログラミング言語では、「32 ビット符号付き整数」とか「64 ビット符号なし整数」のような型が用意されている。このような決まったサイズで 2 進数表現の整数がよく使われるのは、コンピュータ自体 (というか CPU) がそのような形式の整数を扱いやすいようにできているためだ。
Perl はどうかと思って調べてみたところ、Perl インタプリタをビルドした時の設定によって、整数として扱える範囲が変わるらしい。
- 32 ビット環境向けにビルドした場合
- 32 ビット符号なし整数と、32 ビット符号付き整数が使える
- 64 ビット環境向けにビルドした場合
- 64 ビット符号なし整数と、64 ビット符号付き整数が使える
符号の有無は普段意識しないが、Perl インタプリタが内部的に管理していて、自動的に切り替わるらしい。大きな値の場合は符号なし整数で、負の値であれば符号付き整数として保持されているはずで、どちらでもない場合がどうなっているかはわからないが、まぁどちらでもいいか。
念のために書いておくと、あくまで符号付き整数かどうかと値を別に管理している、という話であって、符号と絶対値を別に持っているというわけではない。したがって、Perl で扱える負の整数は、正の整数の半分くらいまでになる。
ビット演算は不思議
プログラム内で最大値・最小値を意識する必要がある場合、32 ビットと 64 ビットで異なるのが少し困る。そこで、計算によって最大値・最小値を求めることを考えてみる。
いろいろ試していて、ふとこんな感じで書いたら最大値を求めることができた。
print(0 ^ -1, "\n"); # 18446744073709551615
僕が使ったのは 64 ビット環境なので、64 ビット符号なし整数の最大値、つまり 2 の 63 乗 - 1 が表示された。
Perl の ^ はビット単位の XOR 演算子で、すべてのビットが下りている状態の 0 と、すべてのビットが立っている符号付き整数の -1 で XOR を取った結果、すべてのビットが立っている符号なし整数になったらしい。不思議。
XOR じゃなくて OR にしても同じになった。
print(0 | -1, "\n"); # 18446744073709551615
どうやら、ビット演算をする場合はオペランドも結果も符号なし扱いされるらしい。0 と XOR や OR を取ったら元の値がそのまま残るものだと思っていたけど、Perl で負の整数を扱う場合はそうとは言えないようだ。
その後、ネットで検索をしていたら、もっとシンプルなのがあった。
print(~0, "\n"); # 18446744073709551615
この ~ は NOT で、~0 と書くことですべてのビットが立った符号なし整数が得られるらしい。こっちの方がわかりやすいね。
最初に書いたとおり、これより大きい値は浮動小数点数になる。
print(~0 + 1, "\n"); # 1.84467440737096e+19
それでは最小値はというと、ビット演算で符号付きを扱えないので他の種類の計算を混ぜて
print(-(~0 >> 1) - 1, "\n"); # -9223372036854775808
これ以上減らすと浮動小数点数になる。
print(-(~0 >> 1) - 2, "\n"); # -9.22337203685478e+18
printf の罠
ここまでの例で、print を使って値を出力できたことからも、Perl 内部で符号の有無がちゃんと管理されていることがわかる。
一方、printf では、どちらとして扱ってほしいかを自分で指定する必要がある。%d の場合は符号付き。符号なし整数を出力する場合には %u を指定する。
printf("%d\n", ~0); # -1 printf("%u\n", ~0); # 18446744073709551615 printf("%u\n", -1); # 18446744073709551615 printf("%d\n", -1); # -1
以前、他の人が書いたスクリプトで、値が正か負かで処理をわけているものがあって、なんでだろうと思っていた。具体的な内容は忘れてしまったけど、たぶんこの printf と同じように、符号付きと符号なしを場合分けしないといけないケースだったのだろう。
Perl っていうと何をするにも手間がかからないと言われるけど、このあたりはちょっと面倒だ。