Units compared by meaning¶
A Fixed<U, P, T, B> or a Point<O, Fixed<..>> is its number on the wire: the unit U, the scale P, the bound
B and the origin O are in the type only. Changing one of them keeps the bytes, so nothing on the wire tells an old
reader that the number means something else now. The evolution checker (ww api check, the client at connect time)
does: since the API model carries the definition of every unit, scale and origin an API uses
(units in the model, INT-13), it compares them by what they mean, not by
their names (EVO-17). The evolution rules have the short version.
One step of the number¶
The unit and the scale of a Fixed together say what one step of its number is: a dimension and an exact factor in
coherent SI. The checker multiplies them out on both sides and compares the result:
- The same step, spelled differently: compatible, listed as
same value.Fixed<Volt, Milli, i32>->Fixed<Volt, Pow10<-3>, i32>(a prefix and the power of ten it is),Fixed<Volt, Milli, i32>->Fixed<Per<Joule, Coulomb>, Pow10<-3>, i32>(a named unit and its product),Fixed<Per<Meter, Second>, Milli, i32>->Fixed<MeterPerSecond, Milli, i32>,Fixed<Volt, Milli, i32>->Fixed<MilliVolt, One, i32>(a unit of your own with the prefix in it: unit and scale are compared together).
- The same dimension, another factor: breaking, under
Rule::Meaning. The finding gives the factor, exactly and rounded:Fixed<Per<Meter, Second>, One, i32>->Fixed<KmPerHour, One, i32>isthe same bytes in another scale, 1 on the old version is 3.6 (exactly 1·18/5) on the new version, which would read it as 1. - Another dimension (
Fixed<Joule, ..>->Fixed<Newton, ..>): breaking,another dimension.
The Mars Climate Orbiter was lost to the second case: the ground software wrote impulse in lbf·s, the spacecraft read
it as N·s. The same numbers, each 4.45 times too small on the reader. As an API, with the pound-force of ww_si's
customary units (STD-72):
// old version
fn set_impulse(impulse: Fixed<Times<PoundForce, Second>, One, i32>);
// new version
fn set_impulse(impulse: Fixed<Times<Newton, Second>, One, i32>);
The checker reports it in both directions, depending on who writes:
Api::set_impulse: argument `impulse`: U of Fixed: marker units::Times<units::PoundForce, units::Second> on the old
version, units::Times<units::Newton, units::Second> on the new version: the same bytes in another scale, 1 on the old
version is 4.448222 (exactly 1·8896443230521/2000000000000) on the new version, which would read it as 1
and a client in N·s refuses that resource of a device in lbf·s at connect time instead of reading its numbers as they are.
Units and scales of your own¶
A unit or scale your API crate declares itself (exact_units::unit!, exact_units::scale!) is compared by its
definition too, also when only the definition changed and every use kept its name: a MilliVolt defined as 1 V by
mistake and fixed to 10⁻³ V is unit MilliVolt redefined: the same bytes in another scale, both for the marker
itself and for every Fixed that uses it. Its symbol ("mV") is how it is shown, not what it means: a new symbol
alone is compatible. A marker that became another kind of marker under the same name (a unit turned scale) is
breaking.
Units by convention¶
A unit fixed by a physical property, not by a definition (an inch of mercury at 32 °F, a Btu at 60 °F), has a scale
that is the value a source prints: conventional("NIST SP 811", 2008) on its unit! line, its provenance in the
model (STD-72, see imperial and US customary units).
- Its value updated (a newer edition, other digits, the same name): compatible, the same unit. Listed apart as
conventional value updated, with both values and sources:type OwnInHg: 1 inHg = 3386.38 in SI, conventional (NIST SP 811, 2008) -> 1 inHg = 3386.389 in SI, conventional (NIST SP 811, 2024), conventional value updated. The uses of the unit are not reported again. - Defined became conventional, or back: breaking, under
Rule::Meaning. A value by convention is not the unit's definition, and the reader would take one for the other. - Another unit by convention (
InchOfMercury32f->InchOfMercury60f): another unit, compared by its step as above.
Kinds of quantity¶
Some quantities share a dimension and still are not the same: a voltage gain V/V, a current gain A/A, a power gain
W/W and a duty cycle are all dimension one; a transimpedance V/A has the dimension of a resistance Ω (STD-68). A
step of the number has the kind of its unit next to its dimension and factor, by exact_units' rules (its kind
module): a quotient of one dimension keeps what cancelled, Ohm and Siemens are kinds of their own (kind
"resistance" on their unit! lines, a MarkerDef::KindUnit in the model), anything else is plain.
- Another kind of one dimension (
Per<Volt, Volt>->RatioorPer<Ampere, Ampere>,Per<Volt, Ampere>->Ohm): breaking, underRule::Meaning, with both kinds in the finding (plain vs resistance, another kind of quantity). The reader would take one for the other. - Another scale within a kind (
Per<Volt, Volt>inMilliand inPow10<-3>): the same value, as above. - A unit that gains a kind of its own under its name (
Ohmonceexact_unitsgave it one, or a unit of your own): compatible, its values don't change. Listed apart askind plain -> kind resistance, kind of its own added. Another kind under the same name (resistance->impedance) is breaking.
Origins¶
An origin is where a point's zero lies (Celsius: 273.15 K; a register formula of your own). Two origins are the
same when they have the same dimension, the same reference, the same zero and the same period, in coherent SI.
Otherwise the reader would take every point for another position:
- Another name for the same zero: compatible.
Point<Celsius, ..>->Point<OwnCelsius, ..>withOwnCelsiusyour own origin at 273.15 K, shown as°Cor not. - Another zero: breaking.
Point<Celsius, ..>->Point<BoardTemp, ..>withBoardTempat 298.15 K isanother zero (273.15 K vs 298.15 K); so isCelsius->AbsoluteZero. - Another reference: breaking.
Point<Vacuum, Fixed<Pascal, ..>>->Point<Gauge, ..>isanother reference (the unit's zero vs exact_units::AmbientPressure): a gauge pressure is against a pressure only known at run time. - Another period: breaking. An angle that wraps every turn and a
position that doesn't are different things.
Angle<Degree>andAngle<Radian>are the same origin (zero, one turn), so only theFixed's own unit counts there. - Another dimension (
Point<Angle<Degree>, ..>->Point<Celsius, ..>): breaking.
How a point is shown (the origin's symbol, its shown unit) is not compared. The step of a point is its Fixed's, and
is compared as above: Point<Celsius, Fixed<Kelvin, Centi, i16>> -> Point<Celsius, Fixed<Kelvin, Milli, i16>> is
another scale.
Bounds, wrapping and saturating¶
A bound (Range, RangeU64, RangeDec) is compared by value, and any change of it is breaking
for the side that refuses. In the place of a bound, Wrapping and Saturating
(rounding and overflow) say what happens at the ends of the number:
Saturating: the same values with the same meaning asUnbounded: compatible both ways.Wrapping: a position modulo its storage (a binary angle, a free-running counter). On one side only (Unbounded->Wrappingor back) it is breaking, although the bytes are the same: the reader of a wrapping number takes0after65535for the next step, a reader of a plain one for a jump back.- A plain number ->
Fixed<.., Wrapping>orFixed<.., Saturating>: compatible, as any plain number to aFixedof it. Range<..>->Saturating: a bound widened, breaking as such.
Markers compared by name¶
A marker without a definition in the model (a unit struct of your own that is no unit!, scale! or origin!, or
a model saved before INT-13) is still compared by name, with its own arguments. Levels (STD-67), kinds (STD-68) and
sentinels (STD-66) come with rules of their own once they are built; until then a marker of a kind the checker
doesn't know is the same only when its definition is exactly the same, so the checker errs on the breaking side:
- Levels (
Level<Kind, Ref>, dBm, dBV, dBFS): a level is a point against its reference, so another reference (1 mW vs 1 W, full scale of a sine vs a square) is breaking as another zero is; the same reference under another name is compatible. - Kinds that share a dimension (relative humidity vs duty cycle, Hz vs Bq): another kind is breaking, although the dimension and the factor are the same; a kind and its parent only through an explicit cast.
- Sentinels (niche codes in the raw value): a new code inside the old values, a removed one, or a code with another meaning is breaking, since the reader takes the code for a value or the value for a code.
In code¶
The checks live in wire_weaver_evolution::compat::TypeCheck and are the same for ww api check, Check in an
API crate's own test, and wire_weaver_client at connect time. A compatible change with the same meaning is listed
in Report::compatible, ending in same value (for a unit, scale or origin) or same bytes (a transparent wrapper);
compat::Unwrapped::same holds which. The tests in wire_weaver_evolution/src/tests/by_meaning.rs run each case above.