Optimized Quantity.to

This commit is contained in:
AdrienBouvais 2026-04-21 11:02:10 +02:00
parent 305d113752
commit 19de4e1dd2

View File

@ -51,7 +51,7 @@ pub fn Quantity(T: type, d: Dimensions, s: Scales) type {
scales.min(@TypeOf(rhs).scales), scales.min(@TypeOf(rhs).scales),
) { ) {
if (comptime !dims.eql(@TypeOf(rhs).dims)) if (comptime !dims.eql(@TypeOf(rhs).dims))
@compileError("Dimension mismatch in add"); @compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
const TargetType = Quantity(T, dims, scales.min(@TypeOf(rhs).scales)); const TargetType = Quantity(T, dims, scales.min(@TypeOf(rhs).scales));
const lhs_converted = self.to(TargetType); const lhs_converted = self.to(TargetType);
@ -66,7 +66,7 @@ pub fn Quantity(T: type, d: Dimensions, s: Scales) type {
scales.min(@TypeOf(rhs).scales), scales.min(@TypeOf(rhs).scales),
) { ) {
if (comptime !dims.eql(@TypeOf(rhs).dims)) if (comptime !dims.eql(@TypeOf(rhs).dims))
@compileError("Dimension mismatch in sub"); @compileError("Dimension mismatch in sub: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
const TargetType = Quantity(T, dims, scales.min(@TypeOf(rhs).scales)); const TargetType = Quantity(T, dims, scales.min(@TypeOf(rhs).scales));
const lhs_converted = self.to(TargetType); const lhs_converted = self.to(TargetType);
@ -101,23 +101,45 @@ pub fn Quantity(T: type, d: Dimensions, s: Scales) type {
pub fn to(self: Self, comptime Dest: type) Dest { pub fn to(self: Self, comptime Dest: type) Dest {
if (comptime !dims.eql(Dest.dims)) if (comptime !dims.eql(Dest.dims))
@compileError("Dimension mismatch: " ++ dims.str() ++ " vs " ++ Dest.dims.str()); @compileError("Dimension mismatch in to: " ++ dims.str() ++ " vs " ++ Dest.dims.str());
if (comptime @TypeOf(self) == Dest) if (comptime @TypeOf(self) == Dest)
return self; return self;
const source_factor = scales.getFactor(dims);
const dest_factor = Dest.scales.getFactor(Dest.dims);
const ratio = source_factor / dest_factor;
const result_f = toF64(self.value) * ratio;
const DestT = Dest.ValueType; const DestT = Dest.ValueType;
return .{ .value = switch (@typeInfo(DestT)) { const ratio = comptime (scales.getFactor(dims) / Dest.scales.getFactor(Dest.dims));
.int => @intFromFloat(@round(result_f)),
.float => @floatCast(result_f),
else => unreachable,
} };
}
// Fast-path: Native pure-integer exact conversions
if (comptime @typeInfo(T) == .int and @typeInfo(DestT) == .int) {
if (ratio >= 1.0 and @round(ratio) == ratio) {
const mult: DestT = @intFromFloat(ratio);
return .{ .value = @as(DestT, @intCast(self.value)) * mult };
} else if (ratio < 1.0 and @round(1.0 / ratio) == 1.0 / ratio) {
const div: DestT = @intFromFloat(1.0 / ratio);
const val = @as(DestT, @intCast(self.value));
const half = div / 2;
// Native round-to-nearest
const rounded = if (val >= 0) @divTrunc(val + half, div) else @divTrunc(val - half, div);
return .{ .value = rounded };
}
}
// Fallback preserving native Float types (e.g., f128 shouldn't downcast to f64)
if (comptime @typeInfo(DestT) == .float) {
const val_f = switch (@typeInfo(T)) {
.int => @as(DestT, @floatFromInt(self.value)),
.float => @as(DestT, @floatCast(self.value)),
else => unreachable,
};
return .{ .value = val_f * @as(DestT, @floatCast(ratio)) };
} else {
const val_f = switch (@typeInfo(T)) {
.int => @as(f64, @floatFromInt(self.value)),
.float => @as(f64, @floatCast(self.value)),
else => unreachable,
};
return .{ .value = @intFromFloat(@round(val_f * ratio)) };
}
}
pub fn vec3(self: Self) Vec3 { pub fn vec3(self: Self) Vec3 {
return .{ .x = self.value, .y = self.value, .z = self.value }; return .{ .x = self.value, .y = self.value, .z = self.value };
} }
@ -415,14 +437,17 @@ test "MulBy with scale" {
test "MulBy with type change" { test "MulBy with type change" {
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k })); const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const Second = Quantity(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{})); const Second = Quantity(f64, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
const KmSec = Quantity(f32, Dimensions.init(.{ .L = 1, .T = 1 }), Scales.init(.{ .L = .k })); const KmSec = Quantity(i64, Dimensions.init(.{ .L = 1, .T = 1 }), Scales.init(.{ .L = .k }));
const KmSec_f = Quantity(f32, Dimensions.init(.{ .L = 1, .T = 1 }), Scales.init(.{ .L = .k }));
const d = Meter{ .value = 3.0 }; const d = Meter{ .value = 3.0 };
const t = Second{ .value = 4.0 }; const t = Second{ .value = 4.0 };
const area_time = d.mulBy(t).to(KmSec); const area_time = d.mulBy(t).to(KmSec);
const area_time_f = d.mulBy(t).to(KmSec_f);
try std.testing.expectEqual(12, area_time.value); try std.testing.expectEqual(12, area_time.value);
try std.testing.expectApproxEqAbs(12, area_time_f.value, 0.0001);
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L)); try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L));
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T)); try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T));
std.debug.print("MulBy: {f} * {f} = {f} OK\n", .{ d, t, area_time }); std.debug.print("MulBy: {f} * {f} = {f} OK\n", .{ d, t, area_time });