1#![cfg_attr(not(feature = "std"), no_std)]
3#![cfg_attr(not(feature = "std"), feature(core_float_math))]
4#![doc = include_str!("../README.md")]
6
7use core::ops;
8
9mod inner;
10mod quantity;
11pub mod si;
12
13pub use crate::quantity::Quantity;
14
15pub struct TenTo<const N: i8>;
21
22pub trait MulPowerOfTen {
24 fn mul_power_of_ten(self, exp: i8) -> Self;
25}
26
27macro_rules! impl_mul_power_of_ten {
28 ($($ty:ty),*) => {
29 $(
30 impl MulPowerOfTen for $ty {
31 fn mul_power_of_ten(self, exp: i8) -> Self {
32 if exp < 0 {
33 self * (10 as $ty).pow(-exp as u32)
34 } else {
35 self / (10 as $ty).pow(exp as u32)
36 }
37 }
38 }
39 )*
40 };
41}
42
43impl_mul_power_of_ten!(i8, i16, i32, i64, isize, u8, u16, u32, u64, u128);
44
45impl MulPowerOfTen for f32 {
46 #[cfg(feature = "std")]
47 fn mul_power_of_ten(self, exp: i8) -> Self {
48 self * 10f32.powi(-exp as i32)
49 }
50
51 #[cfg(not(feature = "std"))]
52 fn mul_power_of_ten(self, exp: i8) -> Self {
53 self * core::f32::math::powi(10.0, -exp as i32)
54 }
55}
56
57impl MulPowerOfTen for f64 {
58 #[cfg(feature = "std")]
59 fn mul_power_of_ten(self, exp: i8) -> Self {
60 self * 10f64.powi(-exp as i32)
61 }
62
63 #[cfg(not(feature = "std"))]
64 fn mul_power_of_ten(self, exp: i8) -> Self {
65 self * core::f64::math::powi(10.0, -exp as i32)
66 }
67}
68
69pub trait Unit {}
71
72macro_rules! power_of_ten_unit_system {
73 ($system:ident { $($unit:ident),* }) => {
74 ::paste::paste! {
75 pub struct [<Typenum $system>]<EXP, $([<$unit:camel>]),*>(core::marker::PhantomData<(EXP, $([<$unit:camel>]),*)>);
76
77 impl<const EXP: i8, $(const [<$unit:upper>]: i8),*> crate::inner::ToConst for [<Typenum $system>]<crate::inner::Const<EXP>, $(crate::inner::Const<{ [<$unit:upper>] }>),*> {
78 type Output = $system<EXP, $({ [<$unit:upper>] }),*>;
79 fn to_const(self) -> Self::Output { $system }
80 }
81
82 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
84 pub struct $system<const EXP: i8, $(const [<$unit:upper>]: i8),*>;
85
86 #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
88 #[allow(non_snake_case)]
89 pub struct[<Runtime $system>] {
90 $(pub $unit: i8),*
91 }
92
93 pub trait [<ToRuntime $system>] {
95 const VALUE: [<Runtime $system>];
96 }
97
98 impl<const EXP: i8, $(const [<$unit:upper>]: i8),*> [<ToRuntime $system>] for $system<EXP, $({ [<$unit:upper>] }),*> {
99 const VALUE: [<Runtime $system>] = [<Runtime $system>] {
100 $($unit: [<$unit:upper>]),*
101 };
102 }
103
104 impl<const EXP: i8, $(const [<$unit:upper>]: i8),*> crate::Unit for $system<EXP, $({ [<$unit:upper>] }),*> {}
105
106 impl<
107 const EXP: i8,
108 const N: i8,
109 $(const [<$unit:upper>]: i8),*
110 > core::ops::Mul<crate::TenTo<{ N }>> for $system<EXP, $({ [<$unit:upper>] }),*>
111 where
112 crate::inner::Const<EXP>: core::ops::Add<crate::inner::Const<N>>,
113 [<Typenum $system>]<
114 <crate::inner::Const<EXP> as core::ops::Add<crate::inner::Const<N>>>::Output,
115 $( crate::inner::Const<{ [<$unit:upper>] }> ),*
116 >: crate::inner::ToConst,
117 {
118 type Output = <[<Typenum $system>]<
119 <crate::inner::Const<EXP> as core::ops::Add<crate::inner::Const<N>>>::Output,
120 $( crate::inner::Const<{ [<$unit:upper>] }> ),*
121 > as crate::inner::ToConst>::Output;
122
123 fn mul(self, _rhs: crate::TenTo<N>) -> Self::Output {
124 crate::inner::ToConst::to_const([<Typenum $system>](core::marker::PhantomData))
125 }
126 }
127
128 impl<
129 const EXP: i8,
130 const N: i8,
131 $(const [<$unit:upper>]: i8),*
132 > core::ops::Div<crate::TenTo<N>> for $system<EXP, $({ [<$unit:upper>] }),*>
133 where
134 crate::inner::Const<EXP>: core::ops::Sub<crate::inner::Const<N>>,
135 [<Typenum $system>]<
136 <crate::inner::Const<EXP> as core::ops::Sub<crate::inner::Const<N>>>::Output,
137 $( crate::inner::Const<{ [<$unit:upper>] }> ),*
138 >: crate::inner::ToConst,
139 {
140 type Output = <[<Typenum $system>]<
141 <crate::inner::Const<EXP> as core::ops::Sub<crate::inner::Const<N>>>::Output,
142 $( crate::inner::Const<{ [<$unit:upper>] }> ),*
143 > as crate::inner::ToConst>::Output;
144
145 fn div(self, _rhs: crate::TenTo<N>) -> Self::Output {
146 crate::inner::ToConst::to_const([<Typenum $system>](core::marker::PhantomData))
147 }
148 }
149
150 impl<
151 const EXP1: i8,
152 const EXP2: i8,
153 $(const [<$unit:upper 1>]: i8, const [<$unit:upper 2>]: i8),*
154 > core::ops::Mul<$system<EXP2, $({ [<$unit:upper 2>] }),*>> for $system<EXP1, $({ [<$unit:upper 1>] }),*>
155 where
156 crate::inner::Const<EXP1>: core::ops::Add<crate::inner::Const<EXP2>>,
157
158 $( crate::inner::Const<{ [<$unit:upper 1>] }>: core::ops::Add<crate::inner::Const<{ [<$unit:upper 2>] }>>, )*
159 [<Typenum $system>]<
160 <crate::inner::Const<EXP1> as core::ops::Add<crate::inner::Const<EXP2>>>::Output,
161 $( <crate::inner::Const<{ [<$unit:upper 1>] }> as core::ops::Add<crate::inner::Const<{ [<$unit:upper 2>] }>>>::Output ),*
162 >: crate::inner::ToConst,
163 {
164 type Output = <[<Typenum $system>]<
165 <crate::inner::Const<EXP1> as core::ops::Add<crate::inner::Const<EXP2>>>::Output,
166 $( <crate::inner::Const<{ [<$unit:upper 1>] }> as core::ops::Add<crate::inner::Const<{ [<$unit:upper 2>] }>>>::Output ),*
167 > as crate::inner::ToConst>::Output;
168
169 fn mul(self, _rhs: $system<EXP2, $({ [<$unit:upper 2>] }),*>) -> Self::Output {
170 crate::inner::ToConst::to_const([<Typenum $system>](core::marker::PhantomData))
171 }
172 }
173
174 impl<
175 const EXP1: i8,
176 const EXP2: i8,
177 $(const [<$unit:upper 1>]: i8, const [<$unit:upper 2>]: i8),*
178 > core::ops::Div<$system<EXP2, $([<$unit:upper 2>]),*>> for $system<EXP1, $([<$unit:upper 1>]),*>
179 where
180 crate::inner::Const<EXP1>: core::ops::Sub<crate::inner::Const<EXP2>>,
181
182 $( crate::inner::Const<[<$unit:upper 1>]>: core::ops::Sub<crate::inner::Const<[<$unit:upper 2>]>>, )*
183 [<Typenum $system>]<
184 <crate::inner::Const<EXP1> as core::ops::Sub<crate::inner::Const<EXP2>>>::Output,
185 $( <crate::inner::Const<[<$unit:upper 1>]> as core::ops::Sub<crate::inner::Const<[<$unit:upper 2>]>>>::Output ),*
186 >: crate::inner::ToConst,
187 {
188 type Output = <[<Typenum $system>]<
189 <crate::inner::Const<EXP1> as core::ops::Sub<crate::inner::Const<EXP2>>>::Output,
190 $( <crate::inner::Const<[<$unit:upper 1>]> as core::ops::Sub<crate::inner::Const<[<$unit:upper 2>]>>>::Output ),*
191 > as crate::inner::ToConst>::Output;
192
193 fn div(self, _rhs: $system<EXP2, $([<$unit:upper 2>]),*>) -> Self::Output {
194 crate::inner::ToConst::to_const([<Typenum $system>](core::marker::PhantomData))
195 }
196 }
197
198 impl<
199 T,
200 const EXP1: i8,
201 const EXP2: i8,
202 $(const [<$unit:upper>]: i8),*
203 > crate::UnitConvert<T, $system<EXP1, $([<$unit:upper>]),*>> for $system<EXP2, $([<$unit:upper>]),*>
204 where
205 T: crate::MulPowerOfTen,
206 {
207 fn unit_convert(val: T) -> T {
208 val.mul_power_of_ten(EXP2 - EXP1)
209 }
210 }
211
212 pub mod base {
214 #![allow(non_camel_case_types)]
215 use super::$system;
216
217 $crate::gen_dimensionless!(@acc $system; $($unit),*;);
219
220 $crate::gen_base_units!(@iter $system; ; $($unit),*);
222 }
223 }
224 }
225}
226pub(crate) use power_of_ten_unit_system;
227
228#[doc(hidden)]
230macro_rules! gen_dimensionless {
231 (@acc $system:ident; ; $($zeros:tt)*) => {
233 pub type dimensionless = $system<0 $($zeros)*>;
234 };
235 (@acc $system:ident; $head:ident $(, $tail:ident)*; $($zeros:tt)*) => {
237 $crate::gen_dimensionless!(@acc $system; $($tail),*; $($zeros)*, 0);
238 };
239}
240pub(crate) use gen_dimensionless;
241
242#[doc(hidden)]
244macro_rules! gen_base_units {
245 (@iter $system:ident; $($before:ident),*;) => {};
247 (@iter $system:ident; ; $current:ident $(, $after:ident)*) => {
249 $crate::gen_one_base!(@zeros_after $system; $current; [, 1]; $($after),*);
251 $crate::gen_base_units!(@iter $system; $current; $($after),*);
252 };
253 (@iter $system:ident; $($before:ident),+; $current:ident $(, $after:ident)*) => {
254 $crate::gen_one_base!(@zeros_before $system; $current; []; $($before),+; $($after),*);
256 $crate::gen_base_units!(@iter $system; $($before,)+ $current; $($after),*);
257 };
258}
259pub(crate) use gen_base_units;
260
261#[doc(hidden)]
263macro_rules! gen_one_base {
264 (@zeros_before $system:ident; $current:ident; [$($acc:tt)*]; $head:ident; $($after:ident),*) => {
266 $crate::gen_one_base!(@zeros_after $system; $current; [$($acc)*, 0, 1]; $($after),*);
268 };
269 (@zeros_before $system:ident; $current:ident; [$($acc:tt)*]; $head:ident, $($tail:ident),+; $($after:ident),*) => {
270 $crate::gen_one_base!(@zeros_before $system; $current; [$($acc)*, 0]; $($tail),+; $($after),*);
272 };
273 (@zeros_after $system:ident; $current:ident; [$($acc:tt)*];) => {
275 pub type $current = $system<0 $($acc)*>;
277 };
278 (@zeros_after $system:ident; $current:ident; [$($acc:tt)*]; $head:ident $(, $tail:ident)*) => {
279 $crate::gen_one_base!(@zeros_after $system; $current; [$($acc)*, 0]; $($tail),*);
281 };
282}
283pub(crate) use gen_one_base;
284
285pub type Mul<A, B> = <A as ops::Mul<B>>::Output;
287pub type Div<A, B> = <A as ops::Div<B>>::Output;
289
290pub trait UnitConvert<T, From>: Unit {
292 fn unit_convert(val: T) -> T;
293}
294
295#[cfg(test)]
296mod tests {
297 extern crate std;
298
299 use super::*;
300 use std::cmp;
301
302 #[test]
307 fn quantity_new_and_deref() {
308 let q: Quantity<i32, si::m> = Quantity::new(42);
309 assert_eq!(*q, 42);
310 }
311
312 #[test]
313 fn quantity_from_trait() {
314 let q: Quantity<i32, si::m> = 42.into();
315 assert_eq!(*q, 42);
316 }
317
318 #[test]
319 fn quantity_equality_and_ordering() {
320 let a: Quantity<i32, si::m> = Quantity::new(5);
321 let b: Quantity<i32, si::m> = Quantity::new(5);
322 let c: Quantity<i32, si::m> = Quantity::new(10);
323 assert_eq!(a, b);
324 assert!(a < c);
325 assert_eq!(a.cmp(&c), cmp::Ordering::Less);
326 }
327
328 #[test]
329 fn quantity_hash() {
330 use std::collections::hash_map::DefaultHasher;
331 use std::hash::{Hash, Hasher};
332
333 let a: Quantity<i32, si::m> = Quantity::new(42);
334 let b: Quantity<i32, si::m> = Quantity::new(42);
335
336 let mut hasher_a = DefaultHasher::new();
337 let mut hasher_b = DefaultHasher::new();
338 a.hash(&mut hasher_a);
339 b.hash(&mut hasher_b);
340 assert_eq!(hasher_a.finish(), hasher_b.finish());
341 }
342
343 #[test]
348 fn add_sub_same_units() {
349 let a: Quantity<i32, si::m> = Quantity::new(10);
350 let b: Quantity<i32, si::m> = Quantity::new(3);
351 assert_eq!(*(a + b), 13);
352 assert_eq!(*(a - b), 7);
353 }
354
355 #[test]
356 fn mul_div_combines_units() {
357 let a: Quantity<i32, si::m> = Quantity::new(6);
359 let b: Quantity<i32, si::m> = Quantity::new(4);
360 let area: Quantity<i32, si::square_meter> = a * b;
361 assert_eq!(*area, 24);
362
363 let c: Quantity<i32, si::m> = Quantity::new(3);
364 let result: Quantity<i32, si::m> = area / c;
365 assert_eq!(*result, 8);
366 }
367
368 #[test]
369 fn division_to_dimensionless() {
370 let a: Quantity<i32, si::m> = Quantity::new(10);
371 let b: Quantity<i32, si::m> = Quantity::new(5);
372 let ratio: Quantity<i32, si::dimensionless> = a / b;
373 assert_eq!(*ratio, 2);
374 }
375
376 #[test]
381 fn mul_power_of_ten_integers() {
382 assert_eq!(5i32.mul_power_of_ten(-3), 5000);
384 assert_eq!(5000i32.mul_power_of_ten(3), 5);
385 assert_eq!(42i32.mul_power_of_ten(0), 42);
386 }
387
388 #[test]
389 fn mul_power_of_ten_floats() {
390 let up = 2.5f64.mul_power_of_ten(-3);
391 let down = 2500.0f64.mul_power_of_ten(3);
392 assert!((2500.0 - up).abs() < f64::EPSILON);
393 assert!((2.5 - down).abs() < f64::EPSILON);
394 }
395
396 #[test]
401 fn conversion_scales_correctly() {
402 let meters: Quantity<i32, si::m> = Quantity::new(3);
404 let mm: Quantity<i32, si::milli<si::m>> = meters.convert();
405 assert_eq!(*mm, 3000);
406
407 let back: Quantity<i32, si::m> = mm.convert();
409 assert_eq!(*back, 3);
410 }
411
412 #[test]
413 fn conversion_f32() {
414 let km: Quantity<f32, si::kilo<si::m>> = Quantity::new(2.5);
415 let m: Quantity<f32, si::m> = km.convert();
416 assert!((2500.0 - *m).abs() < f32::EPSILON);
417 }
418
419 #[test]
420 fn conversion_f64() {
421 let km: Quantity<f64, si::kilo<si::m>> = Quantity::new(2.5);
422 let m: Quantity<f64, si::m> = km.convert();
423 assert!((2500.0 - *m).abs() < f64::EPSILON);
424 }
425
426 #[test]
427 fn conversion_identity() {
428 let m: Quantity<i32, si::m> = Quantity::new(42);
429 let m2: Quantity<i32, si::m> = m.convert();
430 assert_eq!(*m, *m2);
431 }
432
433 #[test]
438 fn velocity_times_time_gives_distance() {
439 let velocity: Quantity<f64, si::meter_per_second> = Quantity::new(10.0);
440 let time: Quantity<f64, si::s> = Quantity::new(5.0);
441 let distance: Quantity<f64, si::m> = velocity * time;
442 assert!((50.0 - *distance).abs() < f64::EPSILON);
443 }
444
445 #[test]
446 fn derived_unit_algebra() {
447 let mass: Quantity<f64, si::kg> = Quantity::new(10.0);
449 let accel: Quantity<f64, si::meter_per_second_squared> = Quantity::new(5.0);
450 let force: Quantity<f64, si::N> = mass * accel;
451 assert!((50.0 - *force).abs() < f64::EPSILON);
452
453 let distance: Quantity<f64, si::m> = Quantity::new(2.0);
455 let energy: Quantity<f64, si::J> = force * distance;
456 assert!((100.0 - *energy).abs() < f64::EPSILON);
457 }
458}