4 releases (stable)
1.0.2 | Sep 13, 2024 |
---|---|
1.0.1 | Apr 2, 2024 |
1.0.0 | Mar 9, 2024 |
0.7.4 | Feb 18, 2024 |
0.1.23 |
|
#151 in Data structures
1,525 downloads per month
Used in 2 crates
300KB
4.5K
SLoC
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 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::Eq
orcore::hash::Hash
on floats.
The 12 types provided by this crate
And their positive and negative counterparts:
Positive
,PositiveFinite
,StrictlyPositive
,StrictlyPositiveFinite
Negative
,NegativeFinite
,StrictlyNegative
,StrictlyNegativeFinite
(Negatives types reject +0.0
and positives types reject -0.0
)
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
x
is neitherNaN
norInfinity
, and is strictly positive - For the user: the result is not
NaN
and is strictly positive but may beInfinity
In that example:
- the person implementing the API doesn't have to check for
NaN
,Infinity
, or<= 0
for the parameterx
- the user only have to check the result for
Infinity
if 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 TypedFloat 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,
f32
orf64
) - From
f32
andf64
- From integers types (except
u128
andi128
) - 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)
Comparaisons: 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) -> Ordering
- sin_cos(self) -> (f64, f64)
- mul_add(self, a: f64, b: f64) -> f64
- clamp(self, min: f64, max: f64) -> f64
- LowerExp
- UpperExp
- Product
- Sum
to_int_unchecked
to*_bits
from*_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
This crate is designed to have a minimal overhead at runtime, in terms of memory, speed and binary size.
It may even be faster than using primitives f32
and f64
directly, as it may avoids some checks by using compiler hints.
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.
Features
std
: enabled by default, gives allf32
andf64
methods.serde
: implementsSerialize
andDeserialize
for all 12 types.libm
: use theFloat
trait fromnum-traits
andlibm
to implement the missing methods when thestd
feature is disabled. When bothstd
andlibm
features are enabled, thestd
implementation is used.compiler_hints
: enabled by default, will addcore::hint::unreachable_unchecked
after alldebug_assert
.ensure_no_undefined_behavior
: Willpanic!
in release mode instead of risking undefined behavior. This will override thecompiler_hints
feature, 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.0
instead ofNaN
min(-0.0, 0.0)
returning-0.0
instead of0.0
(same formax
)frac(-0.0)
returning0.0
instead 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 weekly on GitHub actions.
The minimum supported Rust version (MSRV) is 1.70.0 because of the use of dep:
in Cargo.toml
.
Testing
Tests are run on different architectures on GitHub actions and CircleCI.
To run all tests:
git clone https://github.com/tdelmas/typed_floats
# generate the published documentation, including some tests
cargo xtask pre-build
cd typed_floats
cargo test --all
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::Ord
andcore::hash::Hash
implementations for variousnum
types (u32
,f64
,num_bigint::BigInt
, etc.) - ordered-float Provides several wrapper types for Ord and Eq implementations on f64 and friends.
- partial-min-max
min
andmax
functions 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
Full documentation
Is on docs.rs.
Dependencies
~0.2–0.8MB
~19K SLoC