Arithmetic¶
Within one type¶
Two values of one type add and subtract. A plain number of the storage multiplies and divides:
use exact_units::{Fixed, prefix::Milli, units::Volt};
type MilliVolts = Fixed<Volt, Milli, i32>;
let a = MilliVolts::new(1200);
let b = MilliVolts::new(300);
assert_eq!(a + b, MilliVolts::new(1500));
assert_eq!(a - b, MilliVolts::new(900));
assert_eq!(a * 2, MilliVolts::new(2400));
assert_eq!(a / 4, MilliVolts::new(300));
+ and * overflow as the storage's own operators do: a panic in a debug build, a wrap in a release build. When that matters, say what should happen:
| Method | On overflow | For |
|---|---|---|
checked_add, checked_mul, ... |
None |
values from outside |
saturating_add, ... |
the nearest end | levels, set points |
wrapping_add, ... |
modulo \(2^{\text{bits}}\) | free-running counters |
use exact_units::{Fixed, prefix::Milli, units::Volt};
let v: Fixed<Volt, Milli, i16> = Fixed::new(30_000);
assert_eq!(v.checked_add(Fixed::new(5_000)), None);
assert_eq!(v.saturating_add(Fixed::new(5_000)).get(), i16::MAX);
A type can carry its policy instead, so + itself saturates or wraps: Overflow.
Different scales don't mix¶
mV and V are different types. Adding them is a compile error, not a silent factor of 1000:
use exact_units::{Fixed, prefix::{Milli, One}, units::Volt};
let a: Fixed<Volt, Milli, i32> = Fixed::new(1200);
let b: Fixed<Volt, One, i32> = Fixed::new(1);
let _ = a + b;
Convert one side first, with to.
Products and quotients of two values¶
Two Fixed values multiply into a new unit and a new scale, both worked out by the compiler:
graph LR
V["Fixed<Volt, Milli, i32><br/>3300 mV"] --> P(("·"))
A["Fixed<Ampere, Milli, i32><br/>250 mA"] --> P
P --> VA["Fixed<Times<Volt, Ampere>, Product<Milli, Milli>, i32><br/>825000 µ(V·A)"]
VA -- "convert" --> W["Fixed<Watt, Micro, i32><br/>825000 µW"]
At run time that is one integer multiply. The scale of the product is exact: \(10^{-3} \cdot 10^{-3} = 10^{-6}\).
use exact_units::{Fixed, prefix::{Micro, Milli}, units::{Ampere, Volt, Watt}};
let v: Fixed<Volt, Milli, i32> = Fixed::new(3300);
let i: Fixed<Ampere, Milli, i32> = Fixed::new(250);
let p = v * i;
assert_eq!(p.to_string(), "825000 µ(V·A)");
assert_eq!(p.convert::<Watt, Micro, i32>(), Some(Fixed::new(825_000)));
convert moves to a unit of the same dimension: V·A to W, V/Ω to A. A quotient works the same way, and the classic slip of a factor 1000 becomes a type error:
use exact_units::{Fixed, prefix::Milli, units::{Ampere, Ohm, Volt}};
let v: Fixed<Volt, Milli, i32> = Fixed::new(10);
let r: Fixed<Ohm, Milli, i32> = Fixed::new(1);
let _: Fixed<Ampere, Milli, i32> = v / r; // mV / mΩ is A, not mA
When the product doesn't fit the storage, or should land in another scale, the multiply is named: Products and requantization.
Comparing¶
==, < and sort compare the stored numbers of one type. Values with different bounds but the same unit, scale and storage compare too.