整数型の詳細と整数リテラルの書き方について説明します。
ビット幅が明確に定義された整数型です。符号なしの整数型はuを、符号ありの整数型はiを、先頭に付けてビット幅を続けます。
代表的なビット幅を持つ整数型と、その値の範囲は、次の通りです。
| 型名 | ビット幅 | 最小値 | 最大値 | 
|---|---|---|---|
| i8 | 8 | -128 | 127 | 
| u8 | 8 | 0 | 255 | 
| i16 | 16 | -32,768 | 32,767 | 
| u16 | 16 | 0 | 65535 | 
| i32 | 32 | -2,147,483,648 | 2,147,483,647 | 
| u32 | 32 | 0 | 4,294,967,295 | 
| i64 | 64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | 
| u64 | 64 | 0 | 18,446,744,073,709,551,615 | 
| i128 | 128 | -(2^127) | 2^127-1 | 
| u128 | 128 | 0 | 2^128-1 | 
上述の型に限らず、Zenでは任意のビット幅を持つ整数型が利用できます。例えば、u1は符号なし1ビットの整数、i5は符号あり5ビットの整数を意味します。u1は0か1、i5は-16から15の値だけを取ることができます。
examples/ch02-primitives/src/arbitary_bit_width.zen
const std = @import("std");
const ok = std.testing.ok;
test "arbitary bit width" {
    var v1: u1 = 0;
    ok(v1 == 0);
    v1 = 1;
    ok(v1 == 1);
    // Compile error: integer value 2 cannot be implicitly casted to type 'u1'
    // v1 = 2;
    var v2: i5 = -16;
    ok(v2 == -16);
    v2 = 15;
    ok(v2 == 15);
    // Compile error: integer value -17 cannot be implicitly casted to type 'i5'
    // v2 = -17;
}
ノート: 整数型が取れる最大ビット幅は65535ビットです。
Zenでは明確にビット幅が定義された整数型の他、ターゲット環境のアドレスビット幅に依存する整数型があります。
| 型名 | 32ビット環境 | 64ビット環境 | 
|---|---|---|
| isize | 32ビット | 64ビット | 
| usize | 32ビット | 64ビット | 
これら2つの整数型は、ターゲットとする環境のメモリアドレスのビット幅に依存して、ビット幅が変わります。isizeは符号あり、usizeは符号なしです。
usizeは配列のインデックスなど、幅広く利用されます。
Zenには、コンパイル時のみ利用できる整数型comptime_intがあります。
comptime_int型はビット幅による値の制限がありません。そのため、非常に大きな数値を表現することができます。一方、ビット幅が定義されていないため、実行時に値を参照するためには、u32やusizeといった他の整数型に変換する必要があります。
comptime_int型の変数は、コンパイル時に計算可能である必要があります。実行時に値が決まったり、値が変化する変数は、comptime_int型になることはできません。
comptime_int型は、他の整数型 (u32やusize) に対して暗黙の型変換が働きます。
整数リテラルとは、整数型に対してプログラム内に直接記述した数値を意味します。
整数リテラルは、デフォルトではcomptime_int型になります。
examples/ch02-primitives/src/integers.zen:1:7
const std = @import("std");
const ok = std.testing.ok;
test "integer literal" {
    // 整数リテラルはcomptime_int型
    const decimal = 12345678;
    ok(@TypeOf(decimal) == comptime_int);
comptime_int型は、ビット幅の情報を持ちません。そのため、コンパイル時に計算可能であれば、どのような範囲の値も取ることができます。
examples/ch02-primitives/src/integers.zen:9:10
    const big_int = 2^200;  // u128の最大値より大きい数値もOK
    ok(big_int == 2^200);
ビット幅を指定した整数リテラルを利用する場合は、@to(型名, 整数リテラル)の構文を利用します。
examples/ch02-primitives/src/integers.zen:12:15
    const decimal_u32 = @to(u32, 12345678); // 型をu32に指定
    ok(decimal_u32 == 12345678);
    ok(@TypeOf(decimal_u32) == u32);
    ok(@sizeOf(@TypeOf(decimal_u32)) == 4);
コンパイル時に計算できない値は、comptime_int型を取れないため、具体的な型を明示する必要があります。型は、リテラルに付けることもできますし、変数に付けることもできます。リテラルに付ける場合は、 @to による型変換が必要です。
examples/ch02-primitives/src/integers.zen:17:21
    // 実行時に変化する値は、`comptime_int`型になれない
    var v1 = @to(u32, 1);
    var v2: u32 = 1;
    // Compile error
    // var v1 = 1;
整数リテラルの先頭に、0x, 0o, 0bを付けることで、それぞれ16進数、8進数、2進数で整数リテラルの値を書くことができます。
examples/ch02-primitives/src/integers.zen:23:36
    // 16進数リテラル
    const hex = 0xbc614e;
    ok(hex == 12345678);
    // A-Fは大文字も可
    const HEX = 0xBC614E;
    ok(HEX == 12345678);
    // 8進数リテラル
    const octal = 0o57060516;
    ok(octal == 12345678);
    // 2進数リテラル
    const binary = 0b101111000110000101001110;
    ok(binary == 12345678);
10進数の整数リテラルを 0 から始めることはできません。
C 言語等では 0177 のような整数リテラルは 8 進数と解釈されますが、
Zen 言語ではこのような書き方はエラーとなります。
Zen 言語の古いバージョンでは、
0177は 10 進数の 117 と解釈されていました。 8 進数との混同を避けるため、現在ではこのような表記は禁止されています。
整数リテラルを読みやすくするために、アンダースコア_を間に入れることができます。
examples/ch02-primitives/src/integers.zen:38:40
    // `_`で可読性をあげることもできる
    const underscore = 1234_5678;
    ok(underscore == 12345678);
ASCII文字に対応する文字コードを得られます。型は、comptime_intです。
examples/ch02-primitives/src/integers.zen:43:47
test "ascii code literal" {
    const a = 'A';
    ok(a == 65);
    ok(@TypeOf(a) == comptime_int);
}
Zenの標準ライブラリstd.mathのminIntとmaxIntを使用して、整数型の最小値と最大値を取得することができます。
examples/ch02-primitives/src/integers.zen:49:56
test "max min of int" {
    const math = std.math;
    ok(math.minInt(u8) == 0);
    ok(math.maxInt(u8) == 255);
    ok(math.minInt(i5) == -16);
    ok(math.maxInt(i5) == 15);
}
Zenでは、安全な場合のみ、整数型同士の暗黙の型変換が行われます。わかりやすい例は、より大きな整数型への変換です。
examples/ch02-primitives/src/integers.zen:58:67
test "implicit cast" {
    // より大きな整数型への暗黙の型変換
    const v1: u8 = 255;
    const v2: u16 = v1;
    ok(v2 == 255);
    // より大きな符号あり整数型への暗黙の型変換
    const v3: u8 = 255;
    const v4: i16 = v3;
    ok(v4 == 255);
コンパイル時に安全性が保証される場合、より小さな整数型への変換も可能です。
examples/ch02-primitives/src/integers.zen:69:73
    // より小さな整数型への変換だが、コンパイル時に値が`u8`に収まることが
    // わかるため、暗黙の型変換可能
    const v5: u16 = 255;
    const v6: u8 = v5;
    ok(v6 == 255);
下のコードは、v7が実行時変数となるため、コンパイルエラーになります。
    var v7: u16 = 255;
    var v8: u8 = v7;
安全性を保証できない整数型の型変換を行うには、2つの方法があります。組込み関数の@truncateを使用する方法と、組込み関数の@intCastを使用する方法、です。
ノート:
@から始まる関数はコンパイラ組込みの特別な関数です。詳しくは7章 組込み関数で説明します。
2つの方法の違いは、@truncateが単純に上位ビットを切り捨てるのに対して、@intCastは、安全性保護付き未定義動作が発生する可能性がある点です。
まずは@truncateを使う方法を説明します。
examples/ch02-primitives/src/integers.zen:76:85
test "truncate" {
    var v1: u16 = 255;
    var v2: u8 = @truncate(u8, v1);
    ok(v2 == 255);
    var v3: u16 = 256;
    var v4: u8 = @truncate(u8, v3);
    // 0x0100 => 0x00
    ok(v4 == 0);
}
@truncateは、@truncate(ターゲットの型、値)のように利用します。2つめのok(v4 == 0)から、u16の0x0100をu8に@truncateした結果、上位8ビットが切り捨てられ、u8の0x00に値が変化していることがわかります。
次に@intCastを使う方法です。
examples/ch02-primitives/src/integers.zen:87:91
test "intCast" {
    var v1: u16 = 255;
    var v2: u8 = @intCast(u8, v1);
    ok(v2 == 255);
}
255は安全にu8に変換できるため、上のコードは@truncateと同様に動作します。一方、次のコードは、実行時に安全性保護付き未定義動作が発生し、プログラムが停止します。
    var v3: u16 = 256;
    var v4: u8 = @intCast(u8, v3);
@intCastでは、変換する値が変換先の型の範囲内に収まっているかどうか、を実行時に検査します。検査の結果、値が型の範囲内であれば変換が行われ、そうでなければ安全性保護付き未定義動作が発生します。
整数型同士を計算すると、計算結果が整数型の範囲を超え、オーバーフローが発生する場合があります。例えば、u8では0から255までの値を表現できますが、255+1の計算を行うと、その結果の256はu8では表現できません。
Zenでは、このようなオーバーフローが発生した場合の動作を選択することができます。
まず、次のコードは実行時に安全性保護付き未定義動作が発生し、プログラムが停止します。
    var v1: u8 = std.math.maxInt(u8);
    var v2: u8 = v1 + 1;  // オーバーフローで安全性保護付き未定義動作発生
このような安全性保護付き未定義動作を防ぐためには、主に2つの対策があります。ラップアラウンド演算子を使う方法と、オーバーフロー検出機能付き組込み関数を使う方法です。
まず、ラップアラウンド演算子を使用する方法です。ラップアラウンドとは、その整数型で表現可能な数値の範囲内で、値を循環させることです。加算であれば、+演算子の代わりに+%演算子を使用します。
examples/ch02-primitives/src/integers.zen:93:97
test "wraparound" {
    var v1: u8 = std.math.maxInt(u8);
    var v2: u8 = v1 +% 1;
    ok(v2 == 0);
}
上の例では、u8の最大値に1をラップアラウンド加算した結果は、0になっています。これは、u8で表現可能な255を越えた次の値が、0に循環していることを意味します。
演算の結果、ラップアラウンドすれば良い場合は、こちらのラップアラウンド演算子を使う方法を採用します。
もう1つの方法は、オーバーフロー検出機能付き組込み関数を使う方法です。加算であれば、@addWithOverflowを使用します。
examples/ch02-primitives/src/integers.zen:99:107
test "withOverflow" {
    var v1: u8 = std.math.maxInt(u8);
    var v2: u8 = undefined;
    const result = @addWithOverflow(u8, v1, 1, &mut v2);
    // `true`はオーバーフローが発生したことを意味する
    ok(result == true);
    // オーバーフロー発生時の値
    ok(v2 == 0);
}
@addWithOverflowは、@addWithOverflow(ターゲットの型, 値1, 値2, 結果を格納するポインタ) boolのように利用します。オーバーフローが発生した場合、戻り値はtrueが返ってきます。また、オーバーフローが発生した結果の値が、第4引数で与えたポインタに格納されます。
      ☰ 人の生きた証は永遠に残るよう ☰
      Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
      Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.