Files
zig-dimal/src/Scales.zig
T

156 lines
4.6 KiB
Zig

const std = @import("std");
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,
M: UnitScale = .none,
T: UnitScale = .none,
I: UnitScale = .none,
Tp: UnitScale = .none,
N: UnitScale = .none,
J: UnitScale = .none,
};
/// SI prefix (pico…peta) plus time-unit aliases (min, hour, year).
/// The integer value encodes the exponent for SI prefixes (e.g. `k = 3` → 10³),
/// and the literal factor for time units (e.g. `hour = 3600`).
pub const UnitScale = enum(isize) {
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,
// Imperial Length (Literal factors in meters)
// 1 inch = 0.0254 meters. Since enum backing is isize,
// we use a unique tag and handle the float in getFactor.
inch = -1001,
ft = -1002,
yd = -1003,
mi = -1004,
oz = -1005, // 1 oz = 28.3495231 g
lb = -1006, // 1 lb = 453.59237 g (= 16 oz)
st = -1007, // 1 stone = 6350.29318 g (= 14 lb)
// Undefined
_,
pub fn str(self: @This()) []const u8 {
var buf: [16]u8 = undefined;
return switch (self) {
.none => "",
.P, .T, .G, .M, .k, .h, .da, .d, .c, .m, .u, .n, .p, .f, .min, .year, .inch, .ft, .yd, .mi, .oz, .lb, .st => @tagName(self),
.hour => "h",
else => std.fmt.bufPrint(&buf, "[{d}]", .{@intFromEnum(self)}) catch "[]", // This cannot be inline because of non exhaustive enum, but that's ok, it is just str, not calculation
};
}
pub fn getFactor(self: @This()) comptime_float {
return switch (self) {
// Standard SI Exponents
.P, .T, .G, .M, .k, .h, .da, .none, .d, .c, .m, .u, .n, .p, .f => std.math.pow(f64, 10.0, @floatFromInt(@intFromEnum(self))),
// Time Factors
.min, .hour, .year => @floatFromInt(@intFromEnum(self)),
// Imperial Length (metres)
.inch => 0.0254,
.ft => 0.3048,
.yd => 0.9144,
.mi => 1609.344,
// Imperial Mass (grams — base unit for M is gram, i.e. .none = 1 g)
.oz => 28.3495231,
.lb => 453.59237,
.st => 6350.29318,
else => @floatFromInt(@intFromEnum(self)),
};
}
};
/// Maps each SI base dimension to its `UnitScale`. Stored and resolved entirely at comptime.
const Self = @This();
data: std.EnumArray(Dimension, UnitScale),
/// Create a `Scales` from a struct literal, e.g. `.{ .L = .k, .T = .hour }`.
/// Unspecified dimensions default to `.none` (factor 1).
pub fn init(comptime init_val: ArgOpts) Self {
comptime var s = Self{ .data = std.EnumArray(Dimension, UnitScale).initFill(.none) };
for (std.meta.fields(@TypeOf(init_val))) |f| {
if (comptime @typeInfo(@TypeOf(@field(init_val, f.name))) == .comptime_int)
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 comptime s;
}
pub fn initFill(val: UnitScale) Self {
return .{ .data = std.EnumArray(Dimension, UnitScale).initFill(val) };
}
pub fn get(self: Self, key: Dimension) UnitScale {
return self.data.get(key);
}
pub fn set(self: *Self, key: Dimension, val: UnitScale) void {
self.data.set(key, val);
}
pub fn eql(self: Self, other: Self) bool {
for (self.data.values, other.data.values) |l, r|
if (l != r) return false;
return true;
}
pub fn argsOpt(self: Self) ArgOpts {
var args: ArgOpts = undefined;
for (std.enums.values(Dimension)) |d|
@field(args, @tagName(d)) = self.get(d);
return args;
}
/// Compute the combined scale factor for a given dimension signature.
/// Each dimension's prefix is raised to its exponent and multiplied together.
pub fn getFactor(s: Self, d: Dimensions) comptime_float {
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: comptime_int = 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;
}