const std = @import("std"); pub fn Int(comptime int: std.builtin.Type.Int) type { return @Type(.{.Int = int}); } pub fn clamp(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lower, upper) { return @max(lower, @min(upper, value)); } pub fn checked_add(a: anytype, b: anytype) ?@TypeOf(a + b) { const result = @addWithOverflow(a, b); return if (result.@"1" == 0) result.@"0" else null; } pub fn checked_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) ?@Type(.{.Int = dest_int}) { const dest_min = min_int(dest_int); const dest_max = max_int(dest_int); if (value < dest_min or value > dest_max) { return null; } return @intCast(value); } pub fn checked_mul(a: anytype, b: anytype) ?@TypeOf(a * b) { const result = @mulWithOverflow(a, b); return if (result.@"1" == 0) result.@"0" else null; } pub fn checked_sub(a: anytype, b: anytype) ?@TypeOf(a - b) { const result = @subWithOverflow(a, b); return if (result.@"1" == 0) result.@"0" else null; } pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int(dest_int) { const Value = @TypeOf(value); return switch (@typeInfo(Value)) { .Int => |int| switch (int.signedness) { .signed => @intCast(clamp(value, min_int(dest_int), max_int(dest_int))), .unsigned => @intCast(@min(value, max_int(dest_int))), }, .Float => @intFromFloat(clamp(value, min_int(dest_int), max_int(dest_int))), else => @compileError("`" ++ @typeName(Value) ++ "` cannot be cast to an int"), }; } pub fn max_int(comptime int: std.builtin.Type.Int) comptime_int { const bit_count = int.bits; if (bit_count == 0) return 0; return (1 << (bit_count - @intFromBool(int.signedness == .signed))) - 1; } pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int { if (int.signedness == .unsigned) { return 0; } const bit_count = int.bits; if (bit_count == 0) return 0; return -(1 << (bit_count - 1)); }