#ordering #nan #float #finite #hash

no-std decorum

Total ordering, equivalence, hashing, and constraints for floating-point types

19 releases

0.3.1 May 31, 2020
0.2.0 May 4, 2020
0.1.3 Jan 11, 2019
0.1.2 Dec 1, 2018
0.0.7 Nov 15, 2017

#199 in Rust patterns

Download history 4496/week @ 2023-11-20 6564/week @ 2023-11-27 7343/week @ 2023-12-04 7092/week @ 2023-12-11 4916/week @ 2023-12-18 1915/week @ 2023-12-25 4719/week @ 2024-01-01 7066/week @ 2024-01-08 6235/week @ 2024-01-15 7669/week @ 2024-01-22 6791/week @ 2024-01-29 8232/week @ 2024-02-05 8592/week @ 2024-02-12 8363/week @ 2024-02-19 8852/week @ 2024-02-26 7696/week @ 2024-03-04

33,719 downloads per month
Used in 48 crates (22 directly)

MIT license

90KB
2.5K SLoC

Decorum

Decorum is a Rust library that provides total ordering, equivalence, hashing, and constraints for floating-point representations. Decorum does not require std.

CI Documentation Crate

Total Ordering

The following total ordering is exposed via traits for primitive types and proxy types that implement Ord:

[ -INF < ... < 0 < ... < +INF < NaN ]

IEEE-754 floating-point encoding provides multiple representations of zero (-0 and +0) and NaN. This ordering considers all zero and NaN representations equal, which differs from the standard partial ordering.

Some proxy types disallow unordered NaN values and therefore support a total ordering based on the ordered subset of non-NaN floating-point values (see below).

Proxy Types

Decorum exposes several proxy (wrapper) types. Proxy types provide two primary features: they implement total ordering and equivalence via the Eq, Ord, and Hash traits and they constrain the class of floating-point values they can represent. Different type definitions apply different constraints, with the Total type applying no constraints at all.

Type Aliases Trait Implementations Disallowed Values
Total Encoding + Real + Infinite + Nan + Float
NotNan N32, N64 Encoding + Real + Infinite NaN
Finite R32, R64 Encoding + Real NaN, -INF, +INF

Proxy types implement common operation traits, such as Add and Mul. These types also implement numeric traits from the num-traits crate (such as Float, Num, NumCast, etc.), in addition to more targeted traits like Real and Nan provided by Decorum.

Constraint violations cause panics. For example, NotNan is useful for avoiding or tracing sources of NaNs in computation, while Total provides useful features without introducing any panics at all, because it allows any IEEE-754 floating-point values.

Proxy types should work as a drop-in replacement for primitive types in most applications with the most common exception being initialization (because it requires a conversion). Serialization is optionally supported with serde and approximate comparisons are optionally supported with approx via the serialize-serde and approx features, respectively.

Traits

Traits are essential for generic programming, but the constraints used by some proxy types prevent them from implementing the Float trait, because it implies the presence of -INF, +INF, and NaN (and their corresponding trait implementations).

Decorum provides more granular traits that separate these APIs: Real, Infinite, Nan, and Encoding. Primitive floating-point types implement all of these traits and proxy types implement traits that are consistent with their constraints.

For example, code that wishes to be generic over floating-point types representing real numbers and infinities can use a bound on the Infinite and Real traits:

use decorum::{Infinite, Real};

fn f<T>(x: T, y: T) -> T
where
    T: Infinite + Real,
{
    let z = x / y;
    if z.is_infinite() {
        y
    }
    else {
        z
    }
}

Both Decorum and num-traits provide Real and Float traits. These traits are somewhat different and are not always interchangeable. Traits from both crates are implemented by Decorum where possible. For example, Total implements Float from both Decorum and num-traits.

Conversions

Proxy types are used via conversions to and from primitive floating-point types and other proxy types.

Conversion Input Output Violation
from_inner primitive proxy panic
into_inner proxy primitive n/a
from_subset proxy proxy n/a
into_superset proxy proxy n/a

The from_inner and into_inner conversions move primitive floating-point values into and out of proxies. The into_superset and from_subset conversions provide an inexpensive way to convert between proxy types with different but compatible constraints. All conversions also support the standard From and Into traits, which can also be applied to literals:

use decorum::R32;

fn f(x: R32) -> R32 {
    x * 2.0
}
let y: R32 = 3.1459.into();
let z = f(2.7182.into());
let w: f32 = z.into();

Primitives

Proxy types implement Eq, Hash, and Ord, but sometimes it is not possible or ergonomic to use such a type. Traits can be used with primitive floating-point values for ordering, equivalence, and hashing instead.

Floating-Point Trait General Trait
FloatEq Eq
FloatHash Hash
FloatOrd Ord

These traits use the same total ordering and equivalence rules that proxy types do. They are implemented for base types as well as slices:

use decorum::cmp::FloatEq;

let x = 0.0f64 / 0.0f64; // `NaN`.
let y = f64::INFINITY + f64::NEG_INFINITY; // `NaN`.
assert!(x.float_eq(&y));

let xs = [1.0f64, f64::NAN, f64::INFINITY];
let ys = [1.0f64, f64::NAN, f64::INFINITY];
assert!(xs.float_eq(&ys));

Dependencies

~135–370KB