読者です 読者をやめる 読者になる 読者になる

euphonictechnologies’s diary

Haskell超初心者の日記です。OCamlが好きです。

follow us in feedly

Swift - 整数型でハマる

Swift

Swiftの整数型はSwift Standard Library | Apple Developer Documentationにあるように、符号ありと符号なしの幅指定なしのInt/UIntと幅指定有りのInt32/Int64...などがあります。

こんな感じです。

Type Minimum Value Maximum Value
Int8 -128 127
Int16 -32,768 32,767
Int32 -2,147,483,648 2,147,483,647
Int64 -9,223,372,036,854,775,808 9,223,372,036,854,775,807
Int 32bit環境ではInt32, 64bit環境ではInt64と同じ 左に同じ
UInt8 0 255
UInt16 0 65,535
UInt32 0 4,294,967,295
UInt64 0 18,446,744,073,709,551,615
UInt 32bit環境ではUInt32, 64bit環境ではUInt64と同じ 左に同じ

(Int, UIntを追記:20161023)

符号有り無しの違い

符号有り無しでの違いは

  • 当然符号なし整数には負数が格納できない
  • 最大値・最小値が違う
  • シフト時の動作が違う

上2つは明らかだと思います。シフト時の動作は符号ありの場合は算術シフト、符号なしの場合は論理シフトになります。なので、符号ありの整数のシフトは符号ビット(MSB)は変わらず、(例えば64ビット整数の場合)残りの下位63ビットがシフトします。符号なしの場合は関係なく全部シフトして、空いたところには0が入ります。

整数型同士の計算

四則演算や剰余の演算子が使えます。

The Swift Programming Language (Swift 3): Basic Operators

演算子は同じ型同士の計算でしか使えません。UIntをIntで割ったりかけたりはできません。

あとはビット演算もできます。C言語と同じく&,|,^が使えます。シフトもできます。

The Swift Programming Language (Swift 3): Advanced Operators

整数型同士の変換

これは簡単で

let a: UInt8 = 0b00001111
let b: Int8 = Int8(a)

というように整数型の初期化を使います。ただ

let a: UInt8 = 0b10001111
let b: Int8 = Int8(a)

これはいけない。正しくは

let a: UInt8 = 0b10001111
let c: Int8 = Int8(a &% UInt8(Int8.max))

としないとオーバーフローしてしまう。実際のプログラムはオーバーフローしたところで強制終了します。 Int8の最大値を超える数値を代入してはいけない。UInt8の場合は0b10000000を超える数字はInt8にキャストできません。当然Int16とかにはできます。

オーバーフロー演算子

というわけで、Swiftはオーバーフロー検査がいちいち入っているので例えばInt8同士の計算で

let x1: UInt8 = 0b11111111
let x2: UInt8 = 0b00000001
x1 + x2

はできません。答えがUInt8の範囲を超えるからです。ただ、適当にオーバーフローしても良い場合、オーバーフロー演算子を使えばオーバーフローした後の結果が帰ってきます。つまり、オーバーフローした後の計算の下位8ビットがこの場合は答えになります。

let x1: UInt8 = 0b11111111
let x2: UInt8 = 0b00000001
x1 &+ x2    // Taking least 8 bit of 0b100000000 is 0b00000000 = 0

とにかく罠が多い。安全とも言えます。

The Swift Programming Language (Swift 3): Advanced Operators

IntまたはUIntの運用(20161023追記)

つらつらとわかっている人向けに整数のハマりどころについて書いてきました。言語マニュアルはIntを使うように強く勧めているようです。なので上の話がさっぱりで、整数がビット列としてメモリ上に乗っていることを想像できない人はIntだけを使うのが安全です。これは初心者を侮っているのではなくて本当にそれが安全です(ここ強調)。

整数変数のビットイメージが明確にあって、どういう処理が走るのかμOpCodeレベルでイメージできる人はInt以外を使いましょう。巨大な変数が必要なときは巨大整数ライブラリを使いましょう。"swift big integer"ぐらいで検索するとたくさん出てきます。つってもUをつけても使える最大整数は倍にしかならないですけどね。

じゃあどういうときに幅指定IntやUInt達が必要なの?

幅指定Intが必要な理由は使いたい範囲が広いもしくは狭いからではありません。(超メモリタイトな環境でなければ)そんな理由で使ってはいけません。 整数の範囲検査は基本的には自分でassertするべきです。

幅指定が必要な理由はIntをビット列として使うため、つまりバイナリを扱うときや暗号化に必要なシフト、ローテート演算など、整数をビット列として取り扱うときです。UIntが必要な理由も一緒です。普通の四則演算の整数として使うときはIntを使うのが安全です。たとえ絶対に非負整数だとわかっていてもIntを使いましょう。

オーバーフロー検査なんかいらんわ

という場合はコンパイラの最適化レベルを変えましょう。 プロジェクトの設定のBuild Settings -> Swift Compiler - Code Generationのところを

f:id:euphonictechnologies:20141220131021p:plain

と変えるとオーバーフロー検査とかその他もろもろの検査(配列の範囲外検査)とかが実施されなくなって若干早くなります。セキュリティとかに関わるプログラムではやめたほうが良さそうですが、あまり正確な計算が必要でないスピードが必要なプログラム、そう、例えば将棋やチェス、オセロのAIなんかでは役に立ちそうです。