Skip to content

olson-sean-k/decorum

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Decorum

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

GitHub docs.rs crates.io

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 set 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 in numeric operations. 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.

Construction and Conversions

Proxy types are used via constructors and conversions from and into primitive floating-point types and other compatible proxy types. Unlike numeric operations, these functions do not necessarily panic if a constraint is violated.

Method Input Output Violation
new primitive proxy error
assert primitive proxy panic
into_inner proxy primitive n/a
from_subset proxy proxy n/a
into_superset proxy proxy n/a
try_from_slice primitive proxy error
from_slice primitive proxy n/a

The new constructor and into_inner conversion move primitive floating-point values into and out of proxies and are the most basic way to construct and deconstruct proxies. Note that for Total, which has no constraints, the error type is Infallible.

The assert constructor panics if the given primitive floating-point value violates the proxy's constraints. This is equivalent to unwrapping the output of new.

The into_superset and from_subset conversions provide an inexpensive way to convert between proxy types with different but compatible constraints.

Finally, the try_from_slice and Total::from_slice conversions coerce slices of primitive floating-point values into slices of proxies, which have the same representation.

use decorum::R64;

fn f(x: R64) -> R64 {
    x * 3.0
}

let y = R64::assert(3.1459);
let z = f(R64::new(2.7182).unwrap());
let w = z.into_inner();

let xs = [0.0f64, 1.0, 2.0];
let ys = R64::try_from_slice(&xs).unwrap();

All conversions also support the standard From/Into and TryFrom/TryInto traits, which can also be applied to primitives and literals. Reference coercions are supported only via these standard traits; there are no such inherent functions.

use core::convert::{TryFrom, TryInto};
use decorum::R64;

fn f(x: R64) -> R64 {
    x * 2.0
}

let y: R64 = 3.1459.try_into().unwrap();
let z = f(R64::try_from(2.7182).unwrap());
let w: f64 = z.into();
let r: &R64 = (&w).try_into().unwrap();

Hashing and Comparing 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 Standard 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 primitive types like f64 as well as slices like [f64].

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));