https://youtu.be/jAIlBOEXILc
今は The Basics
の 整数
について眺めていっていますけれど、今回はその中でも有限桁数で構成される整数の境界値に焦点を当ててみます。Swift の整数型ではどんな範囲の値を表現できるのか、そんな辺りに着目しながら整数について見ていけたらいいなと思っています。どうぞよろしくお願いしますね。
—————————————————————————— 熊谷さんのやさしい Swift 勉強会 #111
00:00 開始 01:02 整数型の最小値と最大値 01:45 min と max 02:44 適切なサイズの数値型 04:13 適切な型を返さない事例 06:08 型が違うと型合わせが必要になる 08:45 型推論との併用 10:25 max とオーバーフロー 12:08 オーバーフローの静的検査 16:17 2の補数表現 17:30 意図した型に寄せていく 19:59 数値の範囲 20:14 範囲で最大値を扱うとき 23:31 last と end 25:52 最大値の扱いに注意 28:00 符号なし整数の最小値 29:29 符号なし整数の min と zero 29:55 符号なし整数の最大値 30:24 最大値の算出 31:24 符号付き整数と符号なし整数の最大値の関係 31:52 色表現 32:57 2進数から10進数への変換 ——————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #111
では今日の内容を始めますね。今日は引き続き、整数に関してお話をしていきます。特に Swift での整数について見ていきますが、その前に少し細かい話も交えながら進めていきたいと思います。
まずは「整数の境界」についてです。整数というのは有限で、その範囲が有限であるという話を前回しました。今回はその範囲について具体的に見ていきます。
まず、Swiftでは整数の最小値と最大値を min
と max
で取得できるようになっています。これらのプロパティは適切なサイズの数字型であり、同じ型の他の値と一緒に評価式で使えるということが書かれています。それを実際に Swift のプレイグラウンドで見てみましょう。
以下が例です。整数型の min
と max
はそれぞれスタティックメンバーとして用意されています。
let minInt = Int.min
let maxInt = Int.max
このようにして最小値と最大値を簡単に確認することができます。これを使って例えば、let x = Int.min + 10
のように計算することもできます。
また、Int8
型でも同様に最小値と最大値を確認できます。
let minInt8 = Int8.min
let maxInt8 = Int8.max
このコードをプレイグラウンドで実行すると、適切な型で最小値と最大値が取得できることが確認できます。具体的には Int
型の場合は Int.min
、Int8
型の場合は Int8.min
としてそれぞれの型に応じた最小値が取得できます。
ここで大事なことは、これが文脈的に自然に使用できることです。つまり、それぞれの型が期待する最小値や最大値が簡単に取得でき、それを使った計算も自然に行えるということです。
これが C言語などの場合、専用のヘッダファイルから定数を取得したりすることが一般的です。例えば、<limits.h>
ヘッダファイルに定義されている INT_MAX
などを使う形です。
#include <limits.h>
printf("INT_MAX: %d\\n", INT_MAX);
printf("INT8_MAX: %d\\n", INT8_MAX);
しかし、C言語では型に対しての変換が暗黙的に行われるため、INT_MAX
のような定数の型が明示的でないことがあります。一方、Swiftでは明示的に型が示されるため、型の整合性が保たれます。例えば、C言語では int32_t
型で返されることが多いですが、Swiftでもそれに合わせる形で Int32
という型が用意されています。
このように、Swift では C言語のような古い言語と比較して型の安全性がより強固に保たれているため、プログラムの信頼性が高まります。
今回の内容はここまでです。引き続き、次回も Swift の整数や関連するトピックについて学んでいきますので、どうぞお楽しみに。 型が Int32
になっているので、先ほどの y
みたいなものを計算するときに Int8.min
を使おうとすると、たとえば Int.min + 10
というふうにやろうとすると、これはリテラルなので通ってしまいます。計算は通ってしまいますが、type(of:)
演算をすると Int32
型になります。このように、Int8
のつもりで使っていたのに Int32
になってしまうことがあります。
それほど特殊な事情でなくても、たとえば整数 A
が Int
型、B
が Int8
型で用意されているときに、Int
型の A
に対して B
を足す場合、ここで型が異なるため、型が違うとエラーが出てしまいます。このとき、どちらかの型を合わせる必要がありますが、どちらを合わせるかは状況に依存します。このようなことが起こると少し煩わしく感じるかもしれません。
Slideの内容に戻りますが、ここで min
と max
のプロパティは適切なサイズの数値型であり、同じ型の他の値と一緒に評価式で使えるという話です。これにより、シームレスに使えるということが強調されています。C言語と比べて、ここが強調されています。
また、適切な型が変わるということが型推論と合わせるとさらに便利になっています。例えば、print(A + min)
や print(B + min)
といったコードを使うと、型推論のおかげで定数 A
が Int
型であれば Int
型の min
、B
が Int8
型であれば Int8
型の min
が使われます。これは非常に便利です。このように、それぞれの型に対してプロパティ min
が存在するため、型推論がうまく機能し、迅速に処理できます。これは非常に面白い仕組みですよね。
その .max
を足さない場合も、意図的にやっていましたが、 .max
を足すと最大値にいくつか加えることになりますので、オーバーフローが発生します。これも面白いですよね。簡単にオーバーフローが発生し、どこでオーバーフローしたのかわからなくなることがあります。この画面上でオーバーフローが発生しているのですが、例えば変数 A
に Int8.max
プラス 1 を足すとオーバーフローエラーが発生します。
簡単な例を試してみると、Int8.max + 1
などでもオーバーフローが発生します。これはコンパイルエラーとしても出る場合があります。ランタイムエラーの場合、変数を使用しているとエラーがランタイム中に発生しますが、リテラルの場合はコンパイルエラーとして素直に出ることがあります。これも面白い点です。 今映っている画面では、コンパイルエラーかランタイムエラーか判断しにくいと思いますが、これはコンパイルエラーのはずです。リテラルの最大値とInt8
のmax
を足すときにコンパイルエラーが発生します。普通の価値観だと、これがランタイムエラーになるはずですよね。なぜなら、max
を実行して初めて値を得て、リテラルとの足し算をランタイムで行うのが一般的だからです。しかし、Swiftではこれがコンパイルエラーになります。この判断ができる賢さが、自分にはまだ少し不思議だと感じます。
他にも例があります。Int8
の変数に対して、その範囲を超える値を代入しようとすると、これもコンパイルエラーになります。昔の常識では、このようなエラーは値を代入したときに初めて発生するものと思われていました。しかし、Swiftではこの値の代入が安全に管理されており、可能な限りコンパイル時にエラーを見つけたいという思想が反映されています。これは面白いですよね。
例えば、return 128
のようなクロージャーを実行する際も、これもコンパイルエラーになります。以前はランタイムエラーになるべき場面だと思いましたが、Swiftは賢くなっています。
Int8
で128を代入しようとするには、最終的にInt8
にキャストしないといけませんが、これではランタイムエラーになります。ランタイムに持ち越す計算が必要で、それを行わないと、Swiftはコンパイル時にエラーを出していることになります。
最大値と最小値の部分について話を続けます。例えば、Int8.max + 1
をオーバーフローさせると、これはコンパイルエラーとなります。なぜ22行目が動かなくて23行目が動くのかについては、詳細に見ていればすぐに分かると思いますが、こういった問題が思いがけないタイミングで起こるとバグの元になります。Int8
で扱うつもりだったら、オーバーフローしているということですね。
昔は2の補数表現が当たり前のように頭に入っていましたが、今はちょっと忘れてしまいました。Int8
を取るとInt32
は取れないですね。オーバーフローチェックをしない演算子で値を確認すると、128がマイナスの値になることが分かります。型が統一されていることで、思いがけない計算をせずに済むのは良いことです。 まず、Swiftの素晴らしい点の一つとして、通常の演算子を使う際にオーバーフローチェックが実行されることがあります。このため、例えば Int8.max
に +1
をした場合、それはオーバーフローするのでエラーとなります。この点を理解しておくことが重要です。
どちらの型に合わせるかという問題ですが、まず Int8.max
は Int32
であることを念頭に置いて、その上で Int32
に寄せるのか、 Int8
に寄せるのかを考える必要があります。 Int32
に寄せる場合は、例えばリテラルとして 4
点になります。 Int8
に寄せる場合も同様にリテラルとして 4
点です。このように、 Int8
と Int32
の演算が行われる際にはエラーが発生します。その時にどちらに型を寄せるかを明示的に考える必要があります。
このような配慮は煩わしい部分もありますが、C言語のような暗黙の型変換が行われる場合だと、エラーに気づくのが後回しになってしまいます。一方で、Swiftの場合、演算子がオーバーフローを許可せず、型が違えば計算を行わせないため、明示的に型変換を行う必要があります。結果として、最終的には目的を正確に達成することができます。
範囲の話に移りますが、Swiftでは境界値についても考慮する必要があります。例えば、範囲を Int.min
から Int.max
まで作成する場合、正しく動作します。これは、ローアーバウンドとアッパーバウンドを正しく取得しているからです。かつてはこれがうまくいかなかったこともあったと記憶していますが、現在では問題ありません。
クローズドレンジを使用する場合、例えば 0...10
というレンジを作成することができます。この場合、バウンドが Comparable
である必要があります。 Int
型であることを指定しなければいけません。もしこれを表現する際にイニシャライザーを正しく使用しないとエラーが発生することがあります。
レンジ型はローアーバウンドとアッパーバウンドを持ち、アッパーバウンド未満の形でデータを保持します。このため、特定の範囲を指定した際、ランタイムエラーが発生することがあります。例えば、レンジを Int.min
から Int.max
まで作成する際同様です。これに関してはデータの持ち方の問題なので、考慮する必要があります。 Swiftのレンジ型について話をします。例えば、整数型のレンジを 1...3
と定義すると、上限を含むクローズドレンジになります。この場合、上限は3です。一方で、レンジには「ラスト」と「エンド」という2つの概念があります。プログラミング言語では、ファースト(First)は最小の要素を意味し、ラスト(Last)は最後の要素、エンド(End)は最後の次の要素を指します。これは「番兵(Sentinel)」と呼ばれるもので、ループ処理を管理する際にエンドを使用することが一般的です。このため、レンジ型もラストではなくエンドをアッパーバウンドとして持つことが多いのです。
そのため、通常のレンジを 1..<4
と定義すると、開始値は1、エンドは4になります。このレンジにはコンストラクタがなく、クローズドレンジ以外の形式を使うことになります。例えば、クローズドレンジを使用することで上限を含む形式にすることも可能です。
Swift言語では、バージョン3の頃からレンジが多く使われるようになり、整数の全定義域を表現するのが難しくなってきました。例えば、Int.min
からInt.max
までのレンジを扱う際には様々なエッジケースがあるため、特に注意が必要です。オーバーフローや条件チェックでの問題など、境界値の取り扱いには細心の注意を払う必要があります。
次に、UInt
型について説明します。UInt
の最小値は常にゼロで、ここには特に問題はありません。しかし、型の汎用性を考慮する際には、バイナリーインテジャー型(BinaryInteger)の最小値や最大値がない場合があります。これはサイズが規定されていないためです。そうした場合には、具体的なビット数を持つ型を使用することで、最小値(min
)や最大値を取り扱うことができます。
これらの境界値を取り扱う際には、最小値を明確に取るか、ゼロを取るかという意味合いを正確に理解しておくことが重要です。一般的にはゼロを使うことが多いですが、意図的に最小値を使う場合もあります。このように、境界値の取り扱いには常に注意が必要です。 状況によるにしておきましょう。uintmax
も正確ですから見ておきますかね。何の変哲もない最大値が得られるわけですが、uintmax
の最大値とは違うというのも当たり前のところです。
時間も無くなってきたので、もうちょっとこの最大値最小値の余談をしておきますか。uintmax
がいくつかありますが、すぐ出せる人っているんですかね。頭の中で出せる人、覚えている人でもいい、いるのかな。自分は駄目です。
uint64
はどうですかね。「うまいですね」といいますが、64ビットだから正確な値を覚えている人は少なそうです。uint32max
はパッと言える人いますか。まあ覚えようと思えば覚えられる程度のものですね。自分も、たとえば uint32
は42億の半分みたいなイメージで覚えています。
そうですね、uint32
の最大値は約42億ですが、符号付き整数だからその半分だというような覚え方をすることもあります。int16
の最大値ならわかりますよね。32767とかそれくらいです。
昔のマックの色数ってなんだっけ。フルカラーと呼ばれるものがありましたね。24ビットカラーだと1677万色ですね。16ビットカラーが65536色(正確には65535ですが)です。ですから int16
の最大値は有名ですね。
24ビットはRGBを16ビットより大きく取って自然の色を出せるようにした、という話です。この最大値と最小値について、昔は筆算とかで計算するのが普通でしたかね。2の0乗
が1、2の1乗
が2、2の2乗
が4、そうやって倍々に値が増えていくだけです。
こうして24ビットまで計算していくのは大変なので、もう少し楽なビット変換の方法とかを知っていると便利ですね。でも、今となってはあまりそのケースは見かけないかもしれません。ただ、基本を知っておくと何かの時に便利ですし、電卓がなくても頭の中でぱっと出せると役立つ場面もあるかもしれません。
今日はこれぐらいにしますかね。何か言い残したこととかありますか?特にないようであれば、これで終わりにしましょう。お疲れ様でした。ありがとうございました。