1use std::fmt;
18
19#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
24#[repr(usize)]
25pub enum BaseDim {
26 Length = 0,
27 Mass,
28 Time,
29 Current,
30 Temperature,
31 Amount,
32 Luminous,
33 Angle,
34 SolidAngle,
35 Information,
36}
37
38impl BaseDim {
39 pub const COUNT: usize = 10;
41
42 pub const ALL: [BaseDim; BaseDim::COUNT] = [
44 BaseDim::Length,
45 BaseDim::Mass,
46 BaseDim::Time,
47 BaseDim::Current,
48 BaseDim::Temperature,
49 BaseDim::Amount,
50 BaseDim::Luminous,
51 BaseDim::Angle,
52 BaseDim::SolidAngle,
53 BaseDim::Information,
54 ];
55
56 #[inline]
58 pub const fn index(self) -> usize {
59 self as usize
60 }
61
62 pub const fn symbol(self) -> &'static str {
64 match self {
65 BaseDim::Length => "L",
66 BaseDim::Mass => "M",
67 BaseDim::Time => "T",
68 BaseDim::Current => "I",
69 BaseDim::Temperature => "Θ",
70 BaseDim::Amount => "N",
71 BaseDim::Luminous => "J",
72 BaseDim::Angle => "rad",
73 BaseDim::SolidAngle => "sr",
74 BaseDim::Information => "bit",
75 }
76 }
77}
78
79const fn igcd(mut a: i32, mut b: i32) -> i32 {
81 a = a.abs();
82 b = b.abs();
83 while b != 0 {
84 let t = b;
85 b = a % b;
86 a = t;
87 }
88 a
89}
90
91#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
95pub struct Exp {
96 num: i32,
97 den: i32,
98}
99
100impl Exp {
101 pub const ZERO: Exp = Exp { num: 0, den: 1 };
103 pub const ONE: Exp = Exp { num: 1, den: 1 };
105
106 pub fn new(num: i32, den: i32) -> Exp {
108 assert!(den != 0, "exponent denominator must be nonzero");
109 let (mut num, mut den) = (num, den);
110 if den < 0 {
111 num = -num;
112 den = -den;
113 }
114 if num == 0 {
115 return Exp::ZERO;
116 }
117 let g = igcd(num, den);
118 Exp { num: num / g, den: den / g }
119 }
120
121 #[inline]
123 pub const fn int(n: i32) -> Exp {
124 Exp { num: n, den: 1 }
125 }
126
127 pub const fn numerator(self) -> i32 {
128 self.num
129 }
130
131 pub const fn denominator(self) -> i32 {
132 self.den
133 }
134
135 #[inline]
136 pub const fn is_zero(self) -> bool {
137 self.num == 0
138 }
139
140 pub fn add(self, other: Exp) -> Exp {
142 Exp::new(self.num * other.den + other.num * self.den, self.den * other.den)
143 }
144
145 pub fn sub(self, other: Exp) -> Exp {
147 Exp::new(self.num * other.den - other.num * self.den, self.den * other.den)
148 }
149
150 pub fn neg(self) -> Exp {
152 Exp { num: -self.num, den: self.den }
153 }
154
155 pub fn scale(self, k: i32) -> Exp {
157 Exp::new(self.num * k, self.den)
158 }
159
160 pub fn div_int(self, k: i32) -> Exp {
162 Exp::new(self.num, self.den * k)
163 }
164}
165
166impl fmt::Display for Exp {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 if self.den == 1 {
169 write!(f, "{}", self.num)
170 } else {
171 write!(f, "{}/{}", self.num, self.den)
172 }
173 }
174}
175
176#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
180pub struct Dimension {
181 e: [Exp; BaseDim::COUNT],
182}
183
184impl Dimension {
185 pub const DIMENSIONLESS: Dimension = Dimension { e: [Exp::ZERO; BaseDim::COUNT] };
187
188 pub fn base(d: BaseDim) -> Dimension {
190 let mut e = [Exp::ZERO; BaseDim::COUNT];
191 e[d.index()] = Exp::ONE;
192 Dimension { e }
193 }
194
195 pub fn from_exps(e: [Exp; BaseDim::COUNT]) -> Dimension {
199 Dimension { e }
200 }
201
202 #[inline]
204 pub fn exponent(self, d: BaseDim) -> Exp {
205 self.e[d.index()]
206 }
207
208 pub fn is_dimensionless(self) -> bool {
210 self.e.iter().all(|x| x.is_zero())
211 }
212
213 pub fn mul(self, other: Dimension) -> Dimension {
215 let mut e = self.e;
216 for (i, slot) in e.iter_mut().enumerate() {
217 *slot = slot.add(other.e[i]);
218 }
219 Dimension { e }
220 }
221
222 pub fn div(self, other: Dimension) -> Dimension {
224 let mut e = self.e;
225 for (i, slot) in e.iter_mut().enumerate() {
226 *slot = slot.sub(other.e[i]);
227 }
228 Dimension { e }
229 }
230
231 pub fn recip(self) -> Dimension {
233 let mut e = self.e;
234 for slot in e.iter_mut() {
235 *slot = slot.neg();
236 }
237 Dimension { e }
238 }
239
240 pub fn powi(self, k: i32) -> Dimension {
242 let mut e = self.e;
243 for slot in e.iter_mut() {
244 *slot = slot.scale(k);
245 }
246 Dimension { e }
247 }
248
249 pub fn nth_root(self, k: i32) -> Dimension {
251 let mut e = self.e;
252 for slot in e.iter_mut() {
253 *slot = slot.div_int(k);
254 }
255 Dimension { e }
256 }
257
258 pub fn length() -> Dimension { Dimension::base(BaseDim::Length) }
260 pub fn mass() -> Dimension { Dimension::base(BaseDim::Mass) }
261 pub fn time() -> Dimension { Dimension::base(BaseDim::Time) }
262 pub fn current() -> Dimension { Dimension::base(BaseDim::Current) }
263 pub fn temperature() -> Dimension { Dimension::base(BaseDim::Temperature) }
264 pub fn amount() -> Dimension { Dimension::base(BaseDim::Amount) }
265 pub fn luminous() -> Dimension { Dimension::base(BaseDim::Luminous) }
266 pub fn angle() -> Dimension { Dimension::base(BaseDim::Angle) }
267 pub fn solid_angle() -> Dimension { Dimension::base(BaseDim::SolidAngle) }
268 pub fn information() -> Dimension { Dimension::base(BaseDim::Information) }
269
270 pub fn area() -> Dimension { Self::length().powi(2) }
272 pub fn volume() -> Dimension { Self::length().powi(3) }
273 pub fn speed() -> Dimension { Self::length().div(Self::time()) }
274 pub fn acceleration() -> Dimension { Self::speed().div(Self::time()) }
275 pub fn frequency() -> Dimension { Self::time().recip() }
276 pub fn force() -> Dimension { Self::mass().mul(Self::acceleration()) }
277 pub fn energy() -> Dimension { Self::force().mul(Self::length()) }
278 pub fn power() -> Dimension { Self::energy().div(Self::time()) }
279 pub fn pressure() -> Dimension { Self::force().div(Self::area()) }
280 pub fn charge() -> Dimension { Self::current().mul(Self::time()) }
281 pub fn voltage() -> Dimension { Self::power().div(Self::current()) }
282 pub fn resistance() -> Dimension { Self::voltage().div(Self::current()) }
283 pub fn conductance() -> Dimension { Self::resistance().recip() } pub fn capacitance() -> Dimension { Self::charge().div(Self::voltage()) } pub fn magnetic_flux() -> Dimension { Self::voltage().mul(Self::time()) } pub fn inductance() -> Dimension { Self::magnetic_flux().div(Self::current()) } pub fn magnetic_flux_density() -> Dimension { Self::magnetic_flux().div(Self::area()) } pub fn surface_tension() -> Dimension { Self::force().div(Self::length()) } pub fn density() -> Dimension { Self::mass().div(Self::volume()) }
290 pub fn absorbed_dose() -> Dimension { Self::energy().div(Self::mass()) } pub fn radioactivity() -> Dimension { Self::frequency() } pub fn exposure() -> Dimension { Self::charge().div(Self::mass()) } pub fn catalytic_activity() -> Dimension { Self::amount().div(Self::time()) } pub fn luminous_flux() -> Dimension { Self::luminous().mul(Self::solid_angle()) } pub fn illuminance() -> Dimension { Self::luminous_flux().div(Self::area()) } pub fn luminance() -> Dimension { Self::luminous().div(Self::area()) } pub fn data_rate() -> Dimension { Self::information().div(Self::time()) } pub fn volumetric_flow() -> Dimension { Self::volume().div(Self::time()) } pub fn molar_concentration() -> Dimension { Self::amount().div(Self::volume()) } pub fn molality() -> Dimension { Self::amount().div(Self::mass()) } pub fn dynamic_viscosity() -> Dimension { Self::pressure().mul(Self::time()) } pub fn kinematic_viscosity() -> Dimension { Self::area().div(Self::time()) } pub fn fuel_economy() -> Dimension { Self::length().div(Self::volume()) } pub fn by_name(name: &str) -> Option<Dimension> {
312 let n = name.trim().to_ascii_lowercase();
313 Some(match n.as_str() {
314 "dimensionless" | "scalar" | "number" => Dimension::DIMENSIONLESS,
315 "length" | "distance" => Self::length(),
317 "mass" => Self::mass(),
318 "time" | "duration" => Self::time(),
319 "current" | "electriccurrent" | "electric current" => Self::current(),
320 "temperature" => Self::temperature(),
321 "amount" | "amountofsubstance" => Self::amount(),
322 "luminous" | "luminousintensity" => Self::luminous(),
323 "angle" | "planeangle" => Self::angle(),
324 "solidangle" | "solid angle" => Self::solid_angle(),
325 "information" | "data" => Self::information(),
326 "area" => Self::area(),
328 "volume" => Self::volume(),
329 "speed" | "velocity" => Self::speed(),
330 "acceleration" => Self::acceleration(),
331 "frequency" => Self::frequency(),
332 "force" | "weight" => Self::force(),
333 "energy" | "work" | "heat" => Self::energy(),
334 "power" => Self::power(),
335 "pressure" | "stress" => Self::pressure(),
336 "charge" | "electriccharge" => Self::charge(),
337 "voltage" | "potential" | "emf" => Self::voltage(),
338 "resistance" => Self::resistance(),
339 "conductance" => Self::conductance(),
340 "capacitance" => Self::capacitance(),
341 "inductance" => Self::inductance(),
342 "magneticflux" | "magnetic flux" => Self::magnetic_flux(),
343 "magneticfluxdensity" | "fluxdensity" => Self::magnetic_flux_density(),
344 "density" => Self::density(),
345 "surfacetension" | "surface tension" => Self::surface_tension(),
346 "datarate" | "data rate" | "bandwidth" => Self::data_rate(),
347 "flow" | "volumetricflow" | "volumetric flow" => Self::volumetric_flow(),
348 "concentration" | "molarconcentration" | "molarity" => Self::molar_concentration(),
349 "molality" => Self::molality(),
350 "absorbeddose" | "dose" => Self::absorbed_dose(),
351 "radioactivity" | "activity" => Self::radioactivity(),
352 "exposure" => Self::exposure(),
353 "catalyticactivity" | "catalysis" => Self::catalytic_activity(),
354 "luminousflux" => Self::luminous_flux(),
355 "illuminance" => Self::illuminance(),
356 "luminance" => Self::luminance(),
357 "fueleconomy" | "fuel economy" => Self::fuel_economy(),
358 _ => return None,
359 })
360 }
361}
362
363impl fmt::Display for Dimension {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365 if self.is_dimensionless() {
366 return write!(f, "1");
367 }
368 let mut first = true;
369 for d in BaseDim::ALL {
370 let x = self.exponent(d);
371 if x.is_zero() {
372 continue;
373 }
374 if !first {
375 write!(f, "·")?;
376 }
377 first = false;
378 if x == Exp::ONE {
379 write!(f, "{}", d.symbol())?;
380 } else {
381 write!(f, "{}^{}", d.symbol(), x)?;
382 }
383 }
384 Ok(())
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn dimension_by_name_resolves_the_named_dimensions() {
394 assert_eq!(Dimension::by_name("Length"), Some(Dimension::length()));
395 assert_eq!(Dimension::by_name("length"), Some(Dimension::length())); assert_eq!(Dimension::by_name("Mass"), Some(Dimension::mass()));
397 assert_eq!(Dimension::by_name("Area"), Some(Dimension::area()));
398 assert_eq!(Dimension::by_name("Volume"), Some(Dimension::volume()));
399 assert_eq!(Dimension::by_name("Speed"), Some(Dimension::speed()));
400 assert_eq!(Dimension::by_name("Velocity"), Some(Dimension::speed())); assert_eq!(Dimension::by_name("Force"), Some(Dimension::force()));
402 assert_eq!(Dimension::by_name("Energy"), Some(Dimension::energy()));
403 assert_ne!(Dimension::by_name("Area"), Dimension::by_name("Volume"));
405 assert_ne!(Dimension::by_name("Length"), Dimension::by_name("Mass"));
406 assert_eq!(Dimension::by_name("Frobnicate"), None);
407 }
408
409 struct Rng(u64);
411 impl Rng {
412 fn next(&mut self) -> u64 {
413 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
414 let mut z = self.0;
415 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
416 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
417 z ^ (z >> 31)
418 }
419 fn dim(&mut self) -> Dimension {
421 let mut e = [Exp::ZERO; BaseDim::COUNT];
422 for slot in e.iter_mut() {
423 *slot = Exp::int((self.next() % 7) as i32 - 3);
424 }
425 Dimension { e }
426 }
427 }
428
429 #[test]
434 fn exp_reduces_and_normalizes_sign() {
435 assert_eq!(Exp::new(2, 4), Exp::new(1, 2));
436 assert_eq!(Exp::new(-2, 4), Exp::new(1, -2)); assert_eq!(Exp::new(0, 5), Exp::ZERO);
438 assert_eq!(Exp::new(6, 3), Exp::int(2));
439 assert!(!Exp::new(1, 2).denominator().is_negative());
440 }
441
442 #[test]
443 fn exp_arithmetic_is_exact_rational() {
444 assert_eq!(Exp::new(1, 2).add(Exp::new(1, 2)), Exp::ONE); assert_eq!(Exp::new(1, 3).add(Exp::new(1, 3)).add(Exp::new(1, 3)), Exp::ONE);
446 assert_eq!(Exp::int(2).sub(Exp::new(1, 2)), Exp::new(3, 2));
447 assert_eq!(Exp::new(1, 2).scale(4), Exp::int(2));
448 assert_eq!(Exp::int(2).div_int(2), Exp::ONE); assert_eq!(Exp::ONE.div_int(2), Exp::new(1, 2)); assert_eq!(Exp::new(2, 3).neg(), Exp::new(-2, 3));
451 }
452
453 #[test]
454 fn exp_displays_as_int_or_fraction() {
455 assert_eq!(Exp::int(2).to_string(), "2");
456 assert_eq!(Exp::int(-3).to_string(), "-3");
457 assert_eq!(Exp::new(1, 2).to_string(), "1/2");
458 assert_eq!(Exp::new(-1, 2).to_string(), "-1/2");
459 }
460
461 #[test]
466 fn base_dimensions_are_distinct() {
467 let bases: Vec<Dimension> = BaseDim::ALL.iter().map(|&d| Dimension::base(d)).collect();
468 for (i, a) in bases.iter().enumerate() {
469 for (j, b) in bases.iter().enumerate() {
470 assert_eq!(a == b, i == j, "base {i} vs {j}");
471 }
472 assert!(!a.is_dimensionless());
473 }
474 assert!(Dimension::DIMENSIONLESS.is_dimensionless());
475 }
476
477 #[test]
478 fn multiplication_adds_exponents() {
479 assert_eq!(Dimension::length().mul(Dimension::length()), Dimension::area());
481 assert_eq!(Dimension::length().powi(3), Dimension::volume());
482 assert_eq!(Dimension::area().mul(Dimension::length()), Dimension::volume());
484 }
485
486 #[test]
487 fn division_subtracts_exponents() {
488 assert_eq!(Dimension::area().div(Dimension::length()), Dimension::length());
490 assert!(Dimension::length().div(Dimension::length()).is_dimensionless());
491 assert_eq!(Dimension::length().div(Dimension::time()), Dimension::speed());
493 }
494
495 #[test]
496 fn reciprocal_is_the_group_inverse() {
497 assert_eq!(Dimension::time().recip(), Dimension::frequency());
499 for d in BaseDim::ALL {
501 let x = Dimension::base(d);
502 assert!(x.mul(x.recip()).is_dimensionless());
503 }
504 assert_eq!(Dimension::force().recip().recip(), Dimension::force());
506 }
507
508 #[test]
509 fn powers_and_roots_are_inverse() {
510 assert!(Dimension::length().powi(0).is_dimensionless());
511 assert_eq!(Dimension::length().powi(-1), Dimension::length().recip());
512 assert_eq!(Dimension::area().nth_root(2), Dimension::length());
514 assert_eq!(Dimension::volume().nth_root(3), Dimension::length());
515 let root_hz = Dimension::frequency().nth_root(2);
517 assert_eq!(root_hz.exponent(BaseDim::Time), Exp::new(-1, 2));
518 assert_eq!(root_hz.powi(2), Dimension::frequency());
519 }
520
521 #[test]
522 fn derived_dimensions_compose_correctly() {
523 let force = Dimension::force();
525 assert_eq!(force.exponent(BaseDim::Mass), Exp::int(1));
526 assert_eq!(force.exponent(BaseDim::Length), Exp::int(1));
527 assert_eq!(force.exponent(BaseDim::Time), Exp::int(-2));
528 let energy = Dimension::energy();
530 assert_eq!(energy, Dimension::force().mul(Dimension::length()));
531 assert_eq!(energy.exponent(BaseDim::Length), Exp::int(2));
532 assert_eq!(energy.exponent(BaseDim::Time), Exp::int(-2));
533 assert_eq!(Dimension::power().exponent(BaseDim::Time), Exp::int(-3));
535 let p = Dimension::pressure();
537 assert_eq!(p.exponent(BaseDim::Length), Exp::int(-1));
538 assert_eq!(p.exponent(BaseDim::Mass), Exp::int(1));
539 assert_eq!(Dimension::charge(), Dimension::current().mul(Dimension::time()));
541 assert_eq!(Dimension::resistance(), Dimension::voltage().div(Dimension::current()));
542 assert_eq!(Dimension::density().exponent(BaseDim::Length), Exp::int(-3));
544 }
545
546 #[test]
547 fn all_extended_derived_dimensions_have_their_canonical_signatures() {
548 use BaseDim::*;
549 let cases: &[(Dimension, &[(BaseDim, Exp)])] = &[
552 (Dimension::absorbed_dose(), &[(Length, Exp::int(2)), (Time, Exp::int(-2))]), (Dimension::radioactivity(), &[(Time, Exp::int(-1))]), (Dimension::exposure(), &[(Current, Exp::int(1)), (Time, Exp::int(1)), (Mass, Exp::int(-1))]), (Dimension::catalytic_activity(), &[(Amount, Exp::int(1)), (Time, Exp::int(-1))]), (Dimension::luminous_flux(), &[(Luminous, Exp::int(1)), (SolidAngle, Exp::int(1))]), (Dimension::illuminance(), &[(Luminous, Exp::int(1)), (SolidAngle, Exp::int(1)), (Length, Exp::int(-2))]), (Dimension::luminance(), &[(Luminous, Exp::int(1)), (Length, Exp::int(-2))]), (Dimension::data_rate(), &[(Information, Exp::int(1)), (Time, Exp::int(-1))]), (Dimension::volumetric_flow(), &[(Length, Exp::int(3)), (Time, Exp::int(-1))]), (Dimension::molar_concentration(), &[(Amount, Exp::int(1)), (Length, Exp::int(-3))]), (Dimension::molality(), &[(Amount, Exp::int(1)), (Mass, Exp::int(-1))]), (Dimension::dynamic_viscosity(), &[(Mass, Exp::int(1)), (Length, Exp::int(-1)), (Time, Exp::int(-1))]), (Dimension::kinematic_viscosity(), &[(Length, Exp::int(2)), (Time, Exp::int(-1))]), (Dimension::fuel_economy(), &[(Length, Exp::int(-2))]), ];
570 for (dim, expected) in cases {
571 for axis in BaseDim::ALL {
572 let want = expected.iter().find(|(a, _)| *a == axis).map(|(_, e)| *e).unwrap_or(Exp::int(0));
573 assert_eq!(dim.exponent(axis), want, "{} exponent on {:?}", dim, axis);
574 }
575 }
576 assert_eq!(Dimension::absorbed_dose(), Dimension::energy().div(Dimension::mass()));
579 assert_eq!(Dimension::radioactivity(), Dimension::frequency()); assert_eq!(Dimension::data_rate(), Dimension::information().div(Dimension::time()));
581 assert_eq!(Dimension::volumetric_flow(), Dimension::volume().div(Dimension::time()));
582 assert_eq!(Dimension::molar_concentration(), Dimension::amount().div(Dimension::volume()));
583 assert_eq!(Dimension::dynamic_viscosity(), Dimension::pressure().mul(Dimension::time()));
584 assert_eq!(Dimension::kinematic_viscosity(), Dimension::area().div(Dimension::time()));
585 assert_eq!(Dimension::fuel_economy(), Dimension::length().div(Dimension::volume()));
586 assert_eq!(Dimension::illuminance(), Dimension::luminous_flux().div(Dimension::area()));
587 assert_eq!(Dimension::kinematic_viscosity(), Dimension::dynamic_viscosity().div(Dimension::density()));
589 assert_eq!(Dimension::conductance(), Dimension::resistance().recip()); assert!(Dimension::conductance().mul(Dimension::resistance()).is_dimensionless()); assert_eq!(Dimension::capacitance(), Dimension::charge().div(Dimension::voltage())); assert_eq!(Dimension::magnetic_flux(), Dimension::voltage().mul(Dimension::time())); assert_eq!(Dimension::inductance(), Dimension::magnetic_flux().div(Dimension::current())); assert_eq!(Dimension::magnetic_flux_density(), Dimension::magnetic_flux().div(Dimension::area())); assert_eq!(Dimension::surface_tension(), Dimension::force().div(Dimension::length())); assert_eq!(Dimension::energy(), Dimension::charge().mul(Dimension::voltage()));
599 }
600
601 #[test]
602 fn display_renders_the_signature() {
603 assert_eq!(Dimension::DIMENSIONLESS.to_string(), "1");
604 assert_eq!(Dimension::length().to_string(), "L");
605 assert_eq!(Dimension::area().to_string(), "L^2");
606 assert_eq!(Dimension::frequency().to_string(), "T^-1");
607 assert_eq!(Dimension::speed().to_string(), "L·T^-1");
608 assert_eq!(Dimension::force().to_string(), "L·M·T^-2");
610 }
611
612 #[test]
613 fn dimension_is_copy_and_hashes_by_value() {
614 use std::collections::HashSet;
615 let a = Dimension::force();
616 let b = a; assert_eq!(a, b);
618 let mut set = HashSet::new();
619 set.insert(Dimension::force());
620 assert!(set.contains(&Dimension::mass().mul(Dimension::acceleration())));
621 assert!(!set.contains(&Dimension::energy()));
622 }
623
624 #[test]
625 fn dimensions_form_an_abelian_group_under_fuzz() {
626 let mut r = Rng(0x_D1E5_10_4ABE_1234);
629 let id = Dimension::DIMENSIONLESS;
630 for _ in 0..4000 {
631 let (a, b, c) = (r.dim(), r.dim(), r.dim());
632 assert_eq!(a.mul(b), b.mul(a), "× commutes");
634 assert_eq!(a.mul(b).mul(c), a.mul(b.mul(c)), "× associates");
635 assert_eq!(a.mul(id), a, "1 is the identity");
637 assert!(a.mul(a.recip()).is_dimensionless(), "x · x⁻¹ = 1");
638 assert_eq!(a.div(b), a.mul(b.recip()), "a / b = a · b⁻¹");
640 assert!(a.div(a).is_dimensionless(), "a / a = 1");
641 assert_eq!(a.powi(2).mul(a.powi(3)), a.powi(5), "exponent law");
643 assert_eq!(a.mul(b).powi(2), a.powi(2).mul(b.powi(2)), "power distributes over ×");
644 assert_eq!(a.powi(2).nth_root(2), a, "(a²) √2 = a");
645 assert_eq!(a.powi(0), id, "a⁰ = 1");
646 assert_eq!(a.recip().recip(), a);
648 }
649 }
650}