課題4: 型システムの実践
マンダトリー要件 (80点)
Part 1: 型変換ユーティリティ (20点)
さまざまな型変換を安全に行うユーティリティ関数を実装してください。
ファイル: part1/type_conversion.zig
const std = @import("std");
// TODO: 安全な整数キャスト
// 範囲外の場合はnullを返す
fn safeCast(comptime T: type, value: i64) ?T {
// 実装
}
// TODO: 文字列を整数に変換
fn parseInteger(comptime T: type, str: []const u8) !T {
// 実装
// ヒント: std.fmt.parseInt を使用
}
// TODO: 整数を文字列に変換
fn integerToString(allocator: std.mem.Allocator, value: i64) ![]u8 {
// 実装
// ヒント: std.fmt.allocPrint を使用
}
// TODO: 浮動小数点を指定精度の文字列に変換
fn floatToString(allocator: std.mem.Allocator, value: f64, precision: u8) ![]u8 {
// 実装
}
pub fn main() !void {
const allocator = std.heap.page_allocator;
// safeCast のテスト
std.debug.print("=== Safe Cast ===\n", .{});
if (safeCast(i8, 100)) |val| {
std.debug.print("100 as i8: {}\n", .{val});
}
if (safeCast(i8, 200)) |val| {
std.debug.print("200 as i8: {}\n", .{val});
} else {
std.debug.print("200 cannot be cast to i8\n", .{});
}
// parseInteger のテスト
std.debug.print("\n=== Parse Integer ===\n", .{});
const parsed = try parseInteger(i32, "12345");
std.debug.print("Parsed: {}\n", .{parsed});
// integerToString のテスト
std.debug.print("\n=== Integer to String ===\n", .{});
const str = try integerToString(allocator, 42);
defer allocator.free(str);
std.debug.print("String: {s}\n", .{str});
// floatToString のテスト
std.debug.print("\n=== Float to String ===\n", .{});
const float_str = try floatToString(allocator, 3.14159, 2);
defer allocator.free(float_str);
std.debug.print("Float: {s}\n", .{float_str});
}
Part 2: オプショナル型の活用 (20点)
オプショナル型を使ったユーティリティ関数を実装してください。
ファイル: part2/optional_utils.zig
const std = @import("std");
// TODO: 配列から値を検索し、見つかったインデックスを返す
fn findIndex(comptime T: type, array: []const T, target: T) ?usize {
// 実装
}
// TODO: 配列の最大値を返す(空の配列の場合はnull)
fn maxValue(comptime T: type, array: []const T) ?T {
// 実装
}
// TODO: 配列の最小値を返す(空の配列の場合はnull)
fn minValue(comptime T: type, array: []const T) ?T {
// 実装
}
// TODO: 2つのオプショナル値を加算(どちらかがnullの場合はnull)
fn addOptional(a: ?i32, b: ?i32) ?i32 {
// 実装
}
// TODO: オプショナル値をマップ(値があれば関数を適用)
fn mapOptional(comptime T: type, comptime R: type, value: ?T, func: fn(T) R) ?R {
// 実装
}
pub fn main() void {
std.debug.print("=== Find Index ===\n", .{});
const numbers = [_]i32{ 1, 2, 3, 4, 5 };
if (findIndex(i32, &numbers, 3)) |index| {
std.debug.print("Found 3 at index: {}\n", .{index});
} else {
std.debug.print("Not found\n", .{});
}
std.debug.print("\n=== Max/Min Value ===\n", .{});
if (maxValue(i32, &numbers)) |max| {
std.debug.print("Max: {}\n", .{max});
}
if (minValue(i32, &numbers)) |min| {
std.debug.print("Min: {}\n", .{min});
}
std.debug.print("\n=== Add Optional ===\n", .{});
const sum1 = addOptional(10, 20);
const sum2 = addOptional(10, null);
std.debug.print("10 + 20 = {?}\n", .{sum1});
std.debug.print("10 + null = {?}\n", .{sum2});
std.debug.print("\n=== Map Optional ===\n", .{});
const double = struct {
fn func(x: i32) i32 {
return x * 2;
}
}.func;
const result = mapOptional(i32, i32, 21, double);
std.debug.print("Map 21 * 2 = {?}\n", .{result});
}
Part 3: 整数オーバーフロー処理 (20点)
整数オーバーフローを適切に処理する関数を実装してください。
ファイル: part3/overflow.zig
const std = @import("std");
// TODO: ラップアラウンド加算
fn wrappingAdd(a: u8, b: u8) u8 {
// 実装
}
// TODO: 飽和加算
fn saturatingAdd(a: u8, b: u8) u8 {
// 実装
}
// TODO: チェック付き加算(オーバーフローを検出)
fn checkedAdd(a: u8, b: u8) !u8 {
// 実装
// ヒント: @addWithOverflow を使用
}
// TODO: ラップアラウンド乗算
fn wrappingMul(a: u8, b: u8) u8 {
// 実装
}
// TODO: 飽和乗算
fn saturatingMul(a: u8, b: u8) u8 {
// 実装
}
const OverflowError = error{
Overflow,
};
pub fn main() !void {
std.debug.print("=== Wrapping Operations ===\n", .{});
const wrap_sum = wrappingAdd(255, 10);
const wrap_prod = wrappingMul(20, 20);
std.debug.print("255 +%% 10 = {}\n", .{wrap_sum});
std.debug.print("20 *%% 20 = {}\n", .{wrap_prod});
std.debug.print("\n=== Saturating Operations ===\n", .{});
const sat_sum = saturatingAdd(255, 10);
const sat_prod = saturatingMul(20, 20);
std.debug.print("255 +| 10 = {}\n", .{sat_sum});
std.debug.print("20 *| 20 = {}\n", .{sat_prod});
std.debug.print("\n=== Checked Operations ===\n", .{});
if (checkedAdd(100, 50)) |result| {
std.debug.print("100 + 50 = {}\n", .{result});
} else |err| {
std.debug.print("Overflow: {}\n", .{err});
}
if (checkedAdd(255, 10)) |result| {
std.debug.print("255 + 10 = {}\n", .{result});
} else |err| {
std.debug.print("Overflow: {}\n", .{err});
}
}
Part 4: 浮動小数点計算 (20点)
浮動小数点数を使った計算ユーティリティを実装してください。
ファイル: part4/float_utils.zig
const std = @import("std");
// TODO: 2点間の距離を計算
fn distance(x1: f64, y1: f64, x2: f64, y2: f64) f64 {
// 実装
// ヒント: sqrt((x2-x1)^2 + (y2-y1)^2)
}
// TODO: 浮動小数点数を指定桁数で四捨五入
fn roundTo(value: f64, decimals: u32) f64 {
// 実装
}
// TODO: 2つの浮動小数点数が等しいか(許容誤差付き)
fn almostEqual(a: f64, b: f64, epsilon: f64) bool {
// 実装
}
// TODO: 線形補間
fn lerp(a: f64, b: f64, t: f64) f64 {
// 実装
// a + (b - a) * t
}
// TODO: 値を範囲内にクランプ
fn clamp(value: f64, min: f64, max: f64) f64 {
// 実装
}
pub fn main() void {
std.debug.print("=== Distance ===\n", .{});
const dist = distance(0.0, 0.0, 3.0, 4.0);
std.debug.print("Distance: {d:.2}\n", .{dist});
std.debug.print("\n=== Round To ===\n", .{});
const rounded = roundTo(3.14159, 2);
std.debug.print("Rounded: {d:.2}\n", .{rounded});
std.debug.print("\n=== Almost Equal ===\n", .{});
const eq1 = almostEqual(1.0, 1.0000001, 0.00001);
const eq2 = almostEqual(1.0, 1.1, 0.00001);
std.debug.print("1.0 ~= 1.0000001: {}\n", .{eq1});
std.debug.print("1.0 ~= 1.1: {}\n", .{eq2});
std.debug.print("\n=== Lerp ===\n", .{});
const interpolated = lerp(0.0, 100.0, 0.5);
std.debug.print("Lerp(0, 100, 0.5) = {d:.1}\n", .{interpolated});
std.debug.print("\n=== Clamp ===\n", .{});
const clamped1 = clamp(50.0, 0.0, 100.0);
const clamped2 = clamp(150.0, 0.0, 100.0);
std.debug.print("Clamp(50, 0, 100) = {d:.1}\n", .{clamped1});
std.debug.print("Clamp(150, 0, 100) = {d:.1}\n", .{clamped2});
}
ボーナス課題 (20点)
Bonus 1: 固定小数点演算 (10点)
整数を使った固定小数点演算を実装してください。
ファイル: bonus1/fixed_point.zig
const std = @import("std");
// 16.16 固定小数点数(16ビット整数部、16ビット小数部)
const Fixed16 = struct {
value: i32,
const FRACTIONAL_BITS = 16;
const ONE = 1 << FRACTIONAL_BITS;
// TODO: 整数から固定小数点へ変換
pub fn fromInt(i: i32) Fixed16 {
// 実装
}
// TODO: 浮動小数点から固定小数点へ変換
pub fn fromFloat(f: f64) Fixed16 {
// 実装
}
// TODO: 固定小数点から浮動小数点へ変換
pub fn toFloat(self: Fixed16) f64 {
// 実装
}
// TODO: 加算
pub fn add(self: Fixed16, other: Fixed16) Fixed16 {
// 実装
}
// TODO: 減算
pub fn sub(self: Fixed16, other: Fixed16) Fixed16 {
// 実装
}
// TODO: 乗算
pub fn mul(self: Fixed16, other: Fixed16) Fixed16 {
// 実装
}
// TODO: 除算
pub fn div(self: Fixed16, other: Fixed16) Fixed16 {
// 実装
}
};
pub fn main() void {
const a = Fixed16.fromFloat(3.5);
const b = Fixed16.fromFloat(2.0);
const sum = a.add(b);
const diff = a.sub(b);
const prod = a.mul(b);
const quot = a.div(b);
std.debug.print("3.5 + 2.0 = {d:.2}\n", .{sum.toFloat()});
std.debug.print("3.5 - 2.0 = {d:.2}\n", .{diff.toFloat()});
std.debug.print("3.5 * 2.0 = {d:.2}\n", .{prod.toFloat()});
std.debug.print("3.5 / 2.0 = {d:.2}\n", .{quot.toFloat()});
}
Bonus 2: 複素数演算 (10点)
複素数の演算を実装してください。
ファイル: bonus2/complex.zig
const std = @import("std");
const Complex = struct {
real: f64,
imag: f64,
// TODO: 加算
pub fn add(self: Complex, other: Complex) Complex {
// 実装
}
// TODO: 減算
pub fn sub(self: Complex, other: Complex) Complex {
// 実装
}
// TODO: 乗算
pub fn mul(self: Complex, other: Complex) Complex {
// (a + bi)(c + di) = (ac - bd) + (ad + bc)i
// 実装
}
// TODO: 除算
pub fn div(self: Complex, other: Complex) Complex {
// 実装
}
// TODO: 絶対値
pub fn abs(self: Complex) f64 {
// sqrt(real^2 + imag^2)
// 実装
}
// TODO: 共役複素数
pub fn conjugate(self: Complex) Complex {
// 実装
}
// TODO: 表示
pub fn print(self: Complex) void {
if (self.imag >= 0) {
std.debug.print("{d:.2} + {d:.2}i", .{self.real, self.imag});
} else {
std.debug.print("{d:.2} - {d:.2}i", .{self.real, -self.imag});
}
}
};
pub fn main() void {
const a = Complex{ .real = 3.0, .imag = 4.0 };
const b = Complex{ .real = 1.0, .imag = 2.0 };
std.debug.print("a = ", .{});
a.print();
std.debug.print("\nb = ", .{});
b.print();
std.debug.print("\n\n", .{});
const sum = a.add(b);
std.debug.print("a + b = ", .{});
sum.print();
std.debug.print("\n", .{});
const prod = a.mul(b);
std.debug.print("a * b = ", .{});
prod.print();
std.debug.print("\n", .{});
std.debug.print("|a| = {d:.2}\n", .{a.abs()});
}
評価基準
| 項目 | 配点 |
|---|---|
| Part 1: 型変換ユーティリティ | 20点 |
| Part 2: オプショナル型の活用 | 20点 |
| Part 3: 整数オーバーフロー処理 | 20点 |
| Part 4: 浮動小数点計算 | 20点 |
| **マンダトリー合計** | **80点** |
| Bonus 1: 固定小数点演算 | 10点 |
| Bonus 2: 複素数演算 | 10点 |
| **ボーナス合計** | **20点** |
合格基準
- マンダトリー: 64点以上で合格(80点満点の80%)
- ボーナス: 追加評価(最終成績の加算)
提出方法
exercise04/
├── part1/
│ └── type_conversion.zig
├── part2/
│ └── optional_utils.zig
├── part3/
│ └── overflow.zig
├── part4/
│ └── float_utils.zig
├── bonus1/
│ └── fixed_point.zig
└── bonus2/
└── complex.zig
テスト実行
# マンダトリー
zig run part1/type_conversion.zig
zig run part2/optional_utils.zig
zig run part3/overflow.zig
zig run part4/float_utils.zig
# ボーナス
zig run bonus1/fixed_point.zig
zig run bonus2/complex.zig
ヒント
const value: i32 = 100;
const casted: i16 = @intCast(value);
- オプショナルのアンラップ:
if (optional_value) |value| {
// valueを使用
}
- オーバーフロー検出:
const result = @addWithOverflow(u8, a, b);
const sum = result[0]; // 結果
const overflow = result[1]; // オーバーフローフラグ
- 浮動小数点演算:
const sqrt_val = std.math.sqrt(value);
const pow_val = std.math.pow(f64, base, exp);
参考資料
- Zig Language Reference - Types: https://ziglang.org/documentation/master/#Types
- Zig Standard Library - Math: https://ziglang.org/documentation/master/std/#std.math
- Zig Standard Library - Fmt: https://ziglang.org/documentation/master/std/#std.fmt