First implementation
This commit is contained in:
parent
fbadb6ce06
commit
b3cee0588f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
zig-out
|
||||
.zig-cache
|
||||
37
build.zig
Normal file
37
build.zig
Normal file
@ -0,0 +1,37 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSmall });
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "Zig_Units",
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{},
|
||||
}),
|
||||
});
|
||||
|
||||
b.installArtifact(exe);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const exe_tests = b.addTest(.{
|
||||
.root_module = exe.root_module,
|
||||
});
|
||||
|
||||
// A run step that will run the second test executable.
|
||||
const run_exe_tests = b.addRunArtifact(exe_tests);
|
||||
|
||||
const test_step = b.step("test", "Run tests");
|
||||
test_step.dependOn(&run_exe_tests.step);
|
||||
}
|
||||
81
build.zig.zon
Normal file
81
build.zig.zon
Normal file
@ -0,0 +1,81 @@
|
||||
.{
|
||||
// This is the default name used by packages depending on this one. For
|
||||
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||
// as the key in the `dependencies` table. Although the user can choose a
|
||||
// different name, most users will stick with this provided value.
|
||||
//
|
||||
// It is redundant to include "zig" in this name because it is already
|
||||
// within the Zig package namespace.
|
||||
.name = .Zig_Units,
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.0.0",
|
||||
// Together with name, this represents a globally unique package
|
||||
// identifier. This field is generated by the Zig toolchain when the
|
||||
// package is first created, and then *never changes*. This allows
|
||||
// unambiguous detection of one package being an updated version of
|
||||
// another.
|
||||
//
|
||||
// When forking a Zig project, this id should be regenerated (delete the
|
||||
// field and run `zig build`) if the upstream project is still maintained.
|
||||
// Otherwise, the fork is *hostile*, attempting to take control over the
|
||||
// original project's identity. Thus it is recommended to leave the comment
|
||||
// on the following line intact, so that it shows up in code reviews that
|
||||
// modify the field.
|
||||
.fingerprint = 0x934b00cf88f69b22, // Changing this has security and trust implications.
|
||||
// Tracks the earliest Zig version that the package considers to be a
|
||||
// supported use case.
|
||||
.minimum_zig_version = "0.16.0",
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
//.example = .{
|
||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
||||
// // which will prevent zig from using it.
|
||||
// .url = "https://example.com/foo.tar.gz",
|
||||
//
|
||||
// // This is computed from the file contents of the directory of files that is
|
||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||
// // `paths`.
|
||||
// //
|
||||
// // This field is the source of truth; packages do not come from a `url`; they
|
||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// // obtain a package matching this `hash`.
|
||||
// //
|
||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
// .hash = "...",
|
||||
//
|
||||
// // When this is provided, the package is found in a directory relative to the
|
||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||
// // computed. This field and `url` are mutually exclusive.
|
||||
// .path = "foo",
|
||||
//
|
||||
// // When this is set to `true`, a package is declared to be lazily
|
||||
// // fetched. This makes the dependency only get fetched if it is
|
||||
// // actually used.
|
||||
// .lazy = false,
|
||||
//},
|
||||
},
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
// For example...
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
||||
90
src/Dimensions.zig
Normal file
90
src/Dimensions.zig
Normal file
@ -0,0 +1,90 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Dimension = enum {
|
||||
/// Length
|
||||
L,
|
||||
/// Mass
|
||||
M,
|
||||
/// Time
|
||||
T,
|
||||
/// Electric Current
|
||||
I,
|
||||
/// Temperature
|
||||
Tp,
|
||||
/// Amount of Substance
|
||||
N,
|
||||
/// Luminous Intensity
|
||||
J,
|
||||
|
||||
pub fn unit(self: @This()) []const u8 {
|
||||
return switch (self) {
|
||||
.L => "m",
|
||||
.M => "g",
|
||||
.T => "s",
|
||||
.I => "A",
|
||||
.Tp => "K",
|
||||
.N => "mol",
|
||||
.J => "cd",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// --------- Dimensions struct ---------
|
||||
|
||||
const Self = @This();
|
||||
|
||||
data: std.EnumArray(Dimension, i8),
|
||||
|
||||
pub fn init(comptime init_val: anytype) Self {
|
||||
var s = Self{ .data = std.EnumArray(Dimension, i8).initFill(0) };
|
||||
inline for (std.meta.fields(@TypeOf(init_val))) |f|
|
||||
s.data.set(@field(Dimension, f.name), @field(init_val, f.name));
|
||||
return s;
|
||||
}
|
||||
|
||||
pub fn initFill(val: i8) Self {
|
||||
return .{ .data = std.EnumArray(Dimension, i8).initFill(val) };
|
||||
}
|
||||
|
||||
pub fn get(self: Self, key: Dimension) i8 {
|
||||
return self.data.get(key);
|
||||
}
|
||||
|
||||
pub fn set(self: *Self, key: Dimension, val: i8) void {
|
||||
self.data.set(key, val);
|
||||
}
|
||||
|
||||
pub fn add(comptime a: Self, comptime b: Self) Self {
|
||||
var result = Self.initFill(0);
|
||||
for (std.enums.values(Dimension)) |d|
|
||||
result.set(d, a.get(d) + b.get(d));
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn sub(comptime a: Self, comptime b: Self) Self {
|
||||
@setEvalBranchQuota(10_000);
|
||||
var result = Self.initFill(0);
|
||||
for (std.enums.values(Dimension)) |d|
|
||||
result.set(d, a.get(d) - b.get(d));
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn eql(comptime a: Self, comptime b: Self) bool {
|
||||
for (std.enums.values(Dimension)) |d|
|
||||
if (a.get(d) != b.get(d)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn str(comptime a: Self) []const u8 {
|
||||
var out: []const u8 = "";
|
||||
const dims = std.enums.values(Dimension);
|
||||
|
||||
inline for (dims) |d| {
|
||||
const val = a.get(d);
|
||||
if (val != 0) {
|
||||
out = out ++ @tagName(d) ++ std.fmt.comptimePrint("{d}", .{val});
|
||||
}
|
||||
}
|
||||
|
||||
return if (out.len == 0) "Dimensionless" else out;
|
||||
}
|
||||
110
src/Scales.zig
Normal file
110
src/Scales.zig
Normal file
@ -0,0 +1,110 @@
|
||||
const std = @import("std");
|
||||
const hlp = @import("helper.zig");
|
||||
const Dimensions = @import("Dimensions.zig");
|
||||
const Dimension = @import("Dimensions.zig").Dimension;
|
||||
|
||||
pub const UnitScale = enum(i32) {
|
||||
P = 15,
|
||||
T = 12,
|
||||
G = 9,
|
||||
M = 6,
|
||||
k = 3,
|
||||
h = 2,
|
||||
da = 1,
|
||||
none = 0,
|
||||
d = -1,
|
||||
c = -2,
|
||||
m = -3,
|
||||
u = -6,
|
||||
n = -9,
|
||||
p = -12,
|
||||
f = -15,
|
||||
|
||||
// Custom
|
||||
min = 60,
|
||||
hour = 3_600,
|
||||
year = 31_536_000,
|
||||
|
||||
// Undefined
|
||||
_,
|
||||
|
||||
pub fn str(self: @This()) []const u8 {
|
||||
var buf: [16]u8 = undefined;
|
||||
return switch (self) {
|
||||
.none => "",
|
||||
inline .P, .T, .G, .M, .k, .h, .da, .d, .c, .m, .u, .n, .p, .f, .min, .hour, .year => @tagName(self),
|
||||
else => std.fmt.bufPrint(&buf, "[{d}]", .{@intFromEnum(self)}) catch "[]",
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper to get the actual scaling factor
|
||||
pub fn getFactor(self: @This()) f64 {
|
||||
return 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))),
|
||||
else => @floatFromInt(@intFromEnum(self)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper to get the actual scaling factor in i32
|
||||
pub fn getFactorInt(self: @This()) i32 {
|
||||
return 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,
|
||||
else => @intFromEnum(self),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const Scales = @This();
|
||||
|
||||
data: std.EnumArray(Dimension, UnitScale),
|
||||
|
||||
pub fn init(comptime init_val: anytype) Scales {
|
||||
var s = Scales{ .data = std.EnumArray(Dimension, UnitScale).initFill(.none) };
|
||||
inline for (std.meta.fields(@TypeOf(init_val))) |f| {
|
||||
if (comptime hlp.isInt(@TypeOf(@field(init_val, f.name))))
|
||||
s.data.set(@field(Dimension, f.name), @enumFromInt(@field(init_val, f.name)))
|
||||
else
|
||||
s.data.set(@field(Dimension, f.name), @field(init_val, f.name));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
pub fn initFill(val: UnitScale) Scales {
|
||||
return .{ .data = std.EnumArray(Dimension, UnitScale).initFill(val) };
|
||||
}
|
||||
|
||||
pub fn get(self: Scales, key: Dimension) UnitScale {
|
||||
return self.data.get(key);
|
||||
}
|
||||
|
||||
pub fn set(self: *Scales, key: Dimension, val: UnitScale) void {
|
||||
self.data.set(key, val);
|
||||
}
|
||||
|
||||
pub fn min(comptime s1: Scales, comptime s2: Scales) Scales {
|
||||
var out = Scales.initFill(.none);
|
||||
for (std.enums.values(Dimension)) |dim|
|
||||
out.set(dim, if (s1.get(dim).getFactorInt() > s2.get(dim).getFactorInt()) s2.get(dim) else s1.get(dim));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
pub fn getFactor(comptime s: Scales, comptime d: Dimensions) f64 {
|
||||
var factor: f64 = 1.0;
|
||||
for (std.enums.values(Dimension)) |dim| {
|
||||
const power = d.get(dim);
|
||||
if (power == 0) continue;
|
||||
|
||||
const base = s.get(dim).getFactor();
|
||||
|
||||
var i: i32 = 0;
|
||||
const abs_power = if (power < 0) -power else power;
|
||||
while (i < abs_power) : (i += 1) {
|
||||
if (power > 0)
|
||||
factor *= base
|
||||
else
|
||||
factor /= base;
|
||||
}
|
||||
}
|
||||
return factor;
|
||||
}
|
||||
32
src/helper.zig
Normal file
32
src/helper.zig
Normal file
@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn isInt(comptime T: type) bool {
|
||||
return @typeInfo(T) == .int or @typeInfo(T) == .comptime_int;
|
||||
}
|
||||
|
||||
pub fn printSuperscript(writer: *std.Io.Writer, n: i32) !void {
|
||||
if (n == 0) return;
|
||||
var val = n;
|
||||
if (val < 0) {
|
||||
try writer.writeAll("\u{207B}");
|
||||
val = -val;
|
||||
}
|
||||
var buf: [12]u8 = undefined;
|
||||
const str = std.fmt.bufPrint(&buf, "{d}", .{val}) catch return;
|
||||
for (str) |c| {
|
||||
const s = switch (c) {
|
||||
'0' => "\u{2070}",
|
||||
'1' => "\u{00B9}",
|
||||
'2' => "\u{00B2}",
|
||||
'3' => "\u{00B3}",
|
||||
'4' => "\u{2074}",
|
||||
'5' => "\u{2075}",
|
||||
'6' => "\u{2076}",
|
||||
'7' => "\u{2077}",
|
||||
'8' => "\u{2078}",
|
||||
'9' => "\u{2079}",
|
||||
else => unreachable,
|
||||
};
|
||||
try writer.writeAll(s);
|
||||
}
|
||||
}
|
||||
795
src/main.zig
Normal file
795
src/main.zig
Normal file
@ -0,0 +1,795 @@
|
||||
const std = @import("std");
|
||||
const hlp = @import("helper.zig");
|
||||
|
||||
const Scales = @import("Scales.zig");
|
||||
const UnitScale = Scales.UnitScale;
|
||||
const Dimensions = @import("Dimensions.zig");
|
||||
const Dimension = Dimensions.Dimension;
|
||||
|
||||
pub fn Quantity(T: type, d: Dimensions, s: Scales) type {
|
||||
return struct {
|
||||
value: T,
|
||||
|
||||
const Self = @This();
|
||||
pub const Vec3: type = QuantityVec3(Self);
|
||||
pub const ValueType: type = T;
|
||||
|
||||
pub const dims: Dimensions = d;
|
||||
pub const scales = s;
|
||||
|
||||
/// Internal helper to convert any supported T to f64 for math
|
||||
fn toF64(val: anytype) f64 {
|
||||
const TIn = @TypeOf(val);
|
||||
return switch (@typeInfo(TIn)) {
|
||||
.int => @floatFromInt(val),
|
||||
.float => @floatCast(val),
|
||||
else => @compileError("Unsupported type for Quantity"),
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal helper to convert f64 back to the target T
|
||||
fn fromF64(val: f64) T {
|
||||
return switch (@typeInfo(T)) {
|
||||
.int => @intFromFloat(@round(val)),
|
||||
.float => @floatCast(val),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper for integer power of 10 at comptime
|
||||
fn pow10(comptime exp: i32) T {
|
||||
var res: T = 1;
|
||||
var i: i32 = 0;
|
||||
const abs_exp = if (exp < 0) -exp else exp;
|
||||
while (i < abs_exp) : (i += 1) res *= 10;
|
||||
return res;
|
||||
}
|
||||
|
||||
pub fn add(self: Self, rhs: anytype) Self {
|
||||
if (comptime !dims.eql(@TypeOf(rhs).dims))
|
||||
@compileError("Dimension mismatch in add");
|
||||
return .{ .value = self.value + rhs.to(Self).value };
|
||||
}
|
||||
|
||||
pub fn sub(self: Self, rhs: anytype) Self {
|
||||
if (comptime !dims.eql(@TypeOf(rhs).dims))
|
||||
@compileError("Dimension mismatch in sub");
|
||||
return .{ .value = self.value - rhs.to(Self).value };
|
||||
}
|
||||
|
||||
pub fn mulBy(self: Self, rhs: anytype) Quantity(
|
||||
T,
|
||||
dims.add(@TypeOf(rhs).dims),
|
||||
scales.min(@TypeOf(rhs).scales),
|
||||
) {
|
||||
const self_ = self.to(Quantity(T, dims, scales.min(@TypeOf(rhs).scales)));
|
||||
const rhs_ = rhs.to(Quantity(T, @TypeOf(rhs).dims, scales.min(@TypeOf(rhs).scales)));
|
||||
return .{ .value = self_.value * rhs_.value };
|
||||
}
|
||||
|
||||
pub fn divBy(self: Self, rhs: anytype) Quantity(
|
||||
T,
|
||||
dims.sub(@TypeOf(rhs).dims),
|
||||
scales.min(@TypeOf(rhs).scales),
|
||||
) {
|
||||
const self_ = self.to(Quantity(T, dims, scales.min(@TypeOf(rhs).scales)));
|
||||
const rhs_ = rhs.to(Quantity(T, @TypeOf(rhs).dims, scales.min(@TypeOf(rhs).scales)));
|
||||
return .{ .value = fromF64((toF64(self_.value) / toF64(rhs_.value))) };
|
||||
}
|
||||
|
||||
pub fn scale(self: Self, sc: T) Self {
|
||||
return .{ .value = self.value * sc };
|
||||
}
|
||||
|
||||
pub fn to(self: Self, comptime Dest: type) Dest {
|
||||
if (comptime !dims.eql(Dest.dims))
|
||||
@compileError("Dimension mismatch: " ++ dims.str() ++ " vs " ++ Dest.dims.str());
|
||||
if (comptime @TypeOf(self) == Dest)
|
||||
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;
|
||||
return .{ .value = switch (@typeInfo(DestT)) {
|
||||
.int => @intFromFloat(@round(result_f)),
|
||||
.float => @floatCast(result_f),
|
||||
else => unreachable,
|
||||
} };
|
||||
}
|
||||
|
||||
pub fn vec3(self: Self) Vec3 {
|
||||
return .{ .x = self.value, .y = self.value, .z = self.value };
|
||||
}
|
||||
|
||||
pub fn format(
|
||||
self: Self,
|
||||
writer: *std.Io.Writer,
|
||||
) !void {
|
||||
try writer.print("{d}", .{self.value});
|
||||
var iter = std.EnumSet(Dimension).initFull().iterator();
|
||||
var first = true;
|
||||
while (iter.next()) |bu| {
|
||||
const v = dims.get(bu);
|
||||
if (v == 0) continue;
|
||||
if (!first)
|
||||
try writer.writeAll(".");
|
||||
|
||||
first = false;
|
||||
|
||||
const uscale = scales.get(bu);
|
||||
if (bu == .T and (uscale == .min or uscale == .hour or uscale == .year))
|
||||
try writer.print("{s}", .{uscale.str()})
|
||||
else
|
||||
try writer.print("{s}{s}", .{ uscale.str(), bu.unit() });
|
||||
|
||||
if (v != 1)
|
||||
try hlp.printSuperscript(writer, v);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn QuantityVec3(Q: type) type {
|
||||
const T = Q.ValueType;
|
||||
const d: Dimensions = Q.dims;
|
||||
const s: Scales = Q.scales;
|
||||
|
||||
return struct {
|
||||
x: T,
|
||||
y: T,
|
||||
z: T,
|
||||
|
||||
const Self = @This();
|
||||
pub const QuantityType = Q;
|
||||
pub const ValueType = T;
|
||||
pub const dims: Dimensions = d;
|
||||
pub const scales = s;
|
||||
|
||||
pub const zero = Self{ .x = 0, .y = 0, .z = 0 };
|
||||
pub const one = Self{ .x = 1, .y = 1, .z = 1 };
|
||||
|
||||
pub fn initDefault(v: T) Self {
|
||||
return .{ .x = v, .y = v, .z = v };
|
||||
}
|
||||
|
||||
pub fn add(self: Self, rhs: anytype) Self {
|
||||
const Tr = @TypeOf(rhs);
|
||||
return .{
|
||||
.x = (Q{ .value = self.x }).add(Tr.QuantityType{ .value = rhs.x }).value,
|
||||
.y = (Q{ .value = self.y }).add(Tr.QuantityType{ .value = rhs.y }).value,
|
||||
.z = (Q{ .value = self.z }).add(Tr.QuantityType{ .value = rhs.z }).value,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sub(self: Self, rhs: anytype) Self {
|
||||
const Tr = @TypeOf(rhs);
|
||||
return .{
|
||||
.x = (Q{ .value = self.x }).sub(Tr.QuantityType{ .value = rhs.x }).value,
|
||||
.y = (Q{ .value = self.y }).sub(Tr.QuantityType{ .value = rhs.y }).value,
|
||||
.z = (Q{ .value = self.z }).sub(Tr.QuantityType{ .value = rhs.z }).value,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn divBy(
|
||||
self: Self,
|
||||
rhs: anytype,
|
||||
) QuantityVec3(Quantity(T, d.sub(@TypeOf(rhs).dims), s.min(@TypeOf(rhs).scales))) {
|
||||
const Tr = @TypeOf(rhs);
|
||||
return .{
|
||||
.x = (Q{ .value = self.x }).divBy(Tr.QuantityType{ .value = rhs.x }).value,
|
||||
.y = (Q{ .value = self.y }).divBy(Tr.QuantityType{ .value = rhs.y }).value,
|
||||
.z = (Q{ .value = self.z }).divBy(Tr.QuantityType{ .value = rhs.z }).value,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn mulBy(
|
||||
self: Self,
|
||||
rhs: anytype,
|
||||
) QuantityVec3(Quantity(T, d.sub(@TypeOf(rhs).dims), s.min(@TypeOf(rhs).scales))) {
|
||||
const Tr = @TypeOf(rhs);
|
||||
return .{
|
||||
.x = (Q{ .value = self.x }).mulBy(Tr.QuantityType{ .value = rhs.x }).value,
|
||||
.y = (Q{ .value = self.y }).mulBy(Tr.QuantityType{ .value = rhs.y }).value,
|
||||
.z = (Q{ .value = self.z }).mulBy(Tr.QuantityType{ .value = rhs.z }).value,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn divByScalar(
|
||||
self: Self,
|
||||
scalar: anytype,
|
||||
) QuantityVec3(Quantity(T, d.sub(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) {
|
||||
const q_x = Q{ .value = self.x };
|
||||
const q_y = Q{ .value = self.y };
|
||||
const q_z = Q{ .value = self.z };
|
||||
|
||||
return .{
|
||||
.x = q_x.divBy(scalar).value,
|
||||
.y = q_y.divBy(scalar).value,
|
||||
.z = q_z.divBy(scalar).value,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn mulByScalar(
|
||||
self: Self,
|
||||
scalar: anytype,
|
||||
) QuantityVec3(Quantity(T, d.add(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) {
|
||||
const q_x = Q{ .value = self.x };
|
||||
const q_y = Q{ .value = self.y };
|
||||
const q_z = Q{ .value = self.z };
|
||||
|
||||
return .{
|
||||
.x = q_x.mulBy(scalar).value,
|
||||
.y = q_y.mulBy(scalar).value,
|
||||
.z = q_z.mulBy(scalar).value,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn negate(self: Self) Self {
|
||||
return .{ .x = -self.x, .y = -self.y, .z = -self.z };
|
||||
}
|
||||
|
||||
pub fn scale(self: Self, rhs: T) Self {
|
||||
return .{
|
||||
.x = (Q{ .value = self.x }).scale(rhs).value,
|
||||
.y = (Q{ .value = self.y }).scale(rhs).value,
|
||||
.z = (Q{ .value = self.z }).scale(rhs).value,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn to(self: Self, comptime DestQ: type) QuantityVec3(DestQ) {
|
||||
return .{
|
||||
.x = (Q{ .value = self.x }).to(DestQ).value,
|
||||
.y = (Q{ .value = self.y }).to(DestQ).value,
|
||||
.z = (Q{ .value = self.z }).to(DestQ).value,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Self, writer: *std.Io.Writer) !void {
|
||||
try writer.print("({d:.2}, {d:.2}, {d:.2})", .{ self.x, self.y, self.z });
|
||||
var iter = std.EnumSet(Dimension).initFull().iterator();
|
||||
var first = true;
|
||||
while (iter.next()) |bu| {
|
||||
const v = dims.get(bu);
|
||||
if (v == 0) continue;
|
||||
if (!first) try writer.writeAll(".");
|
||||
first = false;
|
||||
try writer.print("{s}{s}", .{ scales.get(bu).str(), bu.unit() });
|
||||
if (v != 1) try hlp.printSuperscript(writer, v);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lengthSqr(self: Self) T {
|
||||
return self.x * self.x + self.y * self.y + self.z * self.z;
|
||||
}
|
||||
|
||||
pub fn length(self: Self) T {
|
||||
if (comptime hlp.isInt(T))
|
||||
return self.isqrt()
|
||||
else
|
||||
return @sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
|
||||
}
|
||||
|
||||
fn isqrt(self: Self) T {
|
||||
const squared_sum = (self.x * self.x) + (self.y * self.y) + (self.z * self.z);
|
||||
if (squared_sum <= 0) return 0;
|
||||
|
||||
var x = squared_sum;
|
||||
var y = @divTrunc(x + 1, 2);
|
||||
while (y < x) {
|
||||
x = y;
|
||||
y = @divTrunc(x + @divTrunc(squared_sum, x), 2);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn main(_: std.process.Init) void {}
|
||||
|
||||
test "Generate quantity" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = -3 }));
|
||||
const Second = Quantity(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{ .T = .n }));
|
||||
|
||||
const distance = Meter{ .value = 10 };
|
||||
const time = Second{ .value = 2 };
|
||||
|
||||
try std.testing.expectEqual(10, distance.value);
|
||||
try std.testing.expectEqual(2, time.value);
|
||||
}
|
||||
|
||||
test "Add" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
|
||||
const distance = Meter{ .value = 10 };
|
||||
const distance2 = Meter{ .value = 20 };
|
||||
|
||||
const added = distance.add(distance2);
|
||||
try std.testing.expectEqual(30, added.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(added).dims.get(.L));
|
||||
std.debug.print("KiloMeter {f} + {f} = {f} OK\n", .{ distance, distance2, added });
|
||||
|
||||
const KiloMeter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||
const distance3 = KiloMeter{ .value = 2 };
|
||||
const added2 = distance.add(distance3);
|
||||
try std.testing.expectEqual(2010, added2.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(added2).dims.get(.L));
|
||||
std.debug.print("KiloMeter {f} + {f} = {f} OK\n", .{ distance, distance3, added2 });
|
||||
|
||||
const added3 = distance3.add(distance);
|
||||
try std.testing.expectEqual(2, added3.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(added3).dims.get(.L));
|
||||
std.debug.print("KiloMeter {f} + {f} = {f} OK\n", .{ distance3, distance, added3 });
|
||||
|
||||
const KiloMeter_f = Quantity(f64, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||
const distance4 = KiloMeter_f{ .value = 2 };
|
||||
const added4 = distance4.add(distance);
|
||||
try std.testing.expectEqual(2.01, added4.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(added4).dims.get(.L));
|
||||
std.debug.print("KiloMeter_f {f} + {f} = {f} OK\n", .{ distance4, distance, added4 });
|
||||
}
|
||||
|
||||
test "Sub" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const KiloMeter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||
const KiloMeter_f = Quantity(f64, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||
|
||||
const a = Meter{ .value = 500 };
|
||||
const b = Meter{ .value = 200 };
|
||||
const diff = a.sub(b);
|
||||
try std.testing.expectEqual(300, diff.value);
|
||||
std.debug.print("Sub: {f} - {f} = {f} OK\n", .{ a, b, diff });
|
||||
|
||||
const km = KiloMeter{ .value = 1 };
|
||||
const diff2 = a.sub(km);
|
||||
std.debug.print("Sub cross-scale: {f} - {f} = {f}\n", .{ a, km, diff2 });
|
||||
|
||||
const km_f = KiloMeter_f{ .value = 2.5 };
|
||||
const m_f = Meter{ .value = 500 };
|
||||
const diff3 = km_f.sub(m_f);
|
||||
try std.testing.expectApproxEqAbs(@as(f32, 2.0), diff3.value, 1e-4);
|
||||
std.debug.print("Sub float cross-scale: {f} - {f} = {f} OK\n", .{ km_f, m_f, diff3 });
|
||||
}
|
||||
|
||||
test "MulBy" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const Second = Quantity(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||
|
||||
const d = Meter{ .value = 3.0 };
|
||||
const t = Second{ .value = 4.0 };
|
||||
|
||||
const area_time = d.mulBy(t);
|
||||
try std.testing.expectEqual(12, area_time.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L));
|
||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T));
|
||||
std.debug.print("MulBy: {f} * {f} = {f} OK\n", .{ d, t, area_time });
|
||||
|
||||
const d2 = Meter{ .value = 5.0 };
|
||||
const area = d.mulBy(d2);
|
||||
try std.testing.expectEqual(15, area.value);
|
||||
try std.testing.expectEqual(2, @TypeOf(area).dims.get(.L));
|
||||
try std.testing.expectEqual(0, @TypeOf(area).dims.get(.T));
|
||||
std.debug.print("MulBy: {f} * {f} = {f} OK\n", .{ d, d2, area });
|
||||
}
|
||||
|
||||
test "MulBy with scale" {
|
||||
const KiloMeter = Quantity(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||
const KiloGram = Quantity(f32, Dimensions.init(.{ .M = 1 }), Scales.init(.{ .M = .k }));
|
||||
|
||||
const dist = KiloMeter{ .value = 2.0 };
|
||||
const mass = KiloGram{ .value = 3.0 };
|
||||
const prod = dist.mulBy(mass);
|
||||
try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.L));
|
||||
try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.M));
|
||||
std.debug.print("MulBy scaled: {f} * {f} = {f} OK\n", .{ dist, mass, prod });
|
||||
}
|
||||
|
||||
test "MulBy with type change" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||
const Second = Quantity(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||
const KmSec = Quantity(f32, Dimensions.init(.{ .L = 1, .T = 1 }), Scales.init(.{ .L = .k }));
|
||||
|
||||
const d = Meter{ .value = 3.0 };
|
||||
const t = Second{ .value = 4.0 };
|
||||
|
||||
const area_time = d.mulBy(t).to(KmSec);
|
||||
try std.testing.expectEqual(12, area_time.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L));
|
||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T));
|
||||
std.debug.print("MulBy: {f} * {f} = {f} OK\n", .{ d, t, area_time });
|
||||
}
|
||||
|
||||
test "MulBy small" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .n }));
|
||||
const Second = Quantity(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||
|
||||
const d = Meter{ .value = 3.0 };
|
||||
const t = Second{ .value = 4.0 };
|
||||
|
||||
const area_time = d.mulBy(t);
|
||||
try std.testing.expectEqual(12, area_time.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L));
|
||||
try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T));
|
||||
std.debug.print("MulBy: {f} * {f} = {f} OK\n", .{ d, t, area_time });
|
||||
}
|
||||
|
||||
test "Scale" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const Second = Quantity(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||
|
||||
const d = Meter{ .value = 7 };
|
||||
const scaled = d.scale(3);
|
||||
try std.testing.expectEqual(21, scaled.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L));
|
||||
std.debug.print("Scale int: {f} * 3 = {f} OK\n", .{ d, scaled });
|
||||
|
||||
const t = Second{ .value = 1.5 };
|
||||
const scaled_f = t.scale(4.0);
|
||||
try std.testing.expectApproxEqAbs(@as(f32, 6.0), scaled_f.value, 1e-4);
|
||||
std.debug.print("Scale float: {f} * 4 = {f} OK\n", .{ t, scaled_f });
|
||||
}
|
||||
|
||||
test "Chained: velocity and acceleration" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const Second = Quantity(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||
|
||||
const dist = Meter{ .value = 100.0 };
|
||||
const t1 = Second{ .value = 5.0 };
|
||||
const velocity = dist.divBy(t1);
|
||||
try std.testing.expectEqual(20, velocity.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(velocity).dims.get(.L));
|
||||
try std.testing.expectEqual(-1, @TypeOf(velocity).dims.get(.T));
|
||||
|
||||
const t2 = Second{ .value = 4.0 };
|
||||
const accel = velocity.divBy(t2);
|
||||
try std.testing.expectEqual(5, accel.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(accel).dims.get(.L));
|
||||
try std.testing.expectEqual(-2, @TypeOf(accel).dims.get(.T));
|
||||
|
||||
std.debug.print("Velocity: {f}, Acceleration: {f} OK\n", .{ velocity, accel });
|
||||
}
|
||||
|
||||
test "DivBy integer exact" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const Second = Quantity(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||
|
||||
const dist = Meter{ .value = 120 };
|
||||
const time = Second{ .value = 4 };
|
||||
const vel = dist.divBy(time);
|
||||
|
||||
try std.testing.expectEqual(30, vel.value);
|
||||
try std.testing.expectEqual(1, @TypeOf(vel).dims.get(.L));
|
||||
try std.testing.expectEqual(-1, @TypeOf(vel).dims.get(.T));
|
||||
std.debug.print("DivBy int: {f} / {f} = {f} OK\n", .{ dist, time, vel });
|
||||
}
|
||||
|
||||
test "Conversion chain: km -> m -> cm" {
|
||||
const KiloMeter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const CentiMeter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .c }));
|
||||
|
||||
const km = KiloMeter{ .value = 15 };
|
||||
const m = km.to(Meter);
|
||||
const cm = m.to(CentiMeter);
|
||||
|
||||
try std.testing.expectEqual(15_000, m.value);
|
||||
try std.testing.expectEqual(1_500_000, cm.value);
|
||||
std.debug.print("Chain: {f} -> {f} -> {f} OK\n", .{ km, m, cm });
|
||||
}
|
||||
|
||||
test "Conversion: hours -> minutes -> seconds" {
|
||||
const Hour = Quantity(i128, Dimensions.init(.{ .T = 1 }), Scales.init(.{ .T = .hour }));
|
||||
const Minute = Quantity(i128, Dimensions.init(.{ .T = 1 }), Scales.init(.{ .T = .min }));
|
||||
const Second = Quantity(i128, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||
|
||||
const h = Hour{ .value = 1.0 };
|
||||
const min = h.to(Minute);
|
||||
const sec = min.to(Second);
|
||||
|
||||
try std.testing.expectEqual(60, min.value);
|
||||
try std.testing.expectEqual(3600, sec.value);
|
||||
std.debug.print("Time chain: {f} -> {f} -> {f} OK\n", .{ h, min, sec });
|
||||
}
|
||||
|
||||
test "Negative values" {
|
||||
const Meter = Quantity(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
|
||||
const a = Meter{ .value = 5 };
|
||||
const b = Meter{ .value = 20 };
|
||||
const diff = a.sub(b);
|
||||
try std.testing.expectEqual(-15, diff.value);
|
||||
std.debug.print("Negative sub: {f} - {f} = {f} OK\n", .{ a, b, diff });
|
||||
}
|
||||
|
||||
test "Format Quantity" {
|
||||
const MeterPerSecondSq = Quantity(
|
||||
f32,
|
||||
Dimensions.init(.{ .L = 1, .T = -2 }),
|
||||
Scales.init(.{ .T = .n }),
|
||||
);
|
||||
const KgMeterPerSecond = Quantity(
|
||||
f32,
|
||||
Dimensions.init(.{ .M = 1, .L = 1, .T = -1 }),
|
||||
Scales.init(.{ .M = .k }),
|
||||
);
|
||||
|
||||
const accel = MeterPerSecondSq{ .value = 9.81 };
|
||||
const momentum = KgMeterPerSecond{ .value = 42.0 };
|
||||
|
||||
std.debug.print("Acceleration: {f}\n", .{accel});
|
||||
std.debug.print("Momentum: {f}\n", .{momentum});
|
||||
}
|
||||
|
||||
test "Format Vector3" {
|
||||
const MeterPerSecondSq = Quantity(
|
||||
f32,
|
||||
Dimensions.init(.{ .L = 1, .T = -2 }),
|
||||
Scales.init(.{ .T = .n }),
|
||||
);
|
||||
const KgMeterPerSecond = Quantity(
|
||||
f32,
|
||||
Dimensions.init(.{ .M = 1, .L = 1, .T = -1 }),
|
||||
Scales.init(.{ .M = .k }),
|
||||
);
|
||||
|
||||
const accel = MeterPerSecondSq.Vec3.initDefault(9.81);
|
||||
const momentum = KgMeterPerSecond.Vec3{ .x = 43, .y = 0, .z = 11 };
|
||||
|
||||
std.debug.print("Acceleration: {f}\n", .{accel});
|
||||
std.debug.print("Momentum: {f}\n", .{momentum});
|
||||
}
|
||||
|
||||
test "Vec3 Init and Basic Arithmetic" {
|
||||
const Meter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const Vec3M = Meter.Vec3;
|
||||
|
||||
// Test zero, one, initDefault
|
||||
const v_zero = Vec3M.zero;
|
||||
try std.testing.expectEqual(0, v_zero.x);
|
||||
|
||||
const v_one = Vec3M.one;
|
||||
try std.testing.expectEqual(1, v_one.x);
|
||||
|
||||
const v_def = Vec3M.initDefault(5);
|
||||
try std.testing.expectEqual(5, v_def.x);
|
||||
try std.testing.expectEqual(5, v_def.y);
|
||||
try std.testing.expectEqual(5, v_def.z);
|
||||
|
||||
// Test add and sub
|
||||
const v1 = Vec3M{ .x = 10, .y = 20, .z = 30 };
|
||||
const v2 = Vec3M{ .x = 2, .y = 4, .z = 6 };
|
||||
|
||||
const added = v1.add(v2);
|
||||
try std.testing.expectEqual(12, added.x);
|
||||
try std.testing.expectEqual(24, added.y);
|
||||
try std.testing.expectEqual(36, added.z);
|
||||
|
||||
const subbed = v1.sub(v2);
|
||||
try std.testing.expectEqual(8, subbed.x);
|
||||
try std.testing.expectEqual(16, subbed.y);
|
||||
try std.testing.expectEqual(24, subbed.z);
|
||||
|
||||
// Test negate
|
||||
const neg = v1.negate();
|
||||
try std.testing.expectEqual(-10, neg.x);
|
||||
try std.testing.expectEqual(-20, neg.y);
|
||||
try std.testing.expectEqual(-30, neg.z);
|
||||
}
|
||||
|
||||
test "Vec3 Kinematics (Scalar Mul/Div)" {
|
||||
const Meter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const Second = Quantity(i32, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||
const Vec3M = Meter.Vec3;
|
||||
|
||||
const pos = Vec3M{ .x = 100, .y = 200, .z = 300 };
|
||||
const time = Second{ .value = 10 };
|
||||
|
||||
// Vector divided by scalar Quantity (Velocity = Position / Time)
|
||||
const vel = pos.divByScalar(time);
|
||||
try std.testing.expectEqual(10, vel.x);
|
||||
try std.testing.expectEqual(20, vel.y);
|
||||
try std.testing.expectEqual(30, vel.z);
|
||||
try std.testing.expectEqual(1, @TypeOf(vel).dims.get(.L));
|
||||
try std.testing.expectEqual(-1, @TypeOf(vel).dims.get(.T));
|
||||
|
||||
// Vector multiplied by scalar Quantity (Position = Velocity * Time)
|
||||
const new_pos = vel.mulByScalar(time);
|
||||
try std.testing.expectEqual(100, new_pos.x);
|
||||
try std.testing.expectEqual(200, new_pos.y);
|
||||
try std.testing.expectEqual(300, new_pos.z);
|
||||
try std.testing.expectEqual(1, @TypeOf(new_pos).dims.get(.L));
|
||||
try std.testing.expectEqual(0, @TypeOf(new_pos).dims.get(.T));
|
||||
}
|
||||
|
||||
test "Vec3 Element-wise Math and Scaling" {
|
||||
const Meter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const Vec3M = Meter.Vec3;
|
||||
|
||||
const v1 = Vec3M{ .x = 10, .y = 20, .z = 30 };
|
||||
const v2 = Vec3M{ .x = 2, .y = 5, .z = 10 };
|
||||
|
||||
// Element-wise division
|
||||
const div = v1.divBy(v2);
|
||||
try std.testing.expectEqual(5, div.x);
|
||||
try std.testing.expectEqual(4, div.y);
|
||||
try std.testing.expectEqual(3, div.z);
|
||||
try std.testing.expectEqual(0, @TypeOf(div).dims.get(.L)); // M / M = Dimensionless
|
||||
|
||||
// Scale by primitive
|
||||
const scaled = v1.scale(2);
|
||||
try std.testing.expectEqual(20, scaled.x);
|
||||
try std.testing.expectEqual(40, scaled.y);
|
||||
try std.testing.expectEqual(60, scaled.z);
|
||||
}
|
||||
|
||||
test "Vec3 Conversions" {
|
||||
const KiloMeter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||
const Meter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
|
||||
const v_km = KiloMeter.Vec3{ .x = 1, .y = 2, .z = 3 };
|
||||
const v_m = v_km.to(Meter);
|
||||
|
||||
try std.testing.expectEqual(1000, v_m.x);
|
||||
try std.testing.expectEqual(2000, v_m.y);
|
||||
try std.testing.expectEqual(3000, v_m.z);
|
||||
|
||||
// Type checking the result
|
||||
try std.testing.expectEqual(1, @TypeOf(v_m).dims.get(.L));
|
||||
try std.testing.expectEqual(UnitScale.none, @TypeOf(v_m).scales.get(.L));
|
||||
}
|
||||
|
||||
test "Vec3 Length" {
|
||||
const MeterInt = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const MeterFloat = Quantity(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
|
||||
// Integer length (using your custom isqrt)
|
||||
// 3-4-5 triangle on XY plane
|
||||
const v_int = MeterInt.Vec3{ .x = 3, .y = 4, .z = 0 };
|
||||
try std.testing.expectEqual(25, v_int.lengthSqr());
|
||||
try std.testing.expectEqual(5, v_int.length());
|
||||
|
||||
// Float length
|
||||
const v_float = MeterFloat.Vec3{ .x = 3.0, .y = 4.0, .z = 0.0 };
|
||||
try std.testing.expectApproxEqAbs(@as(f32, 25.0), v_float.lengthSqr(), 1e-4);
|
||||
try std.testing.expectApproxEqAbs(@as(f32, 5.0), v_float.length(), 1e-4);
|
||||
}
|
||||
|
||||
test "Comprehensive Benchmark: All Ops × All Types" {
|
||||
const Io = std.Io;
|
||||
const ITERS: usize = 100_000;
|
||||
const SAMPLES: usize = 10; // Number of samples for stats
|
||||
|
||||
var gsink: f64 = 0;
|
||||
const io = std.testing.io;
|
||||
|
||||
// Standard Zig 0.16 timestamp retrieval
|
||||
const getTime = struct {
|
||||
fn f(i: Io) Io.Timestamp {
|
||||
return Io.Clock.awake.now(i);
|
||||
}
|
||||
}.f;
|
||||
|
||||
const fold = struct {
|
||||
fn f(comptime TT: type, s: *f64, v: TT) void {
|
||||
s.* += if (comptime @typeInfo(TT) == .float)
|
||||
@as(f64, @floatCast(v))
|
||||
else
|
||||
@as(f64, @floatFromInt(v));
|
||||
}
|
||||
}.f;
|
||||
|
||||
const getVal = struct {
|
||||
fn f(comptime TT: type, i: usize, comptime mask: u7) TT {
|
||||
const v: u8 = @as(u8, @truncate(i & @as(usize, mask))) + 1;
|
||||
return if (comptime @typeInfo(TT) == .float) @floatFromInt(v) else @intCast(v);
|
||||
}
|
||||
}.f;
|
||||
|
||||
const Stats = struct {
|
||||
median: f64,
|
||||
delta: f64,
|
||||
ops_per_sec: f64,
|
||||
};
|
||||
|
||||
const computeStats = struct {
|
||||
fn f(samples: []f64, iters: usize) Stats {
|
||||
std.mem.sort(f64, samples, {}, std.sort.asc(f64));
|
||||
const mid = samples.len / 2;
|
||||
const median_ns = if (samples.len % 2 == 0) (samples[mid - 1] + samples[mid]) / 2.0 else samples[mid];
|
||||
|
||||
const low = samples[0];
|
||||
const high = samples[samples.len - 1];
|
||||
const delta_ns = (high - low) / 2.0;
|
||||
|
||||
const ns_per_op = median_ns / @as(f64, @floatFromInt(iters));
|
||||
return .{
|
||||
.median = ns_per_op,
|
||||
.delta = (delta_ns / @as(f64, @floatFromInt(iters))),
|
||||
.ops_per_sec = 1_000_000_000.0 / ns_per_op,
|
||||
};
|
||||
}
|
||||
}.f;
|
||||
|
||||
std.debug.print(
|
||||
\\
|
||||
\\ Quantity<T> benchmark — {d} iterations, {d} samples/cell
|
||||
\\
|
||||
\\┌───────────────────┬──────┬─────────────────────┬─────────────────────┐
|
||||
\\│ Operation │ Type │ ns / op (± delta) │ Throughput (ops/s) │
|
||||
\\├───────────────────┼──────┼─────────────────────┼─────────────────────┤
|
||||
\\
|
||||
, .{ ITERS, SAMPLES });
|
||||
|
||||
const Types = .{ i16, i32, i64, i128, f32, f64 };
|
||||
const TNames = .{ "i16", "i32", "i64", "i128", "f32", "f64" };
|
||||
const Ops = .{ "add", "sub", "mulBy", "divBy", "scale", "to" };
|
||||
|
||||
var results_matrix: [Ops.len][Types.len]f64 = undefined;
|
||||
|
||||
comptime var tidx: usize = 0;
|
||||
inline for (Types, TNames) |T, tname| {
|
||||
const M = Quantity(T, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||
const KM = Quantity(T, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||
const S = Quantity(T, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||
|
||||
inline for (Ops, 0..) |op_name, oidx| {
|
||||
var samples: [SAMPLES]f64 = undefined;
|
||||
|
||||
for (0..SAMPLES) |s_idx| {
|
||||
var sink: T = 0;
|
||||
const t_start = getTime(io);
|
||||
|
||||
for (0..ITERS) |i| {
|
||||
const r = if (comptime std.mem.eql(u8, op_name, "add"))
|
||||
(M{ .value = getVal(T, i, 63) }).add(M{ .value = getVal(T, i +% 7, 63) })
|
||||
else if (comptime std.mem.eql(u8, op_name, "sub"))
|
||||
(M{ .value = getVal(T, i +% 10, 63) }).sub(M{ .value = getVal(T, i, 63) })
|
||||
else if (comptime std.mem.eql(u8, op_name, "mulBy"))
|
||||
(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 if (comptime std.mem.eql(u8, op_name, "scale"))
|
||||
(M{ .value = getVal(T, i, 63) }).scale(getVal(T, i +% 2, 63))
|
||||
else
|
||||
(KM{ .value = getVal(T, i, 15) }).to(M);
|
||||
|
||||
if (comptime @typeInfo(T) == .float) sink += r.value else sink ^= r.value;
|
||||
}
|
||||
|
||||
const t_end = getTime(io);
|
||||
samples[s_idx] = @as(f64, @floatFromInt(t_start.durationTo(t_end).toNanoseconds()));
|
||||
fold(T, &gsink, sink);
|
||||
}
|
||||
|
||||
const stats = computeStats(&samples, ITERS);
|
||||
results_matrix[oidx][tidx] = stats.median;
|
||||
|
||||
std.debug.print("│ {s:<17} │ {s:<4} │ {d:>8.2} ns ±{d:<6.2} │ {d:>19.0} │\n", .{ op_name, tname, stats.median, stats.delta, stats.ops_per_sec });
|
||||
}
|
||||
|
||||
if (comptime tidx < Types.len - 1) {
|
||||
std.debug.print("├───────────────────┼──────┼─────────────────────┼─────────────────────┤\n", .{});
|
||||
}
|
||||
tidx += 1;
|
||||
}
|
||||
|
||||
// Median Summary Table
|
||||
std.debug.print("└───────────────────┴──────┴─────────────────────┴─────────────────────┘\n\n", .{});
|
||||
std.debug.print("Median Summary (ns/op):\n", .{});
|
||||
std.debug.print("Operation │ i16 │ i32 │ i64 │ i128 │ f32 │ f64 \n", .{});
|
||||
std.debug.print("───────────────┼───────┼───────┼───────┼───────┼───────┼───────\n", .{});
|
||||
|
||||
inline for (Ops, 0..) |op_name, oidx| {
|
||||
std.debug.print("{s:<14} │", .{op_name});
|
||||
var i: usize = 0;
|
||||
while (i < Types.len) : (i += 1) {
|
||||
std.debug.print("{d:>6.1} │", .{results_matrix[oidx][i]});
|
||||
}
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
|
||||
std.debug.print("\nAnti-optimisation sink: {d:.4}\n", .{gsink});
|
||||
try std.testing.expect(gsink != 0);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user