ざっくりZig - キャスト(明示的な型変換)
Zigでは変数や引数に型を設定しますが、状況によって型を変換したいときがあります。そのため、キャスト(Cast:明示的な型変換)を行う方法が用意されています。大きく分けると代入によるものとビルトイン関数によるものです。
型が明示されない変数
キャストについて紹介する前に、const
で型を明示せず値が代入された変数がどんな型なのかを確認しておきます(const
では型名を宣言することもできます)。整数型はcomptime_int
、浮動小数点型はcomptime_float
です。文字列は*const [長さ:0]u8
、タプルはstruct{要素の型名 = 値, ...}
となります。
const stdout = std.io.getStdOut().writer();
const i = -1;
const g = 1.0;
const s = "string";
const t = .{ -1, 1.0, "string" };
try stdout.print("@TypeOf(i) = {}\n@TypeOf(g) = {}\n@TypeOf(s) = {}\n@TypeOf(t) = {}\n", .{ @TypeOf(i), @TypeOf(g), @TypeOf(s), @TypeOf(t) });
@TypeOf(i) = comptime_int
@TypeOf(g) = comptime_float
@TypeOf(s) = *const [6:0]u8
@TypeOf(t) = struct{comptime comptime_int = -1, comptime comptime_float = 1, comptime *const [6:0]u8 = "string"}
なお、キャストされる値がconst
で宣言されている場合は@constCast(キャスト処理)
を実行しなければならない場合があります。
数値型のキャスト
ある値が型が宣言された変数に格納されているとき、それを別の型が宣言された変数に代入することでキャストが可能な場合があります。
整数型
整数型はu8
からu16
のように種別(u
かi
)が同じでビット幅を広げるだけであれば代入によりキャストできます。浮動小数点型もf16
からf32
などにキャストする場合は同じです。代入する際の式に含まれるのが定数とcomptime_int
やcomptime_float
のみのときは、計算の結果が代入されます。
var j1: u8 = 256 + i; // i = -1 (comptime_int) 256は代入できないが式の結果の255は代入できる
var j2: u16 = j1; // ビット幅が広がる(ビルトイン関数@intCastも可)
var g1: f16 = 12345.6789 - g; // g = 1.0 (comptime_float)
var g2: f32 = g1; // ビット幅が広がる
整数型のビット幅を狭める場合はビルトイン関数の@trucate
を実行します。これにより上位ビットが切り捨てられます。
var jp1: u16 = 0xff00; // 65280
var jp2: u8 = @truncate(jp1);
try stdout.print("jp1 = {}, jp2 = {}\n", .{ jp1, jp2 });
jp1 = 0x00ff; // 255
jp2 = @truncate(jp1);
try stdout.print("jp1 = {}, jp2 = {}\n", .{ jp1, jp2 });
var jm1: i16 = -0x8000; // -32768
var jm2: i8 = @truncate(jm1);
try stdout.print("jm1 = {}, jm2 = {}\n", .{ jm1, jm2 });
jm1 = -0x0080; // -128
jm2 = @truncate(jm1);
try stdout.print("jm1 = {}, jm2 = {}\n", .{ jm1, jm2 });
jp1 = 65280, jp2 = 0
jp1 = 255, jp2 = 255
jm1 = -32768, jm2 = 0
jm1 = -128, jm2 = -128
浮動小数点型
浮動小数点型では、同じくビルトイン関数の@floatCast
を実行できます。こちらはビット幅を問いません。ただし、ビット幅を狭めたときの結果がinf
(infinity:無限、あるいは範囲外)となることがあります。
var g1: f16 = std.math.floatMax(f16); // f16で扱える最大の数
var g2: f32 = g1;
try stdout.print("g1 = {}, g2 = {}\n", .{ g1, g2 });
g2 = std.math.floatMax(f32); // f32で扱える最大の数
g1 = @floatCast(g2);
try stdout.print("g1 = {}, g2 = {}\n", .{ g1, g2 });
g1 = std.math.floatMin(f16); // f16で扱える最小の数
g2 = @floatCast(g1);
try stdout.print("g1 = {}, g2 = {}\n", .{ g1, g2 });
g2 = std.math.floatMin(f32); // f32で扱える最小の数
g1 = @floatCast(g2);
try stdout.print("g1 = {}, g2 = {}\n", .{ g1, g2 });
g1 = 6.55e4, g2 = 6.5504e4
g1 = inf, g2 = 3.4028235e38
g1 = 6.104e-5, g2 = 6.1035156e-5
g1 = 0e0, g2 = 1.1754944e-38
整数型と浮動小数点型とでキャスト
整数型と浮動小数点型とでキャストを行う場合は以下のビルトイン関数があります。
- 整数型 → 浮動小数点型 :
@floatFromInt
- 浮動小数点型 → 整数型 :
@intFromFloat
ただし整数型と浮動小数点型とでは同じビット幅でも表せる値の範囲が異なるため、特に浮動小数点型から整数型にキャストするときにビット幅を超えてしまってpanic: integer part of floating point value out of bounds
というエラーになることがあります(デバッグモード-O Debug
のとき)。
var jn1: i16 = std.math.maxInt(i16);
g1 = @floatFromInt(j1);
try stdout.print("jn1 = {}, g1 = {}\n", .{ jn1, g1 });
g2 = std.math.floatMax(f32);
var jn2: i32 = @intFromFloat(g2); // キャスト不可
try stdout.print("jn2 = {}, g2 = {}\n", .{ jn2, g2 });
jn1 = 32767, g1 = 2.55e2
thread .... panic: integer part of floating point value out of bounds
各ビットの状態を変えずにf32
からi32
あるいはi32
からu32
などのキャストを行う場合は@bitCast
関数を実行します。
g2 = std.math.floatMax(f32);
var jn2: i32 = @bitCast(g2);
try stdout.print("jn2 = {}, g2 = {}\n", .{ jn2, g2 });
jn2 = -jn2;
var jn3: u32 = @bitCast(jn2);
try stdout.print("jn3 = {}, jn2 = {}\n", .{ jn3, jn2 });
jn2 = 2139095039, g2 = 3.4028235e38
jn3 = 2155872257, jn2 = -2139095039
配列のキャスト
配列([N]T
)をスライス([]T
)や要素のポインタ([*]T
)にキャストするには、&配列名
を代入します。ただし配列がconst
で定義されるときは代入される型にもconst
が必要です。ただしスライスは配列名[始端..終端+1]
や配列名[0..]
でも取得できます。
var varr: [3]u8 = .{ 1, 2, 3 }; // varで定義された配列
var vsarr: []u8 = ↕ // varr[0..]も可
var vparr: [*]u8 = ↕
const carr: [3]u8 = .{ 1, 2, 3 }; // constで定義された配列
var csarr: []const u8 = &carr; // carr[0..]も可
var cparr: [*]const u8 = &carr;
また、タプルの要素の型がすべて同じであれば、同じ要素数の配列にキャストできます。また、i8
からu8
のように符号の有無が変わる場合は、同じ値が代入可能な場合のみ有効です。
const tplu8 = .{ 1, 2, 3 };
const arru8: [3]u8 = tplu8;
const vtp: struct { i8, i8, i8 } = .{ 1, 2, 3 }; // .{ -1, -2, -3 }などはキャスト不可
const vau8: [3]u8 = vtp;
ポインタのキャスト
ポインタのキャストは@ptrCast
を実行します。ただし、キャスト後の型によってはデータの一部を失う場合があるため、キャスト後の型と値をきちんと確認する必要があります。
const pu8: *u8 = @constCast(@ptrCast(&arru8));
try stdout.print("tplu8 = {any}, pu8 = {}\n", .{tplu8, pu8.*});
tplu8 = { 1, 2, 3 }, pu8 = 1
キャスト後の型を明示するとき
error: @intCast must have a known result type
のようなエラーが出た時など、キャスト後の型を明示するときは@as(型名, キャスト処理)
を実行します。
var sum: u8 = 0;
for(1..11) |v| {
sum += @as(u8, @intCast(v));
// sum += v; は不可
}
// sum = 55
まとめ
- キャスト(Cast:明示的な型変換)には代入によるものとビルトイン関数によるものとがある
-
const
で変数の型が明示されずに値が代入されたとき、整数型はcomptime_int
、浮動小数点型はcomptime_float
です。文字列は*const [長さ:0]u8
、タプルはstruct{要素の型名, ...}
となる - キャストされる値が
const
で宣言されている場合は@constCast(キャスト処理)
の実行が必要な場合がある - 整数型や浮動小数点型はビット幅を広げるなら代入でキャストできる
- 整数型のビット幅を狭める(切り捨てる)場合はビルトイン関数の
@trucate
を実行する(上位ビット切り捨て) - 浮動小数点型ではビット幅を問わずビルトイン関数の
@floatCast
を実行できるが、ビット幅を狭めたときの結果がinf
(infinity:無限、あるいは範囲外)となることがある - 整数型と浮動小数点型とでキャストを行う場合は
@floatFromInt
あるいは@intFromFloat
を実行する - 浮動小数点型から整数型にキャストするときにビット幅を超えてエラーになることがある(デバッグモードのとき)
- 各ビットの状態を変えずにキャストするときは
@bitCast
関数を実行する - 配列(
[N]T
)をスライス([]T
)や要素のポインタ([*]T
)にキャストするには、&配列名
を代入するればよいが、配列がconst
で定義されるときは代入される型にもconst
が必要 - スライスは
配列名[始端..終端+1]
や配列名[0..]
でも取得できる - タプルの要素の型がすべて同じであれば、同じ要素数の配列にキャストできる
- ポインタのキャストは
@ptrCast
を実行するが、データを失う場合があるので確認が必要 - キャスト後の型を明示するときは
@as(型名, キャスト処理)
を実行する
< structは構造型、型はtype、typeは値
structの組込(usingnamespace) >
ざっくりZig 一覧
Discussion