オフィスアワーがそろそろ始まるよ!()

整数型

整数型の詳細と整数リテラルの書き方について説明します。

ビット幅が定義された整数型

ビット幅が明確に定義された整数型です。符号なしの整数型は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型はビット幅による値の制限がありません。そのため、非常に大きな数値を表現することができます。一方、ビット幅が定義されていないため、実行時に値を参照するためには、u32usizeといった他の整数型に変換する必要があります。

comptime_int型の変数は、コンパイル時に計算可能である必要があります。実行時に値が決まったり、値が変化する変数は、comptime_int型になることはできません。

comptime_int型は、他の整数型 (u32usize) に対して暗黙の型変換が働きます。

整数リテラル

整数リテラルとは、整数型に対してプログラム内に直接記述した数値を意味します。 整数リテラルは、デフォルトでは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);

ビット幅を指定した整数リテラルを利用する場合は、型名(整数リテラル)の構文を利用します。

examples/ch02-primitives/src/integers.zen:12:15

    const decimal_u32 = u32(12345678);  // 型をu32に指定
    ok(decimal_u32 == 12345678);
    ok(@typeOf(decimal_u32) == u32);
    ok(@sizeOf(@typeOf(decimal_u32)) == 4);

コンパイル時に計算できない値は、comptime_int型を取れないため、具体的な型を明示する必要があります。型は、リテラルに付けることもできますし、変数に付けることもできます。

examples/ch02-primitives/src/integers.zen:17:21

    // 実行時に変化する値は、`comptime_int`型になれない
    var v1 = 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);

整数リテラルを読みやすくするために、アンダースコア_を間に入れることができます。

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.mathminIntmaxIntを使用して、整数型の最小値と最大値を取得することができます。

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)から、u160x0100u8@truncateした結果、上位8ビットが切り捨てられ、u80x00に値が変化していることがわかります。

次に@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の計算を行うと、その結果の256u8では表現できません。

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, &v2);
    // `true`はオーバーフローが発生したことを意味する
    ok(result == true);
    // オーバーフロー発生時の値
    ok(v2 == 0);
}

@addWithOverflowは、@addWithOverflow(ターゲットの型, 値1, 値2, 結果を格納するポインタ) boolのように利用します。オーバーフローが発生した場合、戻り値はtrueが返ってきます。また、オーバーフローが発生した結果の値が、第4引数で与えたポインタに格納されます。

加算以外の演算オーバーフローに対する網羅的な対策については、4.2 演算子と[7.2 組込み関数]を参照して下さい。

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 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.