Expand description
This crate helps you to ensure the kind of floats you are using, without panic! (except if the unsafe function new_unchecked is used in an unsound way).
zero overhead: everything is checked at compile time.
(only new and try_from adds a little overhead at runtime)
NaN is rejected by all types.
§TL;DR
This crate is for you if:
-
If you want to know at compile time if a float can be negative, positive, zero, finite and ensure it is not
NaN, withoutpanic!. -
If you need
core::cmp::Ord,core::cmp::Eqorcore::hash::Hashon (non-NaN) floats.
§The 12 types provided by this crate
And their positive and negative counterparts:
Positive,PositiveFinite,StrictlyPositive,StrictlyPositiveFiniteNegative,NegativeFinite,StrictlyNegative,StrictlyNegativeFinite
| Type | -∞ | ]-∞; -0.0[ | -0.0 | +0.0 | ]+0.0; +∞[ | +∞ | NaN |
|---|---|---|---|---|---|---|---|
NonNaN | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
NonNaNFinite | ❌ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
NonZeroNonNaN | ✔️ | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
NonZeroNonNaNFinite | ❌ | ✔️ | ❌ | ❌ | ✔️ | ❌ | ❌ |
Positive | ❌ | ❌ | ❌ | ✔️ | ✔️ | ✔️ | ❌ |
PositiveFinite | ❌ | ❌ | ❌ | ✔️ | ✔️ | ❌ | ❌ |
StrictlyPositive | ❌ | ❌ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
StrictlyPositiveFinite | ❌ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ |
Negative | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
NegativeFinite | ❌ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
StrictlyNegative | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ |
StrictlyNegativeFinite | ❌ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ |
To avoid specifying the kind of float (e.g. like Positive<f32>), you can use the modules tf64 and tf32 which expose aliases.
§When to use it
§When handling floats
When you handle floats, this crate can help you to ensure that you are not using NaN or Infinity by mistake. Methods and functions implemented returns a type as strict as possible, so you know when you really have to check for NaN or Infinity.
§When writing a library
Using one of the type provided by this crate in your public API can help your users to avoid mistakes and limits the checks your functions have to do.
It also helps to make API simpler as you don’t have to handle and document all the possible cases with NaN and Infinity for example.
E.g. the following function:
fn fast_inv_sqrt(x: StrictlyPositiveFinite) -> StrictlyPositive;It ensures:
- For the person implementing the API: the parameter
xis neitherNaNnorInfinity, and is strictly positive - For the user: the result is not
NaNand is strictly positive but may beInfinity
In that example:
- the person implementing the API doesn’t have to check for
NaN,Infinity, or<= 0for the parameterx - the user only have to check the result for
Infinityif they want to handle it differently and can’t call the function with an invalid parameter.
§API
Most methods and traits available on the underlying type are available on the types of this crate.
Most constants are also available, with the most appropriate typed float type (except NAN for obvious reasons) in the tf64 and tf32 modules (in tf64::consts and tf32::consts respectively when the constant comes from core::f64::consts or core::f32::consts). Those modules are named that way to avoid conflicts or confusion with the primitives f32 and f64.
⚠️ Like for primitives f32 and f64,-0.0 == +0.0 is true for all types of this crate.
To facilitate comparisons, the methods is_positive_zero and is_negative_zero are added.
§Traits implemented
§Conversions: core::convert::From / core::convert::TryFrom
- Between all the types of this crate (of the same kind,
f32orf64) - From
f32andf64 - From integers types (except
u128andi128) - From
NonZero*(core::num::NonZeroU8,core::num::NonZeroU16,core::num::NonZeroU32,core::num::NonZeroU64,core::num::NonZeroI8,core::num::NonZeroI16,core::num::NonZeroI32,core::num::NonZeroI64)
(The traits From and TryFrom are implemented depending on the situation)
§Comparisons: core::cmp::PartialOrd and core::cmp::PartialEq
| 🗘 | f32/f64 | NonNaN | NonNaNFinite | NonZeroNonNaN | NonZeroNonNaNFinite | Positive | PositiveFinite | StrictlyPositive | StrictlyPositiveFinite | Negative | NegativeFinite | StrictlyNegative | StrictlyNegativeFinite |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
f32/f64 | N/A | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
NonNaN | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
NonNaNFinite | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
NonZeroNonNaN | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
NonZeroNonNaNFinite | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
Positive | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
PositiveFinite | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
StrictlyPositive | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
StrictlyPositiveFinite | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
Negative | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
NegativeFinite | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
StrictlyNegative | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
StrictlyNegativeFinite | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
§Traits without generic parameters
| Trait | NonNaN | NonNaNFinite | NonZeroNonNaN | NonZeroNonNaNFinite | Positive | PositiveFinite | StrictlyPositive | StrictlyPositiveFinite | Negative | NegativeFinite | StrictlyNegative | StrictlyNegativeFinite |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
core::cmp::Eq | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
core::cmp::Ord | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
core::hash::Hash | ✔️¹ | ✔️¹ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
core::default::Default | 0.0 | 0.0 | ❌ | ❌ | 0.0 | 0.0 | ❌ | ❌ | -0.0 | -0.0 | ❌ | ❌ |
¹: there is a (small) overhead because they accept 0.0 and -0.0 (which are equal) so they must core::hash::Hash to the same value.
§Methods implemented
All 12 types implement the methods available on f32 and f64 except:
- deprecated and nightly-only methods
total_cmp(&self, other: &f64) -> Orderingsin_cos(self) -> (f64, f64)mul_add(self, a: f64, b: f64) -> f64clamp(self, min: f64, max: f64) -> f64LowerExpUpperExpProductSumto_int_uncheckedto*_bitsfrom*_bits
§Panics
The only method that can panic! is the unsafe method new_unchecked when used in an invalid way.
A panic! triggered in any other way is considered a security bug and should be reported.
§Minimal overhead and optimizations
This crate is designed to have a minimal overhead at runtime, in terms of memory, speed and binary size.
It can even be faster than using primitives f32 and f64 directly, as it may avoids some checks by using compiler hints and can use some faster implementations in some cases.
§Overhead
The only methods that adds a little overhead are try_from because of the checks they do at runtime, compared to the unsafe method new_unchecked.
In debug mode, a little overhead is present, both to check the validity of the values and because inline may not be respected.
Any other overhead is considered a bug and should be reported.
§Compiler optimizations
The compiler hints are enabled by default to enable compiler optimization when possible.
Also, some methods are faster than the default implementation. For example:
- When possible
Eqis implemented by comparing the bits of the two floats instead of the slower default implementation, that had special cases forNaN(to handleNaN != NaN) and-0.0(to handle-0.0 == 0.0). (8% faster) Ordis implemented by directly comparing the bits of the two floats instead of the slower default implementation for negatives and positives types. (4% faster)signumdoesn’t needs to check forNaN. (35% faster)- types others than
NonNaNandNonNaNFiniteuse a faster implementation ofEq, opening the door to Jump Threading optimisations.
§Features
std: enabled by default, gives allf32andf64methods.serde: implementsSerializeandDeserializefor all 12 types.libm: use theFloattrait fromnum-traitsandlibmto implement the missing methods when thestdfeature is disabled. When bothstdandlibmfeatures are enabled, thestdimplementation is used.compiler_hints: enabled by default, will addcore::hint::unreachable_uncheckedafter alldebug_assert.ensure_no_undefined_behavior: Willpanic!in release mode instead of risking undefined behavior. This will override thecompiler_hintsfeature, and adds a little overhead tonew_unchecked. This feature can be enabled by any parent crate to ensure no undefined behavior.
§How it works
For each operation, at compile time crate determine the most strict type possible for the result.
For example, if you multiply a PositiveFinite and a StrictlyNegativeFinite, the result will be a Negative.
Methods that takes another float as parameter will also return the most strict type possible depending on the both types. For the methods where a trait is not available to specify the return type depending on the parameter type, a new trait is created:
Hypot, Min, Max, Copysign, DivEuclid and Atan2.
§Main limitations
- Doesn’t fix the floating point quirks such as
0.0 == -0.0 - Doesn’t fix the odd methods such as:
sqrt(-0.0)returning-0.0instead ofNaNmin(-0.0, 0.0)possibly returning0.0instead of-0.0(same formax)frac(-0.0)returning0.0instead of-0.0
Because that would introduce a runtime overhead and may introduce some incompatibilities with existing code.
§Rust version
This crate is tested when a new version is release with:
- Rust beta
- Rust stable
- Rust 1.70.0
Also, tests on nightly, beta and stable are run monthly on GitHub actions.
The minimum supported Rust version (MSRV) is 1.70.0 because of the use of dep: in Cargo.toml.
A change in the MSRV will be treated as a breaking change.
§Testing
Tests are run on different architectures on GitHub actions and CircleCI.
§Similar crates
- checked-float A crate for making invariant-enforcing floating point wrappers
- decorum Decorum is a Rust library that provides total ordering, equivalence, hashing, and constraints for floating-point representations. Decorum does not require std.
- eq-float Float wrappers with a total order (by setting NAN == NAN).
- fix_float Fixed floating types that allows useful trait implementations and datastructures on float numbers
- float-derive A crate that allows deriving Eq and Hash for types that contain floating points.
- float-ord A total ordering for floating-point numbers.
- nanbox NaN boxing implementation.
- noisy_float Contains floating point types that panic if they are set to an illegal value, such as NaN.
- num-order Numerically consistent
core::cmp::Eq,core::cmp::Ordandcore::hash::Hashimplementations for variousnumtypes (u32,f64,num_bigint::BigInt, etc.) - ordered-float Provides several wrapper types for Ord and Eq implementations on f64 and friends.
- partial-min-max
minandmaxfunctions that work withPartialOrd. - real_float Floating point types that check for correctness and implement total ordering.
- result_float Floating point type that cannot store NaN.
- totally-ordered No dependency, no-std totally ordered f32/f64
- unsigned-f64 A wrapper around f64 that guarantees that the value is always non-negative on the type level.
Features provided/checked by those crates:
✔️: provided, ❌: not provided, ❓: unknown
(you may need to scroll to the right to see all the columns: “Production ready”, “Avoid panic!”, “Minimal overhead”, “Eq/Ord”, “Hash”, “NaN”, “Inf”, “Zero”, “Positive”, “Negative”)
| Crates | Production ready | Avoid panic! | Minimal overhead | Eq/Ord | Hash | NaN | Inf | Zero | Positive | Negative |
|---|---|---|---|---|---|---|---|---|---|---|
typed_floats | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
checked-float | ✔️ | ✔️ | ❌ | ✔️ | ❌ | ✔️¹ | ✔️¹ | ✔️¹ | ✔️¹ | ✔️¹ |
decorum | ✔️ | ❌ | ❌ | ❌ | ❌ | ✔️¹ | ✔️¹ | ✔️¹ | ✔️¹ | ✔️¹ |
eq-float | ❌ | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
fix_float | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ |
float-derive | ❌ | ❓ | ❓ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ |
float-ord | ✔️ | ✔️ | ❌ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
nanbox | ❌ | ❓ | ❓ | ❌ | ❌ | ✔️ | ❌ | ❌ | ❌ | ❌ |
noisy_float | ✔️ | ❌ | ❌ | ❌ | ❌ | ✔️ | ✔️ | ❌ | ❌ | ❌ |
num-order | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ |
ordered-float | ✔️ | ❌ | ❌ | ✔️ | ❌ | ✔️ | ❌ | ❌ | ❌ | ❌ |
partial-min-max | ❌ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
real_float | ✔️ | ❌ | ❌ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | ❌ |
result_float | ✔️ | ✔️ | ❌ | ✔️ | ❌ | ✔️ | ❌ | ❌ | ❌ | ❌ |
totally-ordered | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ |
unsigned-f64 | ❌ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ | ❌ |
(N.B. “Production ready” is a subjective measure)
¹: Can be manually checked
§Rules
Conversions rules for operations are summarized in conversions_rules.
§Examples
Operations will return the strictest type possible.
use typed_floats::*;
let a: StrictlyPositiveFinite = 1.0f64.try_into().unwrap();
let b: StrictlyNegativeFinite = (-1.0f64).try_into().unwrap();
let c: StrictlyPositive = a - b;
let d: NonNaNFinite = a + b;
assert_eq!(c, 2.0);
assert_eq!(d, 0.0); use typed_floats::*;
let a: StrictlyPositiveFinite = 1.0f64.try_into().unwrap();
let b: Positive = 0.0f64.try_into().unwrap();
let c: StrictlyPositive = a + b;
assert_eq!(c, 1.0); Operations that assign the result to the left operand are only implemented when it is safe to do so:
use typed_floats::*;
let mut a: StrictlyPositive = f64::MAX.try_into().unwrap();
let b: StrictlyPositive = f64::MAX.try_into().unwrap();
a += b;// Would not compile with StrictlyPositiveFinite
assert_eq!(a, f64::INFINITY);use typed_floats::*;
let mut a: StrictlyPositiveFinite = f64::MAX.try_into().unwrap();
let b: StrictlyPositiveFinite = f64::MAX.try_into().unwrap();
a += b;// Does not compile
assert_eq!(a, f64::INFINITY);Conversions from non-zero integers are available:
use typed_floats::*;
use core::num::NonZeroU64;
let a = NonZeroU64::new(1).unwrap();
let b: StrictlyPositive = a.into(); // no need for try_into
assert_eq!(b, 1.0);Also, comparison between types is available:
use typed_floats::*;
let a: f64 = 1.0;
let b: StrictlyPositive = 1.0.try_into().unwrap();
let c: StrictlyPositiveFinite = 1.0.try_into().unwrap();
assert_eq!(a, b);
assert_eq!(b, a);
assert_eq!(b, c);To return early in a function:
use typed_floats::*;
fn early_return(a:f64,b:f64) -> Result<PositiveFinite,InvalidNumber> {
let a: StrictlyPositiveFinite = a.try_into()?;
let b: StrictlyPositiveFinite = b.try_into()?;
Ok(a % b)
}
assert_eq!(early_return(-1.0,2.0), Err(InvalidNumber::Negative));
assert_eq!(early_return(1.0,2.0).unwrap().get(), 1.0);Modules§
- conversions_
rules - When the result may be
Nan, the result type isf64. - tf32
- This module contains constants from
core::f32, casted to the corresponding type - tf64
- This module contains constants from
core::f64, casted to the corresponding type
Macros§
- as_
const - Macro to create a constant value. Will panic at compile time if the value is not a valid.
- assert_
float_ eq - This macros assert that the two value are equal:
- assert_
is_ nan - This macros assert that the value is NaN.
- assert_
is_ negative_ zero - This macros assert that the value is negative zero.
- assert_
is_ positive_ zero - This macros assert that the value is positive zero.
- assert_
relative_ eq - This macros assert that two values are close to each other.
- generate_
const - Macro to create a constant. It will generate the full declaration of the constant:
Structs§
- Negative
- A non-NaN negative floating point number
- Negative
Finite - A non-NaN negative finite floating point number
- NonNaN
- A non-NaN floating point number
- NonNaN
Finite - A non-NaN finite floating point number different from zero
- NonZero
NonNaN - A non-NaN floating point number different from zero
- NonZero
NonNaN Finite - A non-NaN finite floating point number different from zero
- Positive
- A non-NaN positive floating point number
- Positive
Finite - A non-NaN positive finite floating point number
- Strictly
Negative - A non-NaN strictly negative floating point number
- Strictly
Negative Finite - A non-NaN strictly negative finite floating point number
- Strictly
Positive - A non-NaN strictly positive floating point number
- Strictly
Positive Finite - A non-NaN strictly positive finite floating point number
Enums§
- From
StrError - An error that can occur when converting from a string into a typed float
- Invalid
Number - An error that can occur when converting into a typed float
Traits§
- Atan2
- This trait is used to specify the return type of the
Atan2::atan2()function. - Copysign
- This trait is used to specify the return type of the
Copysign::copysign()function. - DivEuclid
- This trait is used to specify the return type of the
DivEuclid::div_euclid()function. - Hypot
- This trait is used to specify the return type of the
Hypot::hypot()function. - Max
- This trait is used to specify the return type of the
Max::max()function. - Midpoint
- This trait is used to specify the return type of the
Midpoint::midpoint()function. - Min
- This trait is used to specify the return type of the
Min::min()function. - Powf
- This trait is used to specify the return type of the
Powf::powf()function.