Skip to content
Advanced

Overflow

What happens when a result doesn't fit the storage is a choice. exact_units makes it visible: in the method's name, or in the type.

Three policies

For an \(n\)-bit storage holding \(T_{\min} \dots T_{\max}\):

Policy Result For
checked None values from outside, set points
saturate \(\min(\max(x,\, T_{\min}),\, T_{\max})\) audio, control loops, Q formats
wrap \(T_{\min} + \big((x - T_{\min}) \bmod 2^{n}\big)\) phases, free-running counters, sequence numbers

Saturating keeps the sign and stops at the end. Wrapping keeps the low bits and jumps to the other end. A Q15 sum \(0.75 + b\) for \(b\) from \(-1\) to \(1\):

−1−0.500.51−101b (a = 0.75)a + b in Q15Saturating: stays at 1 − 2⁻¹⁵Wrapping: modulo 2¹⁶ steps
The same i16 addition, two policies. Computed by tools/book_plots with the crate's Q15 and a Wrapping Fixed.

On audio, the wrap is a full-scale click. On a phase, it is exactly right.

The policy of a type

The bounds parameter B can carry the policy. Then plain +, -, * and / follow it:

B + past the end Aliases
Unbounded the storage's own + Micros, Millis, Duty16, ...
Saturating the nearest end Q15, Q31, UQ16_16
Wrapping modulo \(2^{n}\) Bam16, Bam32
use exact_units::{Bam16, Q15, units::Ratio};

// ¾ turn + ½ turn is ¼ turn
assert_eq!((Bam16::of(49152) + Bam16::of(32768)).get(), 16384);
// Q15 stops at 1 − 2⁻¹⁵
assert_eq!((Q15::<Ratio>::of(30000) + Q15::of(10000)).get(), i16::MAX);

Values with a policy are made with Fixed::of (a const fn). new stays Unbounded's, so Fixed::new(a) + Fixed::new(b) keeps inferring its type.

The policy costs nothing: the bytes are the storage's, and the policy picks the instruction (add, or qadd on a Cortex-M4).

Changing the policy

with_overflow gives the same bytes another policy:

use exact_units::{Fixed, Wrapping, prefix::Pow2, units::Turn};

let c = Fixed::<Turn, Pow2<-16>, u16>::new(65535);
let w: Fixed<Turn, Pow2<-16>, u16, Wrapping> = c.with_overflow();
assert_eq!((w + Fixed::of(2)).get(), 1);

Two values of different policies don't add: which policy would the sum have?

use exact_units::{Fixed, Q15, Wrapping, fixed, prefix::Pow2, units::Ratio};

let a: Q15<Ratio> = fixed!(0.5);
let _ = a + Fixed::<Ratio, Pow2<-15>, i16, Wrapping>::of(1);

In conversions

The rounding conversions name their policy too: to_rounded (checked), to_saturating, to_wrapping.

use exact_units::{Fixed, Overflow, Rounding, prefix::{Deci, Milli}, units::Volt};

let big: Fixed<Volt, Milli, i16> = Fixed::new(20_000); // 200 dV
assert_eq!(big.to_rounded::<Deci, i8>(Rounding::NearestEven), None);
assert_eq!(big.to_saturating::<Deci, i8>(Rounding::NearestEven).get(), 127);
assert_eq!(big.to_wrapping::<Deci, i8>(Rounding::NearestEven).get(), -56); // 200 − 256
// both chosen at run time
let r = big.requantize::<Deci, i8>(Rounding::Floor, Overflow::Saturate);
assert_eq!(r.map(|r| r.get()), Some(127));

From a float, an infinity saturates to its end and NaN becomes 0.