Added overflaw security + made benchmark work (set to max)

This commit is contained in:
adrien 2026-04-22 23:11:42 +02:00
parent 5efa42c2e1
commit 2c94df7f4a
5 changed files with 74 additions and 40 deletions

View File

@ -5,6 +5,8 @@ const Dimensions = @import("Dimensions.zig");
const Scales = @import("Scales.zig");
const Scalar = @import("Scalar.zig").Scalar;
// TODO: Add common constants like G
fn PhysicalConstant(comptime d: Dimensions.ArgOpts, comptime val: f64, comptime s: Scales.ArgOpts) type {
return struct {
const dims = Dimensions.init(d);

View File

@ -63,7 +63,7 @@ pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_o
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
return .{ .value = lhs_val + rhs_val };
return .{ .value = if (comptime hlp.isInt(T)) lhs_val +| rhs_val else lhs_val + rhs_val };
}
/// Subtract two quantities. Dimensions must match compile error otherwise.
@ -84,7 +84,7 @@ pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_o
const TargetType = Scalar(T, dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
const lhs_val = if (comptime Self == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime RhsType == TargetType) rhs_s.value else rhs_s.to(TargetType).value;
return .{ .value = lhs_val - rhs_val };
return .{ .value = if (comptime hlp.isInt(T)) lhs_val -| rhs_val else lhs_val - rhs_val };
}
/// Multiply two quantities. Dimension exponents are summed: `L¹ * T¹ L¹T¹`.
@ -104,7 +104,7 @@ pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_o
const lhs_val = if (comptime Self == SelfNorm) self.value else self.to(SelfNorm).value;
const rhs_val = if (comptime RhsType == RhsNorm) rhs_s.value else rhs_s.to(RhsNorm).value;
return .{ .value = lhs_val * rhs_val };
return .{ .value = if (comptime hlp.isInt(T)) lhs_val *| rhs_val else lhs_val * rhs_val };
}
/// Divide two quantities. Dimension exponents are subtracted: `L¹ / T¹ L¹T¹`.
@ -121,7 +121,7 @@ pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_o
const RhsNorm = Scalar(T, RhsType.dims.argsOpt(), hlp.finerScales(Self, RhsType).argsOpt());
const lhs_val = if (comptime Self == SelfNorm) self.value else self.to(SelfNorm).value;
const rhs_val = if (comptime RhsType == RhsNorm) rhs_s.value else rhs_s.to(RhsNorm).value;
if (comptime @typeInfo(T) == .int) {
if (comptime hlp.isInt(T)) {
return .{ .value = @divTrunc(lhs_val, rhs_val) };
} else {
return .{ .value = lhs_val / rhs_val };
@ -148,8 +148,8 @@ pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_o
dims.scale(exp).argsOpt(),
scales.argsOpt(),
) {
if (comptime @typeInfo(T) == .int)
return .{ .value = std.math.powi(T, self.value, exp) catch @panic("Integer overflow in pow") }
if (comptime hlp.isInt(T))
return .{ .value = std.math.powi(T, self.value, exp) catch std.math.maxInt(T) }
else
return .{ .value = std.math.pow(T, self.value, @as(T, @floatFromInt(exp))) };
}
@ -163,7 +163,7 @@ pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_o
@compileError("Cannot take sqrt of " ++ dims.str() ++ ": exponents must be even.");
if (self.value < 0) return .{ .value = 0 };
if (comptime @typeInfo(T) == .int) {
if (comptime hlp.isInt(T)) {
const UnsignedT = @Int(.unsigned, @typeInfo(T).int.bits);
const u_len_sq = @as(UnsignedT, @intCast(self.value));
return .{ .value = @as(T, @intCast(std.math.sqrt(u_len_sq))) };

View File

@ -3,6 +3,8 @@ const hlp = @import("helper.zig");
const Dimensions = @import("Dimensions.zig");
const Dimension = @import("Dimensions.zig").Dimension;
// TODO: add more scales like feet and inch
/// Use to initiate Scalar and Scales type
pub const ArgOpts = struct {
L: UnitScale = .none,
@ -53,17 +55,17 @@ pub const UnitScale = enum(isize) {
/// Helper to get the actual scaling factor
pub inline fn getFactor(self: @This()) comptime_float {
return switch (self) {
return comptime switch (self) {
inline .P, .T, .G, .M, .k, .h, .da, .none, .d, .c, .m, .u, .n, .p, .f => std.math.pow(f64, 10.0, @floatFromInt(@intFromEnum(self))),
inline else => @floatFromInt(@intFromEnum(self)),
};
}
/// Helper to get the actual scaling factor in i32
pub inline fn getFactorInt(self: @This()) comptime_int {
return switch (self) {
inline .P, .T, .G, .M, .k, .h, .da, .none, .d, .c, .m, .u, .n, .p, .f => comptime std.math.powi(i32, 10.0, @intFromEnum(self)) catch 0,
inline else => comptime @intFromEnum(self),
pub fn getFactorInt(self: @This()) comptime_int {
return comptime switch (self) {
inline .P, .T, .G, .M, .k, .h, .da, .none, .d, .c, .m, .u, .n, .p, .f => std.math.powi(i32, 10.0, @intFromEnum(self)) catch 0,
inline else => @intFromEnum(self),
};
}
};

View File

@ -272,7 +272,10 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
scales.argsOpt(),
) {
var res_val: T = 1;
inline for (self.data) |v|
if (comptime hlp.isInt(T)) {
inline for (self.data) |v|
res_val = res_val *| v;
} else inline for (self.data) |v|
res_val *= v;
return .{ .value = res_val };
}

View File

@ -78,17 +78,17 @@ fn bench_Scalar(writer: *std.Io.Writer) !void {
\\
, .{ ITERS, SAMPLES });
const Types = .{ i16, i32, i64, i128, i256, f32, f64, f128 };
const TNames = .{ "i16", "i32", "i64", "i128", "i256", "f32", "f64", "f128" };
const Ops = .{ "add", "sub", "mulBy", "divBy", "to" };
const Types = .{ i16, i32, i64, i128, i256, f32, f64 };
const TNames = .{ "i16", "i32", "i64", "i128", "i256", "f32", "f64" };
const Ops = .{ "add", "sub", "mulBy", "divBy", "to", "abs", "pow", "eq", "gt", "mulBy(n)" };
var results_matrix: [Ops.len][Types.len]f64 = undefined;
comptime var tidx: usize = 0;
inline for (Types, TNames) |T, tname| {
const M = Scalar(T, .init(.{ .L = 1 }), .init(.{}));
const KM = Scalar(T, .init(.{ .L = 1 }), .init(.{ .L = .k }));
const S = Scalar(T, .init(.{ .T = 1 }), .init(.{}));
const M = Scalar(T, .{ .L = 1 }, .{});
const KM = Scalar(T, .{ .L = 1 }, .{ .L = .k });
const S = Scalar(T, .{ .T = 1 }, .{});
inline for (Ops, 0..) |op_name, oidx| {
var samples: [SAMPLES]f64 = undefined;
@ -107,8 +107,16 @@ fn bench_Scalar(writer: *std.Io.Writer) !void {
(M{ .value = getVal(T, i, 63) }).mulBy(M{ .value = getVal(T, i +% 1, 63) })
else if (comptime std.mem.eql(u8, op_name, "divBy"))
(M{ .value = getVal(T, i +% 10, 63) }).divBy(S{ .value = getVal(T, i, 63) })
else
(KM{ .value = getVal(T, i, 15) }).to(M);
else if (comptime std.mem.eql(u8, op_name, "to"))
(KM{ .value = getVal(T, i, 15) }).to(M)
else if (comptime std.mem.eql(u8, op_name, "abs"))
(M{ .value = getVal(T, i, 63) }).abs()
else if (comptime std.mem.eql(u8, op_name, "eq"))
(M{ .value = getVal(T, i, 63) }).eq(M{ .value = getVal(T, i +% 3, 63) })
else if (comptime std.mem.eql(u8, op_name, "gt"))
(M{ .value = getVal(T, i, 63) }).gt(M{ .value = getVal(T, i +% 3, 63) })
else // "mulBy(n)" bare comptime_int, dimensionless
(M{ .value = getVal(T, i, 63) }).mulBy(3);
},
);
}
@ -133,9 +141,9 @@ fn bench_Scalar(writer: *std.Io.Writer) !void {
try writer.print("└───────────────────┴──────┴─────────────────────┴─────────────────────┘\n\n", .{});
try writer.print("Median Summary (ns/op):\n", .{});
try writer.print("┌──────────────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────\n", .{});
try writer.print("│ Operation │ i16 │ i32 │ i64 │ i128 │ i256 │ f32 │ f64 │ f128 │\n", .{});
try writer.print("├──────────────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────\n", .{});
try writer.print("┌──────────────┬───────┬───────┬───────┬───────┬───────┬───────┬───────\n", .{});
try writer.print("│ Operation │ i16 │ i32 │ i64 │ i128 │ i256 │ f32 │ f64 │\n", .{});
try writer.print("├──────────────┼───────┼───────┼───────┼───────┼───────┼───────┼───────\n", .{});
inline for (Ops, 0..) |op_name, oidx| {
try writer.print("│ {s:<11} │", .{op_name});
@ -146,7 +154,7 @@ fn bench_Scalar(writer: *std.Io.Writer) !void {
try writer.print("\n", .{});
}
try writer.print("└──────────────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────\n", .{});
try writer.print("└──────────────┴───────┴───────┴───────┴───────┴───────┴───────┴───────\n", .{});
}
fn bench_vsNative(writer: *std.Io.Writer) !void {
@ -180,8 +188,8 @@ fn bench_vsNative(writer: *std.Io.Writer) !void {
var native_total_ns: f64 = 0;
var quantity_total_ns: f64 = 0;
const M = Scalar(T, .init(.{ .L = 1 }), .init(.{}));
const S = Scalar(T, .init(.{ .T = 1 }), .init(.{}));
const M = Scalar(T, .{ .L = 1 }, .{});
const S = Scalar(T, .{ .T = 1 }, .{});
std.mem.doNotOptimizeAway({
for (0..SAMPLES) |_| {
@ -278,9 +286,9 @@ fn bench_crossTypeVsNative(writer: *std.Io.Writer) !void {
var native_total_ns: f64 = 0;
var quantity_total_ns: f64 = 0;
const M1 = Scalar(T1, .init(.{ .L = 1 }), .init(.{}));
const M2 = Scalar(T2, .init(.{ .L = 1 }), .init(.{}));
const S2 = Scalar(T2, .init(.{ .T = 1 }), .init(.{}));
const M1 = Scalar(T1, .{ .L = 1 }, .{});
const M2 = Scalar(T2, .{ .L = 1 }, .{});
const S2 = Scalar(T2, .{ .T = 1 }, .{});
std.mem.doNotOptimizeAway({
for (0..SAMPLES) |_| {
@ -367,28 +375,36 @@ fn bench_Vector(writer: *std.Io.Writer) !void {
try writer.print(
\\
\\ Vector<N, T> benchmark — {d} iterations, {d} samples/cell
\\ (Results in ns/op)
\\ (Results in ns/op; "---" = not applicable for this length)
\\
\\┌─────────────┬──────┬─────────┬─────────┬─────────┐
\\│ Operation │ Type │ Len=3 │ Len=4 │ Len=16 │
\\├─────────────┼──────┼─────────┼─────────┼─────────┤
\\┌──────────────────┬──────┬─────────┬─────────┬─────────┐
\\│ Operation │ Type │ Len=3 │ Len=4 │ Len=16 │
\\├──────────────────┼──────┼─────────┼─────────┼─────────┤
\\
, .{ ITERS, SAMPLES });
const Types = .{ i32, i64, i128, f32, f64 };
const TNames = .{ "i32", "i64", "i128", "f32", "f64" };
const Lengths = .{ 3, 4, 16 };
const Ops = .{ "add", "divBy", "mulByScalar", "length" };
// "cross" is only valid for len=3; other cells will show " --- "
const Ops = .{ "add", "divBy", "mulByScalar", "dot", "cross", "product", "pow", "length" };
inline for (Ops, 0..) |op_name, o_idx| {
inline for (Types, TNames) |T, tname| {
try writer.print("│ {s:<11} │ {s:<4} │", .{ op_name, tname });
try writer.print("│ {s:<16} │ {s:<4} │", .{ op_name, tname });
inline for (Lengths) |len| {
const Q_base = Scalar(T, .init(.{ .L = 1 }), .init(.{}));
const Q_time = Scalar(T, .init(.{ .T = 1 }), .init(.{}));
const Q_base = Scalar(T, .{ .L = 1 }, .{});
const Q_time = Scalar(T, .{ .T = 1 }, .{});
const V = Vector(len, Q_base);
// cross product is only defined for len == 3
const is_cross = comptime std.mem.eql(u8, op_name, "cross");
if (comptime is_cross and len != 3) {
try writer.print(" --- │", .{});
continue;
}
var samples: [SAMPLES]f64 = undefined;
std.mem.doNotOptimizeAway({
@ -405,6 +421,17 @@ fn bench_Vector(writer: *std.Io.Writer) !void {
} else if (comptime std.mem.eql(u8, op_name, "mulByScalar")) {
const s_val = Q_time{ .value = getVal(T, i +% 2, 63) };
_ = v1.mulByScalar(s_val);
} else if (comptime std.mem.eql(u8, op_name, "dot")) {
const v2 = V.initDefault(getVal(T, i +% 5, 63));
_ = v1.dot(v2);
} else if (comptime std.mem.eql(u8, op_name, "cross")) {
// len == 3 guaranteed by the guard above
const v2 = V.initDefault(getVal(T, i +% 5, 63));
_ = v1.cross(v2);
} else if (comptime std.mem.eql(u8, op_name, "product")) {
_ = v1.product();
} else if (comptime std.mem.eql(u8, op_name, "pow")) {
_ = v1.pow(2);
} else if (comptime std.mem.eql(u8, op_name, "length")) {
_ = v1.length();
}
@ -422,8 +449,8 @@ fn bench_Vector(writer: *std.Io.Writer) !void {
}
if (o_idx < Ops.len - 1) {
try writer.print("├─────────────┼──────┼─────────┼─────────┼─────────┤\n", .{});
try writer.print("├──────────────────┼──────┼─────────┼─────────┼─────────┤\n", .{});
}
}
try writer.print("└─────────────┴──────┴─────────┴─────────┴─────────┘\n", .{});
try writer.print("└──────────────────┴──────┴─────────┴─────────┴─────────┘\n", .{});
}