Skip to content
Advanced

ww_numeric

ww_numeric holds a number of any type in one value, NumericValue: the storage of every ww_si quantity, so the API fixes the unit and each device picks the number type (a u16 of mV, an f32 of V). NumericBaseType and NumericAnyType describe a number type without a value, for capabilities and introspection.

Number types as features

NumericValue holds any number, and is as big as its biggest variant: 32 bytes on a PC and 24 on a Cortex-M with u128 in it. Each variant except U8 is behind a cargo feature of its name, so firmware enables only what it uses:

# host: std enables every number type
ww_numeric = "0.2"
# firmware: 8 bytes per value, no 64 or 128-bit code
ww_numeric = { version = "0.2", default-features = false, features = ["u16", "f32"] }

Features: nibble, u16, u32, unib32, u64, i32, f32, un, in, u128, i8, i16, i64, i128, f64, uvlq32, uvlq64, and all. ww_si has the same ones and passes them on.

The wire format is the same for every set. A value of a type that is not enabled is read as Error::EnumVariantDisabled; see #[cfg(..)] on enum variants.

Variable-length numbers

For values that are usually small but have no useful bound (counters, lengths, timestamps in ww_si::Second), three variants grow with the value:

Variant Encoding Bytes on the wire, with discriminant
UNib32(UNib32) nibbles, 3 bits each, right after the discriminant 5: 45; 300: 4C D4
UVlq32(UVlq32) bytes, 7 bits each, big endian 5: 82 05; 300: 82 82 2C
UVlq64(UVlq64) same, up to 10 bytes 5: 92 05; u64::MAX: 11 bytes

UVlq32 and UVlq64 have discriminants 16 and 17, which take two nibbles.

Conversions

From<u16>, From<f32>, From<UVlq32>, .. for NumericValue (each behind its type's feature), so APIs can take impl Into<NumericValue>. to_f64() and as_f32() give the value as a float, to_i128() the exact value of an integer variant, unsigned_bits() the width at which an unsigned counter wraps (U32 and UVlq32: 32).

to_integer() gives the exact value of an integer variant as an Integer: a sign and a magnitude in the narrowest unsigned type that holds every enabled number type (Magnitude: u32 with types up to 32 bits, u64 with u64 / i64 / uvlq64, u128 with u128 / i128). It compares and scales by powers of ten exactly across number types (cmp_pow10, checked_scale_pow10, exact_scale_pow10), adds and multiplies with overflow checks, and goes back into any number type with NumericBaseType::from_integer or wrapping with NumericValue::wrapping_add. On a firmware with 32-bit numbers this is all u32 code, where to_i128() would bring 128-bit arithmetic: ww_si's comparisons and conversions are built on it.