2 Commits
Author SHA1 Message Date
adrien c2675f5117 Added imperial to README 2026-04-23 00:28:49 +02:00
adrien f26f6086ca Added Imperial unit scales 2026-04-23 00:14:57 +02:00
3 changed files with 109 additions and 29 deletions
+25 -19
View File
@@ -14,7 +14,7 @@ Started by a space simulation where `i128` positions were needed to avoid float
- **100% comptime** — all dimension and unit tracking happens at compile time. No added memory, *almost* native performance.
- **Compile-time dimension errors** — adding `Meter` to `Second` is a compile error, not a runtime panic.
- **Automatic unit conversion** — use `.to()` to convert between compatible units (e.g. `km/h` → `m/s`). Scale factors are resolved at comptime.
- **Full SI prefix support** — `pico`, `nano`, `micro`, `milli`, `centi`, `deci`, `kilo`, `mega`, `giga`, `tera`, `peta`, and more.
* **Full SI prefix & Imperial support** — `pico` through `peta`, plus common Imperial units like `inch`, `ft`, `mi`, `lb`, and `oz`.
- **Time scale support** — `min`, `hour`, `year` built in.
- **Scalar and Vector types** — operate on individual values or fixed-size arrays with the same dimensional safety.
- **Built-in physical quantities** — `dma.Base` provides ready-made types for `Velocity`, `Acceleration`, `Force`, `Energy`, `Pressure`, `ElectricCharge`, `ThermalConductivity`, and many more.
@@ -140,6 +140,18 @@ const speed_ms = speed_kmh.to(Velocity); // 33.333... m/s — comptime ratio
// const bad = speed_kmh.to(Second); // "Dimension mismatch in to: L1T-1 vs T1"
```
#### Imperial
```zig
const Inch = Scalar(f64, .{ .L = 1 }, .{ .L = .inch });
const Mile = Scalar(f64, .{ .L = 1 }, .{ .L = .mi });
const Pound = Scalar(f64, .{ .M = 1 }, .{ .M = .lb });
// Conversion example
const dist_m = Meter{ .value = 1609.344 };
const dist_mi = dist_m.to(Mile); // Result: 1.0
```
### Arithmetic with bare numbers
Passing a `comptime_int`, `comptime_float`, or plain `T` to `mul` / `div` treats it as a dimensionless value. Dimensions pass through unchanged.
@@ -292,25 +304,19 @@ Call `.Of(T)` for base-unit scalars, `.Scaled(T, scales)` for custom scales:
`Meter`, `Second`, `Gramm`, `Kelvin`, `ElectricCurrent`, `Speed`, `Acceleration`, `Inertia`, `Force`, `Pressure`, `Energy`, `Power`, `Area`, `Volume`, `Density`, `Frequency`, `Viscosity`, `ElectricCharge`, `ElectricPotential`, `ElectricResistance`, `MagneticFlux`, `ThermalCapacity`, `ThermalConductivity`, and more.
### `Scales` — SI prefixes
### `Scales` — SI and Imperial Units
| Tag | Factor |
|---|---|
| `.P` | 10¹⁵ |
| `.T` | 10¹² |
| `.G` | 10⁹ |
| `.M` | 10⁶ |
| `.k` | 10³ |
| `.none` | 1 |
| `.c` | 10⁻² |
| `.m` | 10⁻³ |
| `.u` | 10⁻⁶ |
| `.n` | 10⁻⁹ |
| `.p` | 10⁻¹² |
| `.f` | 10⁻¹⁵ |
| `.min` | 60 |
| `.hour` | 3600 |
| `.year` | 31 536 000 |
| Tag | Factor (Relative to Base) | Type |
|---|---|---|
| `.P` ... `.f` | $10^{15}$ ... $10^{-15}$ | SI Prefixes |
| `.min`, `.hour`, `.year` | 60, 3600, 31,536,000 | Time |
| **`.inch`** | **0.0254** | Imperial Length (m) |
| **`.ft`** | **0.3048** | Imperial Length (m) |
| **`.yd`** | **0.9144** | Imperial Length (m) |
| **`.mi`** | **1609.344** | Imperial Length (m) |
| **`.oz`** | **28.3495231** | Imperial Mass (g) |
| **`.lb`** | **453.59237** | Imperial Mass (g) |
| **`.st`** | **6350.29318** | Imperial Mass (g) |
Scale entries for dimensions with exponent `0` are ignored — multiplying a dimensionless value by a kilometre-scale value no longer accidentally inherits the `k` prefix.
+55
View File
@@ -749,6 +749,61 @@ test "add/sub bare number on dimensionless scalar" {
try std.testing.expectEqual(7, c.value);
}
test "Imperial length scales" {
const Foot = Scalar(f64, .{ .L = 1 }, .{ .L = .ft });
const Meter = Scalar(f64, .{ .L = 1 }, .{});
const Inch = Scalar(f64, .{ .L = 1 }, .{ .L = .inch });
const CentiMeter = Scalar(f64, .{ .L = 1 }, .{ .L = .c });
const Mile = Scalar(f64, .{ .L = 1 }, .{ .L = .mi });
const KiloMeter = Scalar(f64, .{ .L = 1 }, .{ .L = .k });
const Yard = Scalar(f64, .{ .L = 1 }, .{ .L = .yd });
// 1 ft → 0.3048 m
const one_ft = Foot{ .value = 1.0 };
try std.testing.expectApproxEqAbs(0.3048, one_ft.to(Meter).value, 1e-9);
// 12 in → 1 ft
const twelve_in = Inch{ .value = 12.0 };
try std.testing.expectApproxEqAbs(1.0, twelve_in.to(Foot).value, 1e-9);
// 1 in → 2.54 cm
const one_in = Inch{ .value = 1.0 };
try std.testing.expectApproxEqAbs(2.54, one_in.to(CentiMeter).value, 1e-9);
// 1 mi → 1.609344 km
const one_mi = Mile{ .value = 1.0 };
try std.testing.expectApproxEqAbs(1.609344, one_mi.to(KiloMeter).value, 1e-9);
// 3 ft → 1 yd
const three_ft = Foot{ .value = 3.0 };
try std.testing.expectApproxEqAbs(1.0, three_ft.to(Yard).value, 1e-9);
}
test "Imperial mass scales" {
const Pound = Scalar(f64, .{ .M = 1 }, .{ .M = .lb });
const KiloGram = Scalar(f64, .{ .M = 1 }, .{ .M = .k });
const Ounce = Scalar(f64, .{ .M = 1 }, .{ .M = .oz });
const Stone = Scalar(f64, .{ .M = 1 }, .{ .M = .st });
// 1 lb → ~0.453592 kg
const one_lb = Pound{ .value = 1.0 };
try std.testing.expectApproxEqAbs(0.45359237, one_lb.to(KiloGram).value, 1e-6);
// 16 oz → 1 lb
const sixteen_oz = Ounce{ .value = 16.0 };
try std.testing.expectApproxEqAbs(1.0, sixteen_oz.to(Pound).value, 1e-6);
// 1 stone → 14 lb
const one_st = Stone{ .value = 1.0 };
try std.testing.expectApproxEqAbs(14.0, one_st.to(Pound).value, 1e-4);
// 2 lb + 8 oz → 2.5 lb
const two_lb = Pound{ .value = 2.0 };
const eight_oz = Ounce{ .value = 8.0 };
const total = two_lb.add(eight_oz).to(Pound);
try std.testing.expectApproxEqAbs(2.5, total.value, 1e-6);
}
test "comparisons with comptime_int on dimensionless scalar" {
const DimLess = Scalar(i128, .{}, .{});
const x = DimLess{ .value = 42 };
+29 -10
View File
@@ -41,6 +41,18 @@ pub const UnitScale = enum(isize) {
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
_,
@@ -48,24 +60,31 @@ pub const UnitScale = enum(isize) {
var buf: [16]u8 = undefined;
return switch (self) {
inline .none => "",
inline .P, .T, .G, .M, .k, .h, .da, .d, .c, .m, .u, .n, .p, .f, .min, .hour, .year => @tagName(self),
inline .P, .T, .G, .M, .k, .h, .da, .d, .c, .m, .u, .n, .p, .f, .min, .hour, .year, .inch, .ft, .yd, .mi, .oz, .lb, .st => @tagName(self),
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
};
}
/// Helper to get the actual scaling factor
pub inline fn getFactor(self: @This()) comptime_float {
return comptime switch (self) {
// Standard SI Exponents
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 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),
// Time Factors
inline .min, .hour, .year => @floatFromInt(@intFromEnum(self)),
// Imperial Length (metres)
inline .inch => 0.0254,
inline .ft => 0.3048,
inline .yd => 0.9144,
inline .mi => 1609.344,
// Imperial Mass (grams — base unit for M is gram, i.e. .none = 1 g)
inline .oz => 28.3495231,
inline .lb => 453.59237,
inline .st => 6350.29318,
inline else => @floatFromInt(@intFromEnum(self)),
};
}
};