課題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