decorum-0.4.0/.cargo_vcs_info.json0000644000000001360000000000100124730ustar { "git": { "sha1": "d25892e626104d689e8f2923cc41e4abfff5cded" }, "path_in_vcs": "" }decorum-0.4.0/.github/dependabot.yml000064400000000000000000000003131046102023000154500ustar 00000000000000version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "daily" ignore: - dependency-name: "*" update-types: ["version-update:semver-patch"] decorum-0.4.0/.github/workflows/continuous-integration.yml000064400000000000000000000030121046102023000221260ustar 00000000000000name: CI on: [pull_request, push] jobs: rustfmt: name: Format runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: nightly override: true components: rustfmt - uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check clippy: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true components: clippy - run: rustup component add clippy - uses: actions-rs/cargo@v1 with: command: clippy args: --all-features --all-targets -- -D clippy::all test: name: Test needs: [clippy, rustfmt] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macOS-latest, ubuntu-latest, windows-latest] toolchain: - 1.70.0 # Minimum. - stable - beta steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} override: true - uses: actions-rs/cargo@v1 with: command: test args: --no-default-features --verbose - uses: actions-rs/cargo@v1 with: command: test args: --all-features --verbose decorum-0.4.0/.gitignore000064400000000000000000000000551046102023000132530ustar 00000000000000/target/ /.idea/ **/*.rs.bk *.iml Cargo.lock decorum-0.4.0/Cargo.toml0000644000000036750000000000100105040ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70.0" name = "decorum" version = "0.4.0" authors = ["Sean Olson "] build = "build.rs" autobins = false autoexamples = false autotests = false autobenches = false description = "Total ordering, equivalence, hashing, and constraints for floating-point types." readme = "README.md" keywords = [ "finite", "float", "hash", "nan", "ordering", ] categories = [ "mathematics", "no-std", "rust-patterns", ] license = "MIT" repository = "https://github.com/olson-sean-k/decorum" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--html-in-header", "doc/katex-header.html", ] [lib] name = "decorum" path = "src/lib.rs" [dependencies.approx] version = "^0.5.0" features = [] optional = true default-features = false [dependencies.num-traits] version = "^0.2.0" features = [] default-features = false [dependencies.serde] version = "1.0" optional = true default-features = false [dependencies.serde_derive] version = "1.0" optional = true default-features = false [dependencies.thiserror] version = "^2.0.0" default-features = false [dev-dependencies.num] version = "^0.4.0" [dev-dependencies.serde_json] version = "1.0" [build-dependencies.rustversion] version = "^1.0.3" [features] default = [ "approx", "serde", "std", ] serde = [ "dep:serde", "dep:serde_derive", ] std = [ "approx/std", "num-traits/std", "serde/std", "thiserror/std", ] unstable = [] decorum-0.4.0/Cargo.toml.orig000064400000000000000000000025131046102023000141530ustar 00000000000000[package] name = "decorum" version = "0.4.0" edition = "2021" rust-version = "1.70.0" license = "MIT" readme = "README.md" authors = ["Sean Olson "] repository = "https://github.com/olson-sean-k/decorum" description = "Total ordering, equivalence, hashing, and constraints for floating-point types." keywords = [ "finite", "float", "hash", "nan", "ordering", ] categories = [ "mathematics", "no-std", "rust-patterns", ] [package.metadata.docs.rs] all-features = true # Enable KaTeX support. rustdoc-args = [ "--html-in-header", "doc/katex-header.html", ] [features] default = [ "approx", "serde", "std", ] serde = [ "dep:serde", "dep:serde_derive", ] std = [ "approx/std", "num-traits/std", "serde/std", "thiserror/std", ] unstable = [] [dependencies.approx] version = "^0.5.0" default-features = false features = [] optional = true [dependencies.num-traits] version = "^0.2.0" default-features = false features = [] [dependencies.serde] version = "1.0" default-features = false optional = true [dependencies.serde_derive] version = "1.0" default-features = false optional = true [dependencies.thiserror] version = "^2.0.0" default-features = false [build-dependencies] rustversion = "^1.0.3" [dev-dependencies] num = "^0.4.0" serde_json = "1.0" decorum-0.4.0/LICENSE000064400000000000000000000020261046102023000122700ustar 00000000000000The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. decorum-0.4.0/README.md000064400000000000000000000315631046102023000125520ustar 00000000000000
Decorum

**Decorum** is a Rust library that provides total ordering, equivalence, hashing, constraints, error handling, and more for IEEE 754 floating-point representations. Decorum does **not** require the `std` nor `alloc` libraries, though they are necessary for some features. [![GitHub](https://img.shields.io/badge/GitHub-olson--sean--k/decorum-8da0cb?logo=github&style=for-the-badge)](https://github.com/olson-sean-k/decorum) [![docs.rs](https://img.shields.io/badge/docs.rs-decorum-66c2a5?logo=rust&style=for-the-badge)](https://docs.rs/decorum) [![crates.io](https://img.shields.io/crates/v/decorum.svg?logo=rust&style=for-the-badge)](https://crates.io/crates/decorum) ## Basic Usage Panic when a `NaN` is encountered: ```rust use decorum::NotNan; let x = NotNan::::assert(0.0); let y = NotNan::::assert(0.0); let z = x / y; // Panics. ``` Hash totally ordered IEEE 754 floating-point representations: ```rust use decorum::real::UnaryRealFunction; use decorum::Real; use std::collections::HashMap; let key = Real::::PI; let mut xs: HashMap<_, _> = [(key, "pi")].into_iter().collect(); ``` Configure the behavior of an IEEE 754 floating-point representation: ```rust pub mod real { use decorum::constraint::IsReal; use decorum::divergence::{AsResult, OrError}; use decorum::proxy::{Constrained, OutputFor}; // A 64-bit floating-point type that must represent a real number and returns // `Result`s from fallible operations. pub type Real = Constrained>>; pub type Result = OutputFor; } use real::Real; pub fn f(x: Real) -> real::Result { ... } let x = Real::assert(0.0); let y = Real::assert(0.0); let z = (x / y)?; ``` ## Proxy Types The primary API of Decorum is its `Constrained` types, which transparently wrap primitive IEEE 754 floating-point types and configure their behavior. `Constrained` types support many numeric features and operations and integrate with the [`num-traits`] crate and others when [Cargo features](#cargo-features) are enabled. Depending on its configuration, a proxy can be used as a drop-in replacement for primitive floating-point types. The following `Constrained` behaviors can be configured: 1. the allowed subset of IEEE 754 floating-point values 1. the output type of fallibe operations (that may produce non-member values w.r.t. a subset) 1. what happens when an error occurs (i.e., return an error value or panic) Note that the output type of fallible operations and the error behavior are independent. A `Constrained` type may return a `Result` and yet panic if an error occurs, which can be useful for conditional compilation and builds wherein **behavior** changes but types do not. The behavior of a `Constrained` type is configured using two mechanisms: _constraints_ and _divergence_. ```rust use decorum::constraint::IsReal; use decorum::divergence::OrPanic; use decorum::proxy::Constrained; // `Real` must represent a real number and otherwise panics. pub type Real = Constrained>; ``` Constraints specify a subset of floating-point values that a proxy may represent. IEEE 754 floating-point values are divided into three such subsets: | Subset | Example Member | |---------------|----------------| | real numbers | `3.1459` | | infinities | `+INF` | | not-a-numbers | `NaN` | Constraints can be used to strictly represent real numbers, extended reals, or complete but totally ordered IEEE 754 types (i.e., no constraints). Available constraints are summarized below: | Constraint | Members | Fallible | |------------------|-----------------------------------------|-----------| | `IsFloat` | real numbers, infinities, not-a-numbers | no | | `IsExtendedReal` | real numbers, infinities | yes | | `IsReal` | real numbers | yes | `IsFloat` supports all IEEE 754 floating-point values and so applies no constraint at all. As such, it has no fallible operations w.r.t. the constraint and does not accept a divergence. Many operations on members of these subsets may produce values from other subsets that are illegal w.r.t. constraints, such as the addition of two real numbers resulting in `+INF`. A _divergence type_ determines both the behavior when an illegal value is encountered as well as the output type of such fallible operations. | Divergence | OK | Error | Default Output Kind | |------------|----------|-----------|---------------------| | `OrPanic` | continue | **panic** | `AsSelf` | | `OrError` | continue | break | `AsExpression` | In the above table, _continue_ refers to returning a **non**-error value while _break_ refers to returning an error value. If an illegal value is encountered, then **the `OrPanic` divergence panics** while the `OrError` divergence constructs a value that encodes the error. The output type of fallible operations is determined by an _output kind_: | Output Kind | Type | Continue | Break | |----------------|-----------------------|-----------------|--------------------| | `AsSelf` | `Self` | `self` | | | `AsOption` | `Option` | `Some(self)` | `None` | | `AsResult` | `Result` | `Ok(self)` | `Err(error)` | | `AsExpression` | `Expression` | `Defined(self)` | `Undefined(error)` | In the table above, `Self` refers to a `Constrained` type and `E` refers to the associated error type of its constraint. Note that only the `OrPanic` divergence supports `AsSelf` and can output the same type as its input type for fallible operations (just like primitive IEEE 754 floating-point types). With the sole exception of `AsSelf`, the output type of fallible operations is extrinsic: fallible operations produce types that differ from their input types. The `Expression` type, which somewhat resembles the standard `Result` type, improves the ergonomics of error handling by implementing mathematical traits such that it can be used directly in expressions and defer error checking. ```rust use decorum::constraint::IsReal; use decorum::divergence::{AsExpression, OrError}; use decorum::proxy::{Constrained, OutputFor}; use decorum::real::UnaryRealFunction; use decorum::try_expression; pub type Real = Constrained>>; pub type Expr = OutputFor; pub fn f(x: Real, y: Real) -> Expr { let sum = x + y; sum * g(x) } pub fn g(x: Real) -> Expr { x + Real::ONE } let x: Real = try_expression! { f(Real::E, -Real::ONE) }; // ... ``` When using a nightly Rust toolchain with the `unstable` [Cargo feature](#cargo-features) enabled, `Expression` also supports the (at time of writing) unstable `Try` trait and try operator `?`. ```rust // As above, but using the try operator `?`. let x: Real = f(Real::E, -Real::ONE)?; ``` `Constrained` types support numerous constructions and conversions depending on configuration, including conversions for references, slices, subsets, supersets, and more. Conversions are provided via inherent functions and implementations of the standard `From` and `TryFrom` traits. The following inherent functions are supported by all `Constrained` types, though some more bespoke constructions are available for specific configurations. | Method | Input | Output | Error | |------------------------|-----------|-----------|---------------| | `new` | primitive | proxy | break | | `assert` | primitive | proxy | **panic** | | `try_new` | primitive | proxy | `Result::Err` | | `try_from_{mut_}slice` | primitive | proxy | `Result::Err` | | `into_inner` | proxy | primitive | | | `from_subset` | proxy | proxy | | | `into_superset` | proxy | proxy | | The following type definitions provide common proxy configurations. Each type implements different traits that describe the supported encoding and elements of IEEE 754 floating-point based on its constraints. | Type Definition | Sized Aliases | Trait Implementations | Illegal Values | |-----------------|---------------|-------------------------------------------------|-----------------------| | `Total` | | `BaseEncoding + InfinityEncoding + NanEncoding` | | | `ExtendedReal` | `E32`, `E64` | `BaseEncoding + InfinityEncoding` | `NaN` | | `Real` | `R32`, `R64` | `BaseEncoding` | `NaN`, `-INF`, `+INF` | ## Relations and Total Ordering Decorum provides the following non-standard total ordering for IEEE 754 floating-point representations: ``` -INF < ... < 0 < ... < +INF < NaN ``` IEEE 754 floating-point encoding has multiple representations of zero (`-0` and `+0`) and `NaN`. This ordering and equivalence relations consider all zero and `NaN` representations equal, which differs from the [standard partial ordering](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN). Some proxy types disallow unordered `NaN` values and therefore support a total ordering based on the ordered subset of non-`NaN` floating-point values. `Constrained` types that use `IsFloat` (such as the `Total` type definition) support `NaN` but use the total ordering described above to implement the standard `Eq`, `Hash`, and `Ord` traits. The following traits can be used to compare and hash primitive floating-point values (including slices) using this non-standard relation. | Floating-Point Trait | Standard Trait | |----------------------|------------------| | `CanonicalEq` | `Eq` | | `CanonicalHash` | `Hash` | | `CanonicalOrd` | `Ord` | ```rust use decorum::cmp::CanonicalEq; let x = 0.0f64 / 0.0f64; // `NaN`. let y = f64::INFINITY + f64::NEG_INFINITY; // `NaN`. assert!(x.eq_canonical(&y)); ``` Decorum also provides the `EmptyOrd` trait and the `min_or_empty` and `max_or_empty` functions. This trait defines a particular ordering for types that may have a notion of empty inhabitants. An empty inhabitant is considered incomparable, and comparisons return an empty inhabitant when encountered. For example, `None` is the empty inhabitant for `Option`. For floating-point types (including proxy types), `NaN`s are considered empty inhabitants. `EmptyOrd` functions forward `NaN`s when comparing these types just like most numeric operations (unlike `f64::max`, etc.). ```rust use decorum::cmp; use decorum::real::{Endofunction, RealFunction, UnaryRealFunction}; pub fn f(x: T, y: T) -> T where T: Endofunction + RealFunction, { // `min` is assigned an empty inhabitant if either `x` or `y` are an empty // inhabitant. For `T`, the empty inhabitants are `NaN`s, so this function // forwards any input `NaN`s to its output. let min = cmp::min_or_empty(x, y); min * T::PI } ``` ## Mathematical Traits The `real` module provides various traits that describe real numbers and constructions via IEEE 754 floating-point types. These traits model functions and operations on real numbers and specify a codomain for functions where the output is not mathematically confined to the reals or a floating-point exception may yield a non-real approximation or error. For example, the logarithm of zero is undefined and the sum of two very large reals results in an infinity in IEEE 754. For proxy types, the codomain is the same as the branch type of its divergence (see above). Real number and IEEE 754 encoding traits can both be used for generic programming. The following code demonstrates a function that accepts types that support floating-point infinities and real functions. ```rust use decorum::real::{Endofunction, RealFunction}; use decorum::InfinityEncoding; fn f(x: T, y: T) -> T where T: Endofunction + InfinityEncoding + RealFunction, { let z = x / y; if z.is_infinite() { x + y } else { z + y } } ``` ## Cargo Features Decorum supports the following feature flags. | Feature | Default | Description | |------------|---------|--------------------------------------------------------------| | `approx` | yes | Implements traits from [`approx`] for `Constrained` types. | | `serde` | yes | Implements traits from [`serde`] for `Constrained` types. | | `std` | yes | Integrates the `std` library and enables dependent features. | | `unstable` | no | Enables features that require an unstable compiler. | [`approx`]: https://crates.io/crates/approx [`num-traits`]: https://crates.io/crates/num-traits [`serde`]: https://crates.io/crates/serde decorum-0.4.0/build.rs000064400000000000000000000003531046102023000127310ustar 00000000000000fn prelude() { println!("cargo:rustc-check-cfg=cfg(nightly)"); } #[rustversion::not(nightly)] fn main() { self::prelude(); } #[rustversion::nightly] fn main() { self::prelude(); println!("cargo:rustc-cfg=nightly"); } decorum-0.4.0/doc/decorum-favicon.ico000064400000000000000000007760761046102023000156340ustar 00000000000000 (( #.#. "@`tQ1 "Fp\4 -[vD 'Wu?A|`*  Vx:(cE(gG&dFZ; Lt- 7]!fBFq(&qJ Dp%gA _/~.QM(vt)HL)r|1R_ 9I(m< ]x4 Rv4 Qx7TD)a[(=yF .`xF/\\6%GrrT9%  .DafO</%")4EZt\7 'Fnj: 'Of5J|@ 'Z`)@P 1oI+hJ+kR/t^  :u.LA!e` 87Z^5< af# A)yOT14[Y50{UJs+ a> .rOj= 8\o<<_#o<=_$o< 5rTo<'^Bo<@x]* o< Jf5n<@lW.n; "?atQ0n; #0=GLMJC7*n;n;n;m;m;m;m:m:m:m:l:l:l:l:l9l9k9k9k9k9k9k9k8j8j8j8j8j8j8j8i7i7i7i7i7i7h7h7h6h6h6h6h6g6 &Rg6 &Rg6 &Rg6 &Rg5 &Rg5 &Sf5 &Rf5 &Rf5 &R &S &S &S &S &S 'S 'S 'T 'T 'T 'T 'T 'T 'T 'U (U (U (U (U (U (U (V (V (V )V(f0_0`0`0_0_/_/_/_/_/_/^/^/^.^.^.^.].].].].].]-]-\-\-\-\`-\\--\\--\].-[]. ,[]. ,[]. ,[]./Z].].^.^.^/^/^/^/_/_/_/_/_/_0_0`0`0`0`0`0`0`0a1a1a1a1a1a1b1b1b1b2b2b2b2c2c2c2c2c3c3c3d3d3d3d3d3d3e4e4e4e4d4s;?????????>????????????????????????????????>decorum-0.4.0/doc/decorum-favicon.svg000064400000000000000000000037631046102023000156430ustar 00000000000000 image/svg+xml decorum-0.4.0/doc/decorum-minimal.svg000064400000000000000000000037031046102023000156360ustar 00000000000000 image/svg+xml decorum-0.4.0/doc/decorum.svg000064400000000000000000000037221046102023000142130ustar 00000000000000 image/svg+xml decorum-0.4.0/doc/katex-header.html000064400000000000000000000020201046102023000152520ustar 00000000000000 decorum-0.4.0/rustfmt.toml000064400000000000000000000001131046102023000136570ustar 00000000000000control_brace_style = "ClosingNextLine" format_code_in_doc_comments = true decorum-0.4.0/src/cmp.rs000064400000000000000000000324051046102023000132030ustar 00000000000000//! Ordering and comparisons of IEEE 754 floating-point and other partially ordered types. //! //! This module provides traits and functions for partial and total orderings, in particular of //! floating-point types. For primitive floating-point types, the following total ordering is //! provided via the [`CanonicalEq`] and [`CanonicalOrd`] traits: //! //! $$-\infin<\cdots<0<\cdots<\infin<\text{NaN}$$ //! //! Note that both zero and `NaN` have more than one representation in IEEE 754 encoding. Given the //! set of zero representations $Z$ and set of `NaN` representations $N$, this ordering coalesces //! `-0`, `+0`, and `NaN`s such that: //! //! $$ //! \begin{aligned} //! a=b&\mid a\in{Z},~b\in{Z}\cr\[1em\] //! a=b&\mid a\in{N},~b\in{N}\cr\[1em\] //! n>x&\mid n\in{N},~x\notin{N} //! \end{aligned} //! $$ //! //! These same semantics are used in the [`Eq`] and [`Ord`] implementations for the [`Total`] //! proxy. //! //! The [`EmptyOrd`] trait provides a particular ordering for types with a notion of empty //! inhabitants. These inhabitants are considered incomparable, regardless of whether or not they //! are ordered with respect to [`PartialOrd`] and [`Ord`]. For example, `None` is the empty //! inhabitant of [`Option`] and so cannot be compared with [`EmptyOrd`]. //! //! For floating-point types, `NaN`s are considered empty inhabitants. In this context, _empty_ can //! be thought of as _undefined_, but note that **empty inhabitants are unrelated to proxy //! constraints**. Unlike the standard ordering traits, [`EmptyOrd`] forwards empty inhabitants in //! comparisons. For floating-point types, this importantly means that `NaN`s are forwarded //! consistently in comparisons. //! //! # Examples //! //! Comparing `f64` values using a total ordering: //! //! ```rust //! use core::cmp::Ordering; //! use decorum::cmp::CanonicalOrd; //! use decorum::NanEncoding; //! //! let x = f64::NAN; //! let y = 1.0f64; //! //! let (min, max) = match x.cmp_canonical(&y) { //! Ordering::Less | Ordering::Equal => (x, y), //! _ => (y, x), //! }; //! ``` //! //! Computing a pairwise minimum that propagates `NaN`s with [`EmptyOrd`]: //! //! ```rust //! use decorum::cmp; //! use decorum::NanEncoding; //! //! let x = f64::NAN; //! let y = 1.0f64; //! //! // `NaN` is considered an empty inhabitant in this ordering, so `min` is assigned `NaN` in this //! // example, regardless of the order of parameters. //! let min = cmp::min_or_empty(x, y); //! ``` //! //! [`Total`]: crate::Total use core::cmp::Ordering; use core::convert::Infallible; use crate::{with_primitives, Primitive, ToCanonical}; /// Total equivalence relation of IEEE 754 floating-point encoded types. /// /// `CanonicalEq` agrees with the total ordering provided by `CanonicalOrd`. See the module /// documentation for more. Given the set of `NaN` representations $N$, `CanonicalEq` expresses: /// /// $$ /// \begin{aligned} /// a=b&\mid a\in{N},~b\in{N}\cr\[1em\] /// n\ne x&\mid n\in{N},~x\notin{N} /// \end{aligned} /// $$ /// /// # Examples /// /// Comparing `NaN`s using primitive floating-point types: /// /// ```rust /// use decorum::cmp::CanonicalEq; /// /// let x = 0.0f64 / 0.0; // `NaN`. /// let y = f64::INFINITY - f64::INFINITY; // `NaN`. /// /// assert!(x.eq_canonical(&y)); /// ``` pub trait CanonicalEq { fn eq_canonical(&self, other: &Self) -> bool; } impl CanonicalEq for T where T: ToCanonical, { fn eq_canonical(&self, other: &Self) -> bool { self.to_canonical() == other.to_canonical() } } impl CanonicalEq for [T; N] where T: CanonicalEq, { fn eq_canonical(&self, other: &Self) -> bool { self.iter() .zip(other.iter()) .all(|(a, b)| a.eq_canonical(b)) } } impl CanonicalEq for [T] where T: CanonicalEq, { fn eq_canonical(&self, other: &Self) -> bool { if self.len() == other.len() { self.iter() .zip(other.iter()) .all(|(a, b)| a.eq_canonical(b)) } else { false } } } /// Total ordering of IEEE 754 floating-point encoded types. /// /// `CanonicalOrd` expresses the total ordering: /// /// $$-\infin<\cdots<0<\cdots<\infin<\text{NaN}$$ /// /// This trait can be used to compare primitive floating-point types without the need to wrap them /// within a proxy type. See the module documentation for more about the ordering used by /// `CanonicalOrd` and proxy types. pub trait CanonicalOrd { fn cmp_canonical(&self, other: &Self) -> Ordering; } impl CanonicalOrd for T where // This implementation is bound on `Primitive` rather than something more general to exclude // `PartialOrd` implementations that do not comply with IEEE 754 floating-point partial // ordering. This must be implemented independently for proxy types. T: Primitive, { fn cmp_canonical(&self, other: &Self) -> Ordering { match self.partial_cmp(other) { Some(ordering) => ordering, None => { if self.is_nan() { if other.is_nan() { Ordering::Equal } else { Ordering::Greater } } else { Ordering::Less } } } } } impl CanonicalOrd for [T] where T: CanonicalOrd, { fn cmp_canonical(&self, other: &Self) -> Ordering { match self .iter() .zip(other.iter()) .map(|(a, b)| a.cmp_canonical(b)) .find(|ordering| *ordering != Ordering::Equal) { Some(ordering) => ordering, None => self.len().cmp(&other.len()), } } } pub trait EmptyInhabitant { fn empty() -> Self; } impl EmptyInhabitant for () { fn empty() -> Self {} } impl EmptyInhabitant for Option { #[inline(always)] fn empty() -> Self { None } } impl EmptyInhabitant for Result where E: EmptyInhabitant, { #[inline(always)] fn empty() -> Self { Err(E::empty()) } } /// Defines an ordering for types that (may) have empty inhabitants. /// /// An empty inhabitant is an intrinsic value of a type that is considered incomparable in this /// ordering, regardless of [`PartialOrd`] and [`Ord`] implementations. If an empty inhabitant is /// compared, then the comparison is considered undefined and the output is an empty inhabitant. /// /// For floating-point types, `NaN`s are considered empty inhabitants. /// /// `EmptyOrd` can be implemented for types with no empty inhabitants. Notably, this trait is /// implemented by totally ordered primitive numeric types. This better supports types that are /// defined by conditional compilation. /// /// See [`min_or_empty`] and [`max_or_empty`]. pub trait EmptyOrd: PartialOrd { type Empty; fn from_empty(empty: Self::Empty) -> Self; fn is_empty(&self) -> bool; fn cmp_empty(&self, other: &Self) -> Result; } impl EmptyOrd for Option where T: Ord, { type Empty = Self; #[inline(always)] fn from_empty(empty: ::Empty) -> Self { empty } fn is_empty(&self) -> bool { self.is_none() } fn cmp_empty(&self, other: &Self) -> Result { self.as_ref() .zip(other.as_ref()) .map_or_else(|| Err(EmptyInhabitant::empty()), |(a, b)| Ok(a.cmp(b))) } } impl EmptyOrd for Result where Self: PartialOrd, T: Ord, E: EmptyInhabitant, { type Empty = Self; #[inline(always)] fn from_empty(empty: Self::Empty) -> Self { empty } fn is_empty(&self) -> bool { self.is_err() } fn cmp_empty(&self, other: &Self) -> Result { match (self.as_ref(), other.as_ref()) { (Ok(a), Ok(b)) => Ok(a.cmp(b)), _ => Err(EmptyInhabitant::empty()), } } } macro_rules! impl_empty_ord_for_float_primitive { () => { with_primitives!(impl_empty_ord_for_float_primitive); }; (primitive => $t:ty) => { impl EmptyOrd for $t { type Empty = Self; #[inline(always)] fn from_empty(empty: Self::Empty) -> Self { empty } fn is_empty(&self) -> bool { self.is_nan() } fn cmp_empty(&self, other: &Self) -> Result { self.partial_cmp(other) .ok_or_else(|| EmptyInhabitant::empty()) } } }; } impl_empty_ord_for_float_primitive!(); macro_rules! impl_empty_ord_for_total_primitive { () => { impl_empty_ord_for_total_primitive!(primitive => isize); impl_empty_ord_for_total_primitive!(primitive => i8); impl_empty_ord_for_total_primitive!(primitive => i16); impl_empty_ord_for_total_primitive!(primitive => i32); impl_empty_ord_for_total_primitive!(primitive => i64); impl_empty_ord_for_total_primitive!(primitive => i128); impl_empty_ord_for_total_primitive!(primitive => usize); impl_empty_ord_for_total_primitive!(primitive => u8); impl_empty_ord_for_total_primitive!(primitive => u16); impl_empty_ord_for_total_primitive!(primitive => u32); impl_empty_ord_for_total_primitive!(primitive => u64); impl_empty_ord_for_total_primitive!(primitive => u128); }; (primitive => $t:ty) => { impl EmptyOrd for $t { type Empty = Infallible; fn from_empty(_: Self::Empty) -> Self { unreachable!() } #[inline(always)] fn is_empty(&self) -> bool { false } #[inline(always)] fn cmp_empty(&self, other: &Self) -> Result { Ok(self.cmp(other)) } } }; } impl_empty_ord_for_total_primitive!(); macro_rules! impl_empty_inhabitant_for_float_primitive { () => { with_primitives!(impl_empty_inhabitant_for_float_primitive); }; (primitive => $t:ty) => { impl EmptyInhabitant for $t { #[inline(always)] fn empty() -> Self { Self::NAN } } }; } impl_empty_inhabitant_for_float_primitive!(); /// Pairwise maximum for types that may have an empty inhabitant that is incomparable. /// /// See the [`EmptyOrd`] trait. pub fn max_or_empty(a: T, b: T) -> T where T: EmptyOrd, { match a.cmp_empty(&b) { Ok(Ordering::Less | Ordering::Equal) => b, Ok(Ordering::Greater) => a, Err(empty) => T::from_empty(empty), } } /// Pairwise minimum for types that may have an empty inhabitant that is incomparable. /// /// See the [`EmptyOrd`] trait. pub fn min_or_empty(a: T, b: T) -> T where T: EmptyOrd, { match a.cmp_empty(&b) { Ok(Ordering::Less | Ordering::Equal) => a, Ok(Ordering::Greater) => b, Err(empty) => T::from_empty(empty), } } /// Pairwise ordering for types that may have an empty inhabitant that is incomparable. /// /// The output tuple contains either the minimum and maximum (in that order) or empty inhabitants. /// /// See the [`EmptyOrd`] trait. pub fn min_max_or_empty(a: T, b: T) -> (T, T) where T: EmptyOrd, T::Empty: Copy, { match a.cmp_empty(&b) { Ok(Ordering::Less | Ordering::Equal) => (a, b), Ok(Ordering::Greater) => (b, a), Err(empty) => (T::from_empty(empty), T::from_empty(empty)), } } #[cfg(test)] mod tests { use num_traits::{One, Zero}; use crate::cmp::{self, CanonicalEq, EmptyOrd}; use crate::{NanEncoding, Total}; #[test] #[allow(clippy::eq_op)] #[allow(clippy::zero_divided_by_zero)] fn primitive_eq() { let x = 0.0f64 / 0.0f64; // `NaN`. let y = f64::INFINITY + f64::NEG_INFINITY; // `NaN`. let xs = [1.0f64, f64::NAN, f64::INFINITY]; let ys = [1.0f64, f64::NAN, f64::INFINITY]; assert!(x.eq_canonical(&y)); assert!(xs.eq_canonical(&ys)); } #[test] fn empty_ord_option() { let zero = Some(0u64); let one = Some(1u64); assert_eq!(zero, cmp::min_or_empty(zero, one)); assert_eq!(one, cmp::max_or_empty(zero, one)); assert!(cmp::min_or_empty(None, zero).is_empty()); } #[test] #[allow(clippy::float_cmp)] fn empty_ord_primitive() { let zero = 0.0f64; let one = 1.0f64; assert_eq!(zero, cmp::min_or_empty(zero, one)); assert_eq!(one, cmp::max_or_empty(zero, one)); assert!(cmp::min_or_empty(f64::NAN, zero).is_empty()); } #[test] fn empty_ord_proxy() { let nan = Total::::NAN; let zero = Total::zero(); let one = Total::one(); assert_eq!((zero, one), cmp::min_max_or_empty(zero, one)); assert_eq!((zero, one), cmp::min_max_or_empty(one, zero)); assert_eq!((nan, nan), cmp::min_max_or_empty(nan, zero)); assert_eq!((nan, nan), cmp::min_max_or_empty(zero, nan)); assert_eq!((nan, nan), cmp::min_max_or_empty(nan, nan)); assert_eq!(nan, cmp::min_or_empty(nan, zero)); assert_eq!(nan, cmp::max_or_empty(nan, zero)); assert_eq!(nan, cmp::min_or_empty(nan, nan)); assert_eq!(nan, cmp::max_or_empty(nan, nan)); } } decorum-0.4.0/src/constraint.rs000064400000000000000000000225011046102023000146040ustar 00000000000000//! Constraints on the set of IEEE 754 floating-point values that [`Constrained`] types may //! represent. //! //! This module provides traits and types that define the error conditions of [`Constrained`]s. //! Constraints determine when, if ever, a particular floating-point value is considered an error //! and so construction must [diverge][`divergence`]. Constraints are defined in terms of subsets //! of IEEE 754 floating-point values and each constraint has associated [`Constrained`] type //! definitions for convenience: //! //! | Constraint | Divergent | Type Definition | Disallowed Values | //! |--------------------|-----------|------------------|-----------------------| //! | [`IsFloat`] | no | [`Total`] | | //! | [`IsExtendedReal`] | yes | [`ExtendedReal`] | `NaN` | //! | [`IsReal`] | yes | [`Real`] | `NaN`, `+INF`, `-INF` | //! //! [`IsFloat`] and [`Total`] apply no constraints on floating-point values. Unlike primitive //! floating-point types however, [`Total`] defines equivalence and total ordering to `NaN`, which //! allows it to implement related standard traits like `Eq`, `Hash`, and `Ord`. //! //! [`ExtendedReal`], [`Real`], and their corresponding constraints disallow certain IEEE 754 //! values. Because the output of some floating-point operations may yield these values (even when //! the inputs are real numbers), these constraints must specify a [divergence][`divergence`], //! which determines the behavior of [`Constrained`]s when such a value is encountered. //! //! [`cmp`]: crate::cmp //! [`divergence`]: crate::divergence //! [`ExtendedReal`]: crate::ExtendedReal //! [`Real`]: crate::Real //! [`Total`]: crate::Total use core::convert::Infallible; use core::fmt::{self, Debug, Display, Formatter}; use core::marker::PhantomData; use thiserror::Error; use crate::cmp::EmptyInhabitant; use crate::divergence::{Divergence, OrPanic, OutputFor}; use crate::proxy::{Constrained, ConstrainedProxy}; use crate::sealed::{Sealed, StaticDebug}; use crate::{NanEncoding, Primitive}; pub(crate) mod sealed { use crate::proxy::Constrained; use crate::Primitive; /// Defines a notion of empty inhabitants for [`Constraint`] types. /// /// This trait is a corollary to [`EmptyOrd`] and is used to implement that trait for /// [`Constrained`] types more generally than is otherwise possible. /// /// **The notion of an empty inhabitant is independent of constraints.** Regardless of /// constraint, `NaN`s are considered empty inhabitants. pub trait FromEmpty: Sized { type Empty; fn empty() -> Self::Empty where T: Primitive; fn from_empty(empty: Self::Empty) -> Constrained where T: Primitive; fn is_empty(primitive: &T) -> bool where T: Primitive; } } use sealed::FromEmpty; #[derive(Clone, Copy, Debug, Error)] pub enum ConstraintError { #[error(transparent)] NotExtendedReal(NotExtendedRealError), #[error(transparent)] NotReal(NotRealError), } impl From for ConstraintError { fn from(error: NotExtendedRealError) -> Self { ConstraintError::NotExtendedReal(error) } } impl From for ConstraintError { fn from(error: NotRealError) -> Self { ConstraintError::NotReal(error) } } #[derive(Clone, Copy, Debug, Error)] #[error("{}", "floating-point value must be an extended real")] pub struct NotExtendedRealError; impl EmptyInhabitant for NotExtendedRealError { fn empty() -> Self { NotExtendedRealError } } #[derive(Clone, Copy, Debug, Error)] #[error("{}", "floating-point value must be a real")] pub struct NotRealError; impl EmptyInhabitant for NotRealError { fn empty() -> Self { NotRealError } } pub(crate) trait ExpectConstrained: Sized { fn expect_constrained(self) -> T; } impl ExpectConstrained for Result where E: Debug, { fn expect_constrained(self) -> T { self.unwrap() } } pub enum RealSet {} pub enum InfinitySet {} pub enum NanSet {} pub trait Member: Sealed {} pub trait SupersetOf: Sealed {} pub trait SubsetOf: Sealed {} impl SubsetOf for C1 where C1: Sealed, C2: SupersetOf, { } /// Describes constraints on the set of floating-point values that a [`Constrained`] may represent. /// /// Note that constraints require [`Member`][`Member`], meaning that the set of real /// numbers must always be supported and is implied wherever a `Constraint` bound is used. pub trait Constraint: FromEmpty + Member + StaticDebug { type Divergence: Divergence; // TODO: Bound this on `core::Error` once it is stabilized. type Error: Debug + Display; // It is not possible for constraints to map accepted values because of reference conversions, // so the successful output is the unit type and primitive values must be used as-is. That is, // this function only expresses the membership of the given value and no other. fn check(inner: T) -> Result<(), Self::Error> where T: Primitive; fn map(inner: T, f: F) -> OutputFor where T: Primitive, U: ConstrainedProxy, F: FnOnce(T) -> U, { Self::Divergence::diverge(Self::check(inner).map(|_| f(inner))) } } #[derive(Debug)] pub enum IsFloat {} impl Constraint for IsFloat { // Branching in the `Divergence` is completely bypassed in this implementation. type Divergence = OrPanic; type Error = Infallible; #[inline(always)] fn check(_inner: T) -> Result<(), Self::Error> where T: Primitive, { Ok(()) } #[inline(always)] fn map(inner: T, f: F) -> U where T: Primitive, U: ConstrainedProxy, F: FnOnce(T) -> U, { f(inner) } } impl FromEmpty for IsFloat { type Empty = Constrained; #[inline(always)] fn empty() -> Self::Empty where T: Primitive, { Constrained::NAN } #[inline(always)] fn from_empty(empty: Self::Empty) -> Constrained where T: Primitive, { empty } #[inline(always)] fn is_empty(primitive: &T) -> bool where T: Primitive, { primitive.is_nan() } } impl Member for IsFloat {} impl Member for IsFloat {} impl Member for IsFloat {} impl Sealed for IsFloat {} impl StaticDebug for IsFloat { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "IsFloat") } } impl SupersetOf> for IsFloat {} impl SupersetOf> for IsFloat {} #[derive(Debug)] pub struct IsExtendedReal(PhantomData D>, Infallible); pub type IsNotNan = IsExtendedReal; impl Constraint for IsExtendedReal where D: Divergence, { type Divergence = D; type Error = NotExtendedRealError; fn check(inner: T) -> Result<(), Self::Error> where T: Primitive, { if inner.is_nan() { Err(NotExtendedRealError) } else { Ok(()) } } } impl FromEmpty for IsExtendedReal where D: Divergence, { type Empty = Infallible; fn empty() -> Self::Empty where T: Primitive, { unreachable!() } fn from_empty(_: Self::Empty) -> Constrained where T: Primitive, { unreachable!() } #[inline(always)] fn is_empty(_: &T) -> bool where T: Primitive, { false } } impl Member for IsExtendedReal {} impl Member for IsExtendedReal {} impl Sealed for IsExtendedReal {} impl StaticDebug for IsExtendedReal where D: StaticDebug, { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "IsExtendedReal<")?; D::fmt(formatter)?; write!(formatter, ">") } } impl SupersetOf> for IsExtendedReal {} #[derive(Debug)] pub struct IsReal(PhantomData D>, Infallible); impl Constraint for IsReal where D: Divergence, { type Divergence = D; type Error = NotRealError; fn check(inner: T) -> Result<(), Self::Error> where T: Primitive, { if inner.is_nan() || inner.is_infinite() { Err(NotRealError) } else { Ok(()) } } } impl FromEmpty for IsReal where D: Divergence, { type Empty = Infallible; fn empty() -> Self::Empty where T: Primitive, { unreachable!() } fn from_empty(_: Self::Empty) -> Constrained where T: Primitive, { unreachable!() } #[inline(always)] fn is_empty(_: &T) -> bool where T: Primitive, { false } } impl Member for IsReal {} impl Sealed for IsReal {} impl StaticDebug for IsReal where D: StaticDebug, { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "IsReal<")?; D::fmt(formatter)?; write!(formatter, ">") } } decorum-0.4.0/src/divergence.rs000064400000000000000000000212071046102023000145350ustar 00000000000000//! Error behavior and output types for fallible operations. //! //! This module provides type constructors that determine the behavior and output types of fallible //! [`Constrained`] operations. These types are used as parameters of some //! [constraints][`constraint`]. //! //! # Error Behaviors //! //! Error behavior is determined by a [divergence type][`Divergence`]: //! //! | Divergence | OK | Error | Default Output Kind | //! |-------------|----------|-----------|---------------------| //! | [`OrPanic`] | continue | **panic** | [`AsSelf`] | //! | [`OrError`] | continue | break | [`AsExpression`] | //! //! Divergence is independent of output types: the [`OrPanic`] divergence panics when breaking even //! when the output type can represent errors (e.g., [`Result`]). Because [`OrPanic`] never returns //! an error value, it can be used with output types that cannot respresent errors. This differs //! from [`OrError`], which requires an error representation. //! //! # Output Types //! //! Output types are determined by an [output kind][`Continue`]. An output kind is type constructor //! with which a [`Divergence`] can construct an output type: //! //! | Output Kind | Output Type | Continue | Break | //! |------------------|-----------------------|-----------------|--------------------| //! | [`AsSelf`] | `Self` | `self` | | //! | [`AsOption`] | `Option` | `Some(self)` | `None` | //! | [`AsResult`] | `Result` | `Ok(self)` | `Err(error)` | //! | [`AsExpression`] | `Expression` | `Defined(self)` | `Undefined(error)` | //! //! In the above table, `Self` refers to a [`Constrained`] type and `E` refers to the [associated //! error][`Constraint::Error`] type of its [constraint][`constraint`]. [`AsSelf`] is unique in //! that it cannot represent errors and so does not support breaking: its output type is the //! identity. //! //! # Examples //! //! The following example illustrates how to define a [`Constrained`] type. //! //! ```rust //! use decorum::constraint::IsNotNan; //! use decorum::divergence::{AsSelf, OrPanic}; //! use decorum::proxy::Constrained; //! //! // A 32-bit floating-point representation that must be a real number or an infinity. Panics if //! // constructed from a `NaN`. //! pub type NotNan = Constrained>>; //! ``` //! //! The following example demonstrates a conditionally compiled `Real` type definition with a //! [`Result`] branch type that, when an error occurs, returns `Err` in **non**-debug builds but //! panics in debug builds. //! //! ```rust //! pub mod real { //! use decorum::constraint::IsReal; //! use decorum::divergence::{self, AsResult}; //! use decorum::proxy::{Constrained, OutputFor}; //! //! #[cfg(debug_assertions)] //! type OrDiverge = divergence::OrPanic; //! #[cfg(not(debug_assertions))] //! type OrDiverge = divergence::OrError; //! //! pub type Real = Constrained>; //! pub type Result = OutputFor; //! } //! //! use decorum::real::UnaryRealFunction; //! //! use real::Real; //! //! pub fn f(x: Real) -> real::Result { //! // This panics in debug builds and returns `Err` in non-debug builds. //! x / Real::ZERO //! } //! ``` //! //! [`Constrained`]: crate::proxy::Constrained //! [`constraint`]: crate::constraint //! [`Constraint::Error`]: crate::constraint::Constraint::Error use core::convert::Infallible; use core::fmt::{self, Debug, Formatter}; use core::marker::PhantomData; use crate::constraint::ExpectConstrained as _; use crate::expression::{Defined, Expression, Undefined}; use crate::sealed::{Sealed, StaticDebug}; /// An output kind that can continue with an output. pub trait Continue: Sealed + StaticDebug { type As; fn continue_with_output(output: P) -> Self::As; } /// An output kind that can break with an error. pub trait Break: Continue { fn break_with_error(error: E) -> Self::As; } /// An [output kind][`Continue`] that outputs the identity. pub trait NonResidual: Continue = P> {} impl NonResidual for K where K: Continue = P> {} #[derive(Debug)] pub enum AsExpression {} impl Break for AsExpression { fn break_with_error(error: E) -> Self::As { Undefined(error) } } impl Continue for AsExpression { type As = Expression; fn continue_with_output(output: P) -> Self::As { Defined(output) } } impl Sealed for AsExpression {} impl StaticDebug for AsExpression { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "AsExpression") } } #[derive(Debug)] pub enum AsOption {} impl Break for AsOption { fn break_with_error(_: E) -> Self::As { None } } impl Continue for AsOption { type As = Option

; fn continue_with_output(output: P) -> Self::As { Some(output) } } impl Sealed for AsOption {} impl StaticDebug for AsOption { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "AsOption") } } #[derive(Debug)] pub enum AsResult {} impl Break for AsResult { fn break_with_error(error: E) -> Self::As { Err(error) } } impl Continue for AsResult { type As = Result; fn continue_with_output(output: P) -> Self::As { Ok(output) } } impl Sealed for AsResult {} impl StaticDebug for AsResult { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "AsResult") } } #[derive(Debug)] pub enum AsSelf {} impl Continue for AsSelf { type As = P; fn continue_with_output(output: P) -> Self::As { output } } impl Sealed for AsSelf {} impl StaticDebug for AsSelf { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "AsSelf") } } /// Determines the output type and behavior of a [`Constrained`] when it is fallibly constructed. /// /// The output type is defined by an associated [output **kind**][`Continue`]. Regardless of this /// type, this trait implements continuing and breaking on the [`Result`] of constructing a /// [`Constrained`]. See the [module documentation][`divergence`]. /// /// [`Constrained`]: crate::proxy::Constrained /// [`divergence`]: crate::divergence pub trait Divergence: Sealed + StaticDebug { type Continue: Continue; fn diverge(result: Result) -> ::As where E: Debug; } pub type ContinueFor = ::Continue; pub type OutputFor = as Continue>::As; /// Divergence that breaks on errors by **panicking**. /// /// **`OrPanic` panics if a [`Constrained`] cannot be constructed.** This behavior is independent /// of the output kind, so even an `OrPanic` divergence with a [`Result`] output type panics if an /// error occurs. /// /// By default, `OrPanic` uses the [`AsSelf`] output kind. /// /// [`Constrained`]: crate::proxy::Constrained #[derive(Debug)] pub struct OrPanic(PhantomData K>, Infallible); impl Divergence for OrPanic where K: Continue, { type Continue = K; fn diverge(result: Result) -> K::As where E: Debug, { K::continue_with_output(result.expect_constrained()) } } impl Sealed for OrPanic {} impl StaticDebug for OrPanic where K: StaticDebug, { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "OrPanic<")?; K::fmt(formatter)?; write!(formatter, ">") } } /// Divergence that breaks on errors by constructing an error representation of its output type. /// /// The output kind `K` must support an error representation and implement [`Break`]. /// /// By default, `OrError` uses the [`AsExpression`] kind and therefore has an [`Expression`] output /// type. pub struct OrError(PhantomData K>, Infallible); impl Divergence for OrError where K: Break, { type Continue = K; fn diverge(result: Result) -> K::As where E: Debug, { match result { Ok(output) => K::continue_with_output(output), Err(error) => K::break_with_error(error), } } } impl Sealed for OrError {} impl StaticDebug for OrError where K: StaticDebug, { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "OrError<")?; K::fmt(formatter)?; write!(formatter, ">") } } decorum-0.4.0/src/expression.rs000064400000000000000000000670611046102023000146310ustar 00000000000000use core::cmp::Ordering; use core::convert::Infallible; use core::fmt::Debug; use core::hint; #[cfg(all(nightly, feature = "unstable"))] use core::ops::{self, ControlFlow, FromResidual}; use core::ops::{Add, Div, Mul, Neg, Rem, Sub}; use crate::cmp::{self, EmptyOrd}; use crate::constraint::{Constraint, Member, NanSet}; use crate::divergence::{AsExpression, Divergence, OrError}; use crate::proxy::{Constrained, ErrorFor, ExpressionFor}; use crate::real::{BinaryRealFunction, Function, Sign, UnaryRealFunction}; use crate::{with_binary_operations, with_primitives, InfinityEncoding, NanEncoding, Primitive}; pub use Expression::Defined; pub use Expression::Undefined; /// Unwraps an [`Expression`] or propagates its error. /// /// This macro mirrors the standard [`try`] macro but operates on [`Expression`]s rather than /// [`Result`]s. If the given [`Expression`] is the `Defined` variant, then the expression (of the /// macro) is the accompanying value. Otherwise, the error in the `Undefined` variant is converted /// via [`From`] and returned in the constructed [`Expression`]. #[macro_export] macro_rules! try_expression { ($x:expr $(,)?) => {{ let expression: $crate::expression::Expression<_, _> = $x; match expression { $crate::expression::Expression::Defined(inner) => inner, $crate::expression::Expression::Undefined(error) => { return $crate::expression::Expression::Undefined(core::convert::From::from(error)); } } }}; ($x:block $(,)?) => { let expression: $crate::expression::Expression<_, _> = $x; try_expression!(expression); }; } pub use try_expression; /// The result of an arithmetic expression that may or may not be defined. /// /// `Expression` is a fallible output of arithmetic expressions over [`Constrained`] types. It /// resembles [`Result`], but `Expression` crucially implements numeric traits and can be used in /// arithmetic expressions. This allows complex expressions to defer matching or trying for more /// fluent syntax. /// /// When the `unstable` Cargo feature is enabled with a nightly Rust toolchain, [`Expression`] also /// implements the unstable (at time of writing) [`Try`] trait and supports the try operator `?`. /// /// # Examples /// /// The following two examples contrast deferred matching and trying of `Expression`s versus /// immediate matching and trying of `Result`s. /// /// ```rust /// use decorum::constraint::IsReal; /// use decorum::divergence::OrError; /// use decorum::proxy::{Constrained, OutputFor}; /// use decorum::real::UnaryRealFunction; /// use decorum::try_expression; /// /// pub type Real = Constrained>; /// pub type Expr = OutputFor; /// /// # fn fallible() -> Expr { /// fn f(x: Real, y: Real, z: Real) -> Expr { /// let w = (x + y + z); /// w / Real::ONE /// } /// /// let x = Real::ONE; /// let y: Real = try_expression!(f(x, x, x)); /// // ... /// # f(x, x, x) /// # } /// ``` /// /// ```rust /// use decorum::constraint::IsReal; /// use decorum::divergence::{AsResult, OrError}; /// use decorum::proxy::{Constrained, OutputFor}; /// use decorum::real::UnaryRealFunction; /// /// pub type Real = Constrained>>; /// pub type RealResult = OutputFor; /// /// # fn fallible() -> RealResult { /// fn f(x: Real, y: Real, z: Real) -> RealResult { /// // The expression `x + y` outputs a `Result`, which cannot be used in a mathematical /// // expression, so it must be tried first. /// let w = ((x + y)? + z)?; /// w / Real::ONE /// } /// /// let x = Real::ONE; /// let y: Real = f(x, x, x)?; /// // ... /// # f(x, x, x) /// # } /// ``` /// /// When the `unstable` Cargo feature is enabled with a nightly Rust toolchain, `Expression` /// supports the try operator `?`. /// /// ```rust,ignore /// use decorum::constraint::IsReal; /// use decorum::divergence::{AsExpression, OrError}; /// use decorum::proxy::{Constrained, OutputFor}; /// use decorum::real::UnaryRealFunction; /// /// pub type Real = Constrained>>; /// pub type Expr = OutputFor; /// /// # fn fallible() -> Expr { /// fn f(x: Real, y: Real, z: Real) -> Expr { /// let w = (x + y + z)?; // Try. /// eprintln!("x + y + z => defined!"); /// w / Real::ONE /// } /// /// let x = Real::ONE; /// let y = Real::ZERO; /// let z = f(x, y, x)?; // Try. /// eprintln!("f(x, y, x) => defined!"); /// // ... /// # f(x, y, x) /// # } /// ``` /// /// [`Try`]: core::ops::Try #[derive(Clone, Copy, Debug)] pub enum Expression { Defined(T), Undefined(E), } impl Expression { pub fn unwrap(self) -> T { match self { Defined(defined) => defined, _ => panic!(), } } pub fn as_ref(&self) -> Expression<&T, &E> { match self { Defined(ref defined) => Defined(defined), Undefined(ref undefined) => Undefined(undefined), } } pub fn map(self, f: F) -> Expression where F: FnOnce(T) -> U, { match self { Defined(defined) => Defined(f(defined)), Undefined(undefined) => Undefined(undefined), } } pub fn and_then(self, f: F) -> Expression where F: FnOnce(T) -> Expression, { match self { Defined(defined) => f(defined), Undefined(undefined) => Undefined(undefined), } } pub fn defined(self) -> Option { match self { Defined(defined) => Some(defined), _ => None, } } pub fn undefined(self) -> Option { match self { Undefined(undefined) => Some(undefined), _ => None, } } pub fn is_defined(&self) -> bool { matches!(self, Defined(_)) } pub fn is_undefined(&self) -> bool { matches!(self, Undefined(_)) } } impl Expression<&'_ T, E> { pub fn copied(self) -> Expression where T: Copy, { match self { Defined(defined) => Defined(*defined), Undefined(undefined) => Undefined(undefined), } } pub fn cloned(self) -> Expression where T: Clone, { match self { Defined(defined) => Defined(defined.clone()), Undefined(undefined) => Undefined(undefined), } } } impl Expression<&'_ mut T, E> { pub fn copied(self) -> Expression where T: Copy, { match self { Defined(defined) => Defined(*defined), Undefined(undefined) => Undefined(undefined), } } pub fn cloned(self) -> Expression where T: Clone, { match self { Defined(defined) => Defined(defined.clone()), Undefined(undefined) => Undefined(undefined), } } } impl Expression { pub fn into_defined(self) -> T { #[allow(unreachable_patterns)] match self { Defined(defined) => defined, // SAFETY: `Infallible` is uninhabited, so it is not possible to construct the // `Undefined` variant here. Undefined(_) => unsafe { hint::unreachable_unchecked() }, } } pub fn get(&self) -> &T { #[allow(unreachable_patterns)] match self { Defined(ref defined) => defined, // SAFETY: `Infallible` is uninhabited, so it is not possible to construct the // `Undefined` variant here. Undefined(_) => unsafe { hint::unreachable_unchecked() }, } } } impl Expression { pub fn into_undefined(self) -> E { #[allow(unreachable_patterns)] match self { Undefined(undefined) => undefined, // SAFETY: `Infallible` is uninhabited, so it is not possible to construct the // `Defined` variant here. Defined(_) => unsafe { hint::unreachable_unchecked() }, } } } impl BinaryRealFunction for ExpressionFor> where ErrorFor>: Clone + cmp::EmptyInhabitant, T: Primitive, C: Constraint, C::Divergence: Divergence, { #[cfg(feature = "std")] fn div_euclid(self, n: Self) -> Self::Codomain { BinaryRealFunction::div_euclid(try_expression!(self), try_expression!(n)) } #[cfg(feature = "std")] fn rem_euclid(self, n: Self) -> Self::Codomain { BinaryRealFunction::rem_euclid(try_expression!(self), try_expression!(n)) } #[cfg(feature = "std")] fn pow(self, n: Self) -> Self::Codomain { BinaryRealFunction::pow(try_expression!(self), try_expression!(n)) } #[cfg(feature = "std")] fn log(self, base: Self) -> Self::Codomain { BinaryRealFunction::log(try_expression!(self), try_expression!(base)) } #[cfg(feature = "std")] fn hypot(self, other: Self) -> Self::Codomain { BinaryRealFunction::hypot(try_expression!(self), try_expression!(other)) } #[cfg(feature = "std")] fn atan2(self, other: Self) -> Self::Codomain { BinaryRealFunction::atan2(try_expression!(self), try_expression!(other)) } } impl BinaryRealFunction for ExpressionFor> where ErrorFor>: Clone + cmp::EmptyInhabitant, T: Primitive, C: Constraint, C::Divergence: Divergence, { #[cfg(feature = "std")] fn div_euclid(self, n: T) -> Self::Codomain { BinaryRealFunction::div_euclid( try_expression!(self), try_expression!(Constrained::::new(n)), ) } #[cfg(feature = "std")] fn rem_euclid(self, n: T) -> Self::Codomain { BinaryRealFunction::rem_euclid( try_expression!(self), try_expression!(Constrained::::new(n)), ) } #[cfg(feature = "std")] fn pow(self, n: T) -> Self::Codomain { BinaryRealFunction::pow( try_expression!(self), try_expression!(Constrained::::new(n)), ) } #[cfg(feature = "std")] fn log(self, base: T) -> Self::Codomain { BinaryRealFunction::log( try_expression!(self), try_expression!(Constrained::::new(base)), ) } #[cfg(feature = "std")] fn hypot(self, other: T) -> Self::Codomain { BinaryRealFunction::hypot( try_expression!(self), try_expression!(Constrained::::new(other)), ) } #[cfg(feature = "std")] fn atan2(self, other: T) -> Self::Codomain { BinaryRealFunction::atan2( try_expression!(self), try_expression!(Constrained::::new(other)), ) } } impl BinaryRealFunction> for ExpressionFor> where ErrorFor>: Clone + cmp::EmptyInhabitant, T: Primitive, C: Constraint, C::Divergence: Divergence, { #[cfg(feature = "std")] fn div_euclid(self, n: Constrained) -> Self::Codomain { BinaryRealFunction::div_euclid(try_expression!(self), n) } #[cfg(feature = "std")] fn rem_euclid(self, n: Constrained) -> Self::Codomain { BinaryRealFunction::rem_euclid(try_expression!(self), n) } #[cfg(feature = "std")] fn pow(self, n: Constrained) -> Self::Codomain { BinaryRealFunction::pow(try_expression!(self), n) } #[cfg(feature = "std")] fn log(self, base: Constrained) -> Self::Codomain { BinaryRealFunction::log(try_expression!(self), base) } #[cfg(feature = "std")] fn hypot(self, other: Constrained) -> Self::Codomain { BinaryRealFunction::hypot(try_expression!(self), other) } #[cfg(feature = "std")] fn atan2(self, other: Constrained) -> Self::Codomain { BinaryRealFunction::atan2(try_expression!(self), other) } } impl BinaryRealFunction>> for Constrained where ErrorFor>: Clone + cmp::EmptyInhabitant, T: Primitive, C: Constraint, C::Divergence: Divergence, { #[cfg(feature = "std")] fn div_euclid(self, n: ExpressionFor>) -> Self::Codomain { BinaryRealFunction::div_euclid(self, try_expression!(n)) } #[cfg(feature = "std")] fn rem_euclid(self, n: ExpressionFor>) -> Self::Codomain { BinaryRealFunction::rem_euclid(self, try_expression!(n)) } #[cfg(feature = "std")] fn pow(self, n: ExpressionFor>) -> Self::Codomain { BinaryRealFunction::pow(self, try_expression!(n)) } #[cfg(feature = "std")] fn log(self, base: ExpressionFor>) -> Self::Codomain { BinaryRealFunction::log(self, try_expression!(base)) } #[cfg(feature = "std")] fn hypot(self, other: ExpressionFor>) -> Self::Codomain { BinaryRealFunction::hypot(self, try_expression!(other)) } #[cfg(feature = "std")] fn atan2(self, other: ExpressionFor>) -> Self::Codomain { BinaryRealFunction::atan2(self, try_expression!(other)) } } impl From for Expression, ErrorFor>> where T: Primitive, C: Constraint, { fn from(inner: T) -> Self { Constrained::try_new(inner).into() } } impl<'a, T, C> From<&'a T> for ExpressionFor> where Constrained: TryFrom<&'a T, Error = C::Error>, T: Primitive, C: Constraint>, { fn from(inner: &'a T) -> Self { Constrained::::try_from(inner).into() } } impl<'a, T, C> From<&'a mut T> for ExpressionFor> where Constrained: TryFrom<&'a mut T, Error = C::Error>, T: Primitive, C: Constraint>, { fn from(inner: &'a mut T) -> Self { Constrained::::try_from(inner).into() } } impl From> for Expression, ErrorFor>> where T: Primitive, C: Constraint, { fn from(proxy: Constrained) -> Self { Defined(proxy) } } impl From> for Expression { fn from(result: Result) -> Self { match result { Ok(output) => Defined(output), Err(error) => Undefined(error), } } } impl From> for Result { fn from(result: Expression) -> Self { match result { Defined(defined) => Ok(defined), Undefined(undefined) => Err(undefined), } } } #[cfg(all(nightly, feature = "unstable"))] impl FromResidual for Expression { fn from_residual(residual: Expression) -> Self { Undefined(residual.into_undefined()) } } impl Function for ExpressionFor> where ErrorFor>: cmp::EmptyInhabitant, T: Primitive, C: Constraint, C::Divergence: Divergence, { type Codomain = Self; } impl InfinityEncoding for ExpressionFor> where ErrorFor>: Copy, Constrained: InfinityEncoding, T: Primitive, C: Constraint, C::Divergence: Divergence, { const INFINITY: Self = Defined(InfinityEncoding::INFINITY); const NEG_INFINITY: Self = Defined(InfinityEncoding::NEG_INFINITY); fn is_infinite(self) -> bool { self.defined().is_some_and(InfinityEncoding::is_infinite) } fn is_finite(self) -> bool { self.defined().is_some_and(InfinityEncoding::is_finite) } } impl EmptyOrd for ExpressionFor> where T: Primitive, C: Constraint + Member, { type Empty = Self; #[inline(always)] fn from_empty(empty: ::Empty) -> Self { empty } fn is_empty(&self) -> bool { self.get().is_nan() } fn cmp_empty(&self, other: &Self) -> Result::Empty> { match (self.is_undefined(), other.is_undefined()) { (true, _) => Err(*self), (_, true) => Err(*other), (false, false) => Ok(self.get().cmp(other.get())), } } } impl Neg for ExpressionFor> where T: Primitive, C: Constraint, C::Divergence: Divergence, { type Output = Self; fn neg(self) -> Self::Output { self.map(|defined| -defined) } } impl PartialEq for Expression where T: PartialEq, { fn eq(&self, other: &Self) -> bool { self.as_ref() .defined() .zip(other.as_ref().defined()) .is_some_and(|(left, right)| left.eq(right)) } } impl PartialOrd for Expression where T: PartialOrd, { fn partial_cmp(&self, other: &Self) -> Option { self.as_ref() .defined() .zip(other.as_ref().defined()) .and_then(|(left, right)| left.partial_cmp(right)) } } #[cfg(all(nightly, feature = "unstable"))] impl ops::Try for Expression { type Output = T; type Residual = Expression; fn from_output(output: T) -> Self { Defined(output) } fn branch(self) -> ControlFlow { match self { Defined(defined) => ControlFlow::Continue(defined), Undefined(undefined) => ControlFlow::Break(Undefined(undefined)), } } } impl UnaryRealFunction for ExpressionFor> where ErrorFor>: Clone + cmp::EmptyInhabitant, T: Primitive, C: Constraint, C::Divergence: Divergence, { const ZERO: Self = Defined(UnaryRealFunction::ZERO); const ONE: Self = Defined(UnaryRealFunction::ONE); const E: Self = Defined(UnaryRealFunction::E); const PI: Self = Defined(UnaryRealFunction::PI); const FRAC_1_PI: Self = Defined(UnaryRealFunction::FRAC_1_PI); const FRAC_2_PI: Self = Defined(UnaryRealFunction::FRAC_2_PI); const FRAC_2_SQRT_PI: Self = Defined(UnaryRealFunction::FRAC_2_SQRT_PI); const FRAC_PI_2: Self = Defined(UnaryRealFunction::FRAC_PI_2); const FRAC_PI_3: Self = Defined(UnaryRealFunction::FRAC_PI_3); const FRAC_PI_4: Self = Defined(UnaryRealFunction::FRAC_PI_4); const FRAC_PI_6: Self = Defined(UnaryRealFunction::FRAC_PI_6); const FRAC_PI_8: Self = Defined(UnaryRealFunction::FRAC_PI_8); const SQRT_2: Self = Defined(UnaryRealFunction::SQRT_2); const FRAC_1_SQRT_2: Self = Defined(UnaryRealFunction::FRAC_1_SQRT_2); const LN_2: Self = Defined(UnaryRealFunction::LN_2); const LN_10: Self = Defined(UnaryRealFunction::LN_10); const LOG2_E: Self = Defined(UnaryRealFunction::LOG2_E); const LOG10_E: Self = Defined(UnaryRealFunction::LOG10_E); fn is_zero(self) -> bool { self.defined().is_some_and(UnaryRealFunction::is_zero) } fn is_one(self) -> bool { self.defined().is_some_and(UnaryRealFunction::is_one) } fn sign(self) -> Sign { self.defined().map_or(Sign::Zero, |defined| defined.sign()) } #[cfg(feature = "std")] fn abs(self) -> Self { self.map(UnaryRealFunction::abs) } #[cfg(feature = "std")] fn floor(self) -> Self { self.map(UnaryRealFunction::floor) } #[cfg(feature = "std")] fn ceil(self) -> Self { self.map(UnaryRealFunction::ceil) } #[cfg(feature = "std")] fn round(self) -> Self { self.map(UnaryRealFunction::round) } #[cfg(feature = "std")] fn trunc(self) -> Self { self.map(UnaryRealFunction::trunc) } #[cfg(feature = "std")] fn fract(self) -> Self { self.map(UnaryRealFunction::fract) } fn recip(self) -> Self::Codomain { self.and_then(UnaryRealFunction::recip) } #[cfg(feature = "std")] fn powi(self, n: i32) -> Self::Codomain { self.and_then(|defined| UnaryRealFunction::powi(defined, n)) } #[cfg(feature = "std")] fn sqrt(self) -> Self::Codomain { self.and_then(UnaryRealFunction::sqrt) } #[cfg(feature = "std")] fn cbrt(self) -> Self { self.map(UnaryRealFunction::cbrt) } #[cfg(feature = "std")] fn exp(self) -> Self::Codomain { self.and_then(UnaryRealFunction::exp) } #[cfg(feature = "std")] fn exp2(self) -> Self::Codomain { self.and_then(UnaryRealFunction::exp2) } #[cfg(feature = "std")] fn exp_m1(self) -> Self::Codomain { self.and_then(UnaryRealFunction::exp_m1) } #[cfg(feature = "std")] fn ln(self) -> Self::Codomain { self.and_then(UnaryRealFunction::ln) } #[cfg(feature = "std")] fn log2(self) -> Self::Codomain { self.and_then(UnaryRealFunction::log2) } #[cfg(feature = "std")] fn log10(self) -> Self::Codomain { self.and_then(UnaryRealFunction::log10) } #[cfg(feature = "std")] fn ln_1p(self) -> Self::Codomain { self.and_then(UnaryRealFunction::ln_1p) } #[cfg(feature = "std")] fn to_degrees(self) -> Self::Codomain { self.and_then(UnaryRealFunction::to_degrees) } #[cfg(feature = "std")] fn to_radians(self) -> Self { self.map(UnaryRealFunction::to_radians) } #[cfg(feature = "std")] fn sin(self) -> Self { self.map(UnaryRealFunction::sin) } #[cfg(feature = "std")] fn cos(self) -> Self { self.map(UnaryRealFunction::cos) } #[cfg(feature = "std")] fn tan(self) -> Self::Codomain { self.and_then(UnaryRealFunction::tan) } #[cfg(feature = "std")] fn asin(self) -> Self::Codomain { self.and_then(UnaryRealFunction::asin) } #[cfg(feature = "std")] fn acos(self) -> Self::Codomain { self.and_then(UnaryRealFunction::acos) } #[cfg(feature = "std")] fn atan(self) -> Self { self.map(UnaryRealFunction::atan) } #[cfg(feature = "std")] fn sin_cos(self) -> (Self, Self) { match self { Defined(defined) => { let (sin, cos) = defined.sin_cos(); (Defined(sin), Defined(cos)) } Undefined(undefined) => (Undefined(undefined.clone()), Undefined(undefined)), } } #[cfg(feature = "std")] fn sinh(self) -> Self { self.map(UnaryRealFunction::sinh) } #[cfg(feature = "std")] fn cosh(self) -> Self { self.map(UnaryRealFunction::cosh) } #[cfg(feature = "std")] fn tanh(self) -> Self { self.map(UnaryRealFunction::tanh) } #[cfg(feature = "std")] fn asinh(self) -> Self::Codomain { self.and_then(UnaryRealFunction::asinh) } #[cfg(feature = "std")] fn acosh(self) -> Self::Codomain { self.and_then(UnaryRealFunction::acosh) } #[cfg(feature = "std")] fn atanh(self) -> Self::Codomain { self.and_then(UnaryRealFunction::atanh) } } impl cmp::EmptyInhabitant for Expression where E: cmp::EmptyInhabitant, { #[inline(always)] fn empty() -> Self { Expression::Undefined(E::empty()) } } macro_rules! impl_binary_operation_for_expression { () => { with_binary_operations!(impl_binary_operation_for_expression); }; (operation => $trait:ident :: $method:ident) => { impl_binary_operation_for_expression!(operation => $trait :: $method, |left, right| { left.zip_map(right, $trait::$method) }); }; (operation => $trait:ident :: $method:ident, |$left:ident, $right:ident| $f:block) => { macro_rules! impl_primitive_binary_operation_for_expression { () => { with_primitives!(impl_primitive_binary_operation_for_expression); }; (primitive => $t:ty) => { impl $trait>> for $t where C: Constraint, C::Divergence: Divergence, { type Output = ExpressionFor>; fn $method(self, other: ExpressionFor>) -> Self::Output { let $left = try_expression!(Constrained::<_, C>::new(self)); let $right = try_expression!(other); $f } } }; } impl_primitive_binary_operation_for_expression!(); impl $trait> for Constrained where T: Primitive, C: Constraint, C::Divergence: Divergence, { type Output = ExpressionFor; fn $method(self, other: ExpressionFor) -> Self::Output { let $left = self; let $right = try_expression!(other); $f } } impl $trait> for ExpressionFor> where T: Primitive, C: Constraint, C::Divergence: Divergence, { type Output = Self; fn $method(self, other: Constrained) -> Self::Output { let $left = try_expression!(self); let $right = other; $f } } impl $trait>> for ExpressionFor> where T: Primitive, C: Constraint, C::Divergence: Divergence, { type Output = Self; fn $method(self, other: Self) -> Self::Output { let $left = try_expression!(self); let $right = try_expression!(other); $f } } impl $trait for ExpressionFor> where T: Primitive, C: Constraint, C::Divergence: Divergence, { type Output = Self; fn $method(self, other: T) -> Self::Output { let $left = try_expression!(self); let $right = try_expression!(Constrained::<_, C>::new(other)); $f } } }; } impl_binary_operation_for_expression!(); macro_rules! impl_try_from_for_expression { () => { with_primitives!(impl_try_from_for_expression); }; (primitive => $t:ty) => { impl TryFrom, C::Error>> for Constrained<$t, C> where C: Constraint, { type Error = C::Error; fn try_from( expression: Expression, C::Error>, ) -> Result { match expression { Defined(defined) => Ok(defined), Undefined(undefined) => Err(undefined), } } } impl TryFrom, C::Error>> for $t where C: Constraint, { type Error = C::Error; fn try_from( expression: Expression, C::Error>, ) -> Result { match expression { Defined(defined) => Ok(defined.into()), Undefined(undefined) => Err(undefined), } } } }; } impl_try_from_for_expression!(); decorum-0.4.0/src/hash.rs000064400000000000000000000030361046102023000133450ustar 00000000000000//! Hashing of IEEE 754 floating-point values. //! //! This module provides hashing for primitive floating-point values. Given the set of zero //! representations $Z$ and set of `NaN` representations $N$, hashing coalesces their //! representations such that: //! //! $$ //! \begin{aligned} //! h(a)=h(b)&\mid a\in{Z},~b\in{Z}\cr\[1em\] //! h(a)=h(b)&\mid a\in{N},~b\in{N} //! \end{aligned} //! $$ //! //! The [`CanonicalHash`] trait agrees with the ordering and equivalence relations of the //! [`CanonicalOrd`] and [`CanonicalEq`] traits. //! //! [`CanonicalEq`]: crate::cmp::CanonicalEq //! [`CanonicalOrd`]: crate::cmp::CanonicalOrd use core::hash::{Hash, Hasher}; use crate::ToCanonical; pub trait CanonicalHash { fn hash_canonical(&self, state: &mut H) where H: Hasher; } // TODO: This implementation conflicts with implementations over references to a type `T` where // `T: CanonicalHash`. However, this is because `rustc` claims that "sealed" traits can be // implemented downstream, which isn't true. Write reference implementations when possible // (or consider removing this blanket implementation). impl CanonicalHash for T where T: ToCanonical, { fn hash_canonical(&self, state: &mut H) where H: Hasher, { self.to_canonical().hash(state) } } impl CanonicalHash for [T] where T: CanonicalHash, { fn hash_canonical(&self, state: &mut H) where H: Hasher, { for item in self { item.hash_canonical(state); } } } decorum-0.4.0/src/lib.rs000064400000000000000000000544361046102023000132020ustar 00000000000000//! Making floating-point behave: total ordering, equivalence, hashing, constraints, error //! handling, and more for IEEE 754 floating-point representations. //! //! Decorum provides APIs for extending IEEE 754 floating-point. This is primarily accomplished //! with [proxy types][`proxy`] that wrap primitive floating-point types and compose //! [constraints][`constraint`] and [divergence] to configure behavior. Decorum also provides //! numerous traits describing real numerics and IEEE 754 encoding. //! //! # Proxy Types //! //! [`Constrained`] types wrap primitive floating-point types and constrain the set of values that //! they can represent. These types use the same representation as primitives and in many cases can //! be used as drop-in replacements, in particular the [`Total`] type. [`Constrained`] supports //! numerous traits and APIs (including third-party integrations) and always provides a complete //! API for real numbers. //! //! Constrained types and their [constraints][`constraint`] operate on three subsets of IEEE 754 //! floating-point values: //! //! | Subset | Example Member | //! |--------------|----------------| //! | real numbers | `3.1459` | //! | infinities | `+Inf` | //! | not-a-number | `NaN` | //! //! These subsets are reflected throughout APIs, in particular in traits concerning IEEE 754 //! encoding and constraints. [`Constraint`]s describe which subsets are members of a proxy type //! and are composed with a [divergence], which further describes the outputs and error behavior of //! operations. //! //! These types can be configured to, for example, cause a panic in debugging builds whenever a //! `NaN` is encountered or enable structured error handling of extended real numbers where any //! `NaN` is interpreted as undefined and yields an explicit error value. //! //! Constrained types and their components are provided by the [`proxy`], [`constraint`], and //! [`divergence`] modules. Numerous type definitions are also provided in the crate root: //! //! | Type Definition | Subsets | //! |------------------|----------------------------------------| //! | [`Total`] | real numbers, infinities, not-a-number | //! | [`ExtendedReal`] | real numbers, infinities | //! | [`Real`] | real numbers | //! //! # Equivalence and Ordering //! //! The [`cmp`] module provides APIs for comparing floating-point representations as well as other //! partially ordered types. For example, it provides traits for intrinic comparisons of partially //! ordered types that propagate `NaN`s when used with floating-point representations. It also //! defines a non-standard total ordering for complete floating-point types: //! //! $$-\infin<\cdots<0<\cdots<\infin<\text{NaN}$$ //! //! Note that all `NaN` representations are considered equivalent in this relation. The [`Total`] //! proxy type uses this ordering. //! //! # Hashing //! //! The [`hash`] module provides traits for hashing floating-point representations. Hashing is //! consistent with the total ordering defined by the [`cmp`] module. Constrained types implement //! the standard [`Hash`] trait via this module and it also provides functions for hashing //! primitive floating-point types. //! //! # Numeric Traits //! //! The [`real`] module provides traits that describe real numbers and their approximation via //! floating-point representations. These traits describe the codomain of operations and respect //! the branching behavior of such functions. For example, many functions over real numbers have a //! range that includes non-reals (such as undefined). Additionally, these traits feature ergonomic //! improvements on similar traits in the crate ecosystem. //! //! # Expressions //! //! [`Expression`] types represent the output of computations using constrained [`Constrained`] //! types. They provide structured types that directly encode divergence (errors) as values. Unlike //! other branch types, [`Expression`] also supports the same numeric operations as [`Constrained`] //! types, so they can be used fluently in numeric expressions without matching or trying. //! //! ```rust //! use decorum::constraint::IsReal; //! use decorum::divergence::OrError; //! use decorum::proxy::{Constrained, OutputFor}; //! //! type Real = Constrained>; //! type Expr = OutputFor; //! //! fn f(x: Real, y: Real) -> Expr { //! let z = x + y; //! z / x //! } //! //! let z = f(Real::assert(3.0), Real::assert(4.0)); //! assert!(z.is_defined()); //! ``` //! //! For finer control, the [`try_expression`] macro can be used to differentiate between //! expressions and defined results. When using a nightly Rust toolchain, the `unstable` Cargo //! feature also implements the unstable (at time of writing) [`Try`] trait for [`Expression`] so //! that the try operator `?` may be used instead. //! //! ```rust,ignore //! use decorum::constraint::IsReal; //! use decorum::divergence::OrError; //! use decorum::proxy::{OutputFor, Constrained}; //! use decorum::real::UnaryRealFunction; //! //! type Real = Constrained>; //! type Expr = OutputFor; //! //! # fn fallible() -> Expr { //! fn f(x: Real, y: Real) -> Expr { //! x / y //! } //! //! let z = f(Real::PI, Real::ONE)?; // OK: `z` is `Real`. //! let w = f(Real::PI, Real::ZERO)?; // Error: this returns `Expression::Undefined`. //! // ... //! # f(Real::PI, Real::ONE) //! # } //! ``` //! //! [`Constraint`]: crate::constraint::Constraint //! [`Expression`]: crate::expression::Expression //! [`Try`]: core::ops::Try #![doc( html_favicon_url = "https://raw.githubusercontent.com/olson-sean-k/decorum/master/doc/decorum-favicon.ico" )] #![doc( html_logo_url = "https://raw.githubusercontent.com/olson-sean-k/decorum/master/doc/decorum.svg?sanitize=true" )] #![no_std] #![cfg_attr(all(nightly, feature = "unstable"), feature(try_trait_v2))] #[cfg(feature = "std")] extern crate std; pub mod cmp; pub mod constraint; pub mod divergence; pub mod expression; pub mod hash; pub mod proxy; pub mod real; use core::hash::Hash; use core::num::FpCategory; use crate::cmp::EmptyOrd; use crate::constraint::{IsExtendedReal, IsFloat, IsReal}; use crate::divergence::OrPanic; use crate::proxy::{Constrained, Nan}; use crate::real::{ BinaryRealFunction, Endofunction, Function, RealFunction, Sign, UnaryRealFunction, }; mod sealed { use core::convert::Infallible; use core::fmt::{self, Formatter}; pub trait Sealed {} impl Sealed for Infallible {} pub trait StaticDebug { fn fmt(formatter: &mut Formatter<'_>) -> fmt::Result; } } use crate::sealed::Sealed; pub mod prelude { pub use crate::cmp::{CanonicalEq, CanonicalOrd}; pub use crate::hash::CanonicalHash; pub use crate::real::{BinaryRealFunction, UnaryRealFunction}; } /// IEEE 754 floating-point representation with non-standard total ordering and hashing. /// /// This [`Constrained`] type applies no constraints and no divergence. It can trivially replace /// primitive floating point types and implements the standard [`Eq`] and [`Ord`] traits. See the /// [`cmp`] module for more details about these relations. pub type Total = Constrained; /// IEEE 754 floating-point representation that must be an extended real. pub type ExtendedReal = Constrained>; /// IEEE 754 floating-point representation that must not be `NaN`. pub type NotNan = ExtendedReal; /// 32-bit IEEE 754 floating-point representation that must be an extended real (not `NaN`). pub type E32 = ExtendedReal; /// 64-bit IEEE 754 floating-point representation that must be an extended real (not `NaN`). pub type E64 = ExtendedReal; /// IEEE 754 floating-point representation that must be a real number. pub type Real = Constrained>; /// 32-bit IEEE 754 floating-point representation that must be a real number. pub type R32 = Real; /// 64-bit IEEE 754 floating-point representation that must be a real number. pub type R64 = Real; /// Converts IEEE 754 floating-point values to a canonicalized form. pub trait ToCanonical: BaseEncoding + Copy { type Canonical: Copy + Eq + Hash; /// Conversion to a canonical representation. /// /// This function collapses real numbers, zeroes, infinities, and `NaN`s into a canonical form /// such that every semantic value has a unique representation with an equivalence relation. fn to_canonical(self) -> Self::Canonical; } // TODO: Implement this differently for differently sized primitive types. impl ToCanonical for T where T: Primitive, { type Canonical = u64; fn to_canonical(self) -> Self::Canonical { const SIGN_MASK: u64 = 0x8000_0000_0000_0000; const EXPONENT_MASK: u64 = 0x7ff0_0000_0000_0000; const MANTISSA_MASK: u64 = 0x000f_ffff_ffff_ffff; const CANONICAL_NAN_BITS: u64 = 0x7ff8_0000_0000_0000; const CANONICAL_ZERO_BITS: u64 = 0x0; if self.is_nan() { CANONICAL_NAN_BITS } else { let (mantissa, exponent, sign) = self.integer_decode(); if mantissa == 0 { CANONICAL_ZERO_BITS } else { let exponent = u64::from(exponent as u16); let sign = u64::from(sign > 0); (mantissa & MANTISSA_MASK) | ((exponent << 52) & EXPONENT_MASK) | ((sign << 63) & SIGN_MASK) } } } } /// A type with an IEEE 754 floating-point representation that exposes its basic encoding. /// /// `BaseEncoding` types have a floating-point representation ([`binaryN`]), **but may not support /// nor expose other elements of the specification**. This trait describes the most basic /// non-computational elements of the encoding and does not specify the inhabitants of a type. /// /// [`binaryN`]: https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats pub trait BaseEncoding: Copy { const MAX_FINITE: Self; const MIN_FINITE: Self; const MIN_POSITIVE_NORMAL: Self; const EPSILON: Self; fn classify(self) -> FpCategory; fn is_normal(self) -> bool; fn is_sign_positive(self) -> bool; fn is_sign_negative(self) -> bool; #[cfg(feature = "std")] fn signum(self) -> Self; fn integer_decode(self) -> (u64, i16, i8); } impl BaseEncoding for f32 { const MAX_FINITE: Self = f32::MAX; const MIN_FINITE: Self = f32::MIN; const MIN_POSITIVE_NORMAL: Self = f32::MIN_POSITIVE; const EPSILON: Self = f32::EPSILON; fn classify(self) -> FpCategory { self.classify() } fn is_normal(self) -> bool { self.is_normal() } fn is_sign_positive(self) -> bool { Self::is_sign_positive(self) } fn is_sign_negative(self) -> bool { Self::is_sign_negative(self) } #[cfg(feature = "std")] fn signum(self) -> Self { Self::signum(self) } fn integer_decode(self) -> (u64, i16, i8) { let bits = self.to_bits(); let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 }; let exponent: i16 = ((bits >> 23) & 0xff) as i16; let mantissa = if exponent == 0 { (bits & 0x7f_ffff) << 1 } else { (bits & 0x7f_ffff) | 0x80_0000 }; (mantissa as u64, exponent - (127 + 23), sign) } } impl BaseEncoding for f64 { const MAX_FINITE: Self = f64::MAX; const MIN_FINITE: Self = f64::MIN; const MIN_POSITIVE_NORMAL: Self = f64::MIN_POSITIVE; const EPSILON: Self = f64::EPSILON; fn classify(self) -> FpCategory { self.classify() } fn is_normal(self) -> bool { self.is_normal() } fn is_sign_positive(self) -> bool { Self::is_sign_positive(self) } fn is_sign_negative(self) -> bool { Self::is_sign_negative(self) } #[cfg(feature = "std")] fn signum(self) -> Self { Self::signum(self) } fn integer_decode(self) -> (u64, i16, i8) { let bits = self.to_bits(); let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 }; let exponent: i16 = ((bits >> 52) & 0x7ff) as i16; let mantissa = if exponent == 0 { (bits & 0xf_ffff_ffff_ffff) << 1 } else { (bits & 0xf_ffff_ffff_ffff) | 0x10_0000_0000_0000 }; (mantissa, exponent - (1023 + 52), sign) } } /// A type with an IEEE 754 floating-point representation that supports infinities. /// /// `InfinityEncoding` types have `-INF` and `+INF` inhabitants. pub trait InfinityEncoding: Copy { const INFINITY: Self; const NEG_INFINITY: Self; fn is_infinite(self) -> bool; fn is_finite(self) -> bool; } /// A type with an IEEE 754 floating-point representation that supports `NaN`s. /// /// `NanEncoding` types have `NaN` inhabitants. pub trait NanEncoding: Copy { /// The type of the arbitrary `Nan` representation [`NAN`]. /// /// This may be an intermediate type other than `Self`. In particular, primitive IEEE 754 /// floating-point types represent `NaN` with the [`Nan`] type, which is incomparable and must /// first be converted into its primitive types. /// /// For proxy types, which are totally ordered, this type satisfies the bound `Eq + Ord`. /// /// [`NAN`]: crate::NanEncoding::NAN type Nan; /// An arbitrary representation of `NaN`. const NAN: Self::Nan; fn is_nan(self) -> bool; } /// A primitive IEEE 754 floating-point type. pub trait Primitive: BaseEncoding + Copy + EmptyOrd + Endofunction + InfinityEncoding + NanEncoding> + PartialEq + PartialOrd + RealFunction + Sealed { } // TODO: Remove this. Of course. fn _sanity() { use crate::real::FloatFunction; type Real = Constrained>; fn f(x: T) -> T where T: FloatFunction, { -x } fn g(x: T, y: U) -> T where T: BinaryRealFunction + Endofunction + FloatFunction, { (x + T::ONE) * y } fn h(x: T, y: T) -> T where T: Endofunction + FloatFunction, { x + y } let x = Real::ONE; let y = g(f(x), 2.0); let z = h(y, Real::assert(1.0)); let _ = f(y + z); } macro_rules! with_primitives { ($f:ident) => { $f!(primitive => f32); $f!(primitive => f64); } } pub(crate) use with_primitives; macro_rules! with_binary_operations { ($f:ident) => { $f!(operation => Add::add); $f!(operation => Div::div); $f!(operation => Mul::mul); $f!(operation => Rem::rem); $f!(operation => Sub::sub); }; } pub(crate) use with_binary_operations; /// Implements real number and floating-point traits for primitive types. macro_rules! impl_primitive { () => { with_primitives!(impl_primitive); }; (primitive => $t:ident) => { impl BinaryRealFunction<$t> for $t { #[cfg(feature = "std")] fn div_euclid(self, n: Self) -> Self::Codomain { <$t>::div_euclid(self, n) } #[cfg(feature = "std")] fn rem_euclid(self, n: Self) -> Self::Codomain { <$t>::rem_euclid(self, n) } #[cfg(feature = "std")] fn pow(self, n: Self) -> Self::Codomain { <$t>::powf(self, n) } #[cfg(feature = "std")] fn log(self, base: Self) -> Self::Codomain { <$t>::log(self, base) } #[cfg(feature = "std")] fn hypot(self, other: Self) -> Self::Codomain { <$t>::hypot(self, other) } #[cfg(feature = "std")] fn atan2(self, other: Self) -> Self { <$t>::atan2(self, other) } } impl Function for $t { type Codomain = $t; } impl InfinityEncoding for $t { const INFINITY: Self = <$t>::INFINITY; const NEG_INFINITY: Self = <$t>::NEG_INFINITY; fn is_infinite(self) -> bool { self.is_infinite() } fn is_finite(self) -> bool { self.is_finite() } } impl NanEncoding for $t { type Nan = Nan<$t>; const NAN: Self::Nan = Nan::unchecked(<$t>::NAN); fn is_nan(self) -> bool { self.is_nan() } } impl Primitive for $t {} impl Sealed for $t {} impl UnaryRealFunction for $t { // TODO: The propagation from a constant in a module requires that this macro accept an // `ident` token rather than a `ty` token. Use `ty` if these constants become // associated constants of the primitive types. const ZERO: Self = 0.0; const ONE: Self = 1.0; const E: Self = core::$t::consts::E; const PI: Self = core::$t::consts::PI; const FRAC_1_PI: Self = core::$t::consts::FRAC_1_PI; const FRAC_2_PI: Self = core::$t::consts::FRAC_2_PI; const FRAC_2_SQRT_PI: Self = core::$t::consts::FRAC_2_SQRT_PI; const FRAC_PI_2: Self = core::$t::consts::FRAC_PI_2; const FRAC_PI_3: Self = core::$t::consts::FRAC_PI_3; const FRAC_PI_4: Self = core::$t::consts::FRAC_PI_4; const FRAC_PI_6: Self = core::$t::consts::FRAC_PI_6; const FRAC_PI_8: Self = core::$t::consts::FRAC_PI_8; const SQRT_2: Self = core::$t::consts::SQRT_2; const FRAC_1_SQRT_2: Self = core::$t::consts::FRAC_1_SQRT_2; const LN_2: Self = core::$t::consts::LN_2; const LN_10: Self = core::$t::consts::LN_10; const LOG2_E: Self = core::$t::consts::LOG2_E; const LOG10_E: Self = core::$t::consts::LOG10_E; fn is_zero(self) -> bool { self == Self::ZERO } fn is_one(self) -> bool { self == Self::ONE } fn sign(self) -> Sign { if self.is_nan() || self.is_zero() { Sign::Zero } else if self > 0.0 { Sign::Positive } else { Sign::Negative } } #[cfg(feature = "std")] fn abs(self) -> Self { <$t>::abs(self) } #[cfg(feature = "std")] fn floor(self) -> Self { <$t>::floor(self) } #[cfg(feature = "std")] fn ceil(self) -> Self { <$t>::ceil(self) } #[cfg(feature = "std")] fn round(self) -> Self { <$t>::round(self) } #[cfg(feature = "std")] fn trunc(self) -> Self { <$t>::trunc(self) } #[cfg(feature = "std")] fn fract(self) -> Self { <$t>::fract(self) } fn recip(self) -> Self::Codomain { <$t>::recip(self) } #[cfg(feature = "std")] fn powi(self, n: i32) -> Self::Codomain { <$t>::powi(self, n) } #[cfg(feature = "std")] fn sqrt(self) -> Self::Codomain { <$t>::sqrt(self) } #[cfg(feature = "std")] fn cbrt(self) -> Self { <$t>::cbrt(self) } #[cfg(feature = "std")] fn exp(self) -> Self::Codomain { <$t>::exp(self) } #[cfg(feature = "std")] fn exp2(self) -> Self::Codomain { <$t>::exp2(self) } #[cfg(feature = "std")] fn exp_m1(self) -> Self::Codomain { <$t>::exp_m1(self) } #[cfg(feature = "std")] fn ln(self) -> Self::Codomain { <$t>::ln(self) } #[cfg(feature = "std")] fn log2(self) -> Self::Codomain { <$t>::log2(self) } #[cfg(feature = "std")] fn log10(self) -> Self::Codomain { <$t>::log10(self) } #[cfg(feature = "std")] fn ln_1p(self) -> Self::Codomain { <$t>::ln_1p(self) } #[cfg(feature = "std")] fn to_degrees(self) -> Self::Codomain { <$t>::to_degrees(self) } #[cfg(feature = "std")] fn to_radians(self) -> Self { <$t>::to_radians(self) } #[cfg(feature = "std")] fn sin(self) -> Self { <$t>::sin(self) } #[cfg(feature = "std")] fn cos(self) -> Self { <$t>::cos(self) } #[cfg(feature = "std")] fn tan(self) -> Self::Codomain { <$t>::tan(self) } #[cfg(feature = "std")] fn asin(self) -> Self::Codomain { <$t>::asin(self) } #[cfg(feature = "std")] fn acos(self) -> Self::Codomain { <$t>::acos(self) } #[cfg(feature = "std")] fn atan(self) -> Self { <$t>::atan(self) } #[cfg(feature = "std")] fn sin_cos(self) -> (Self, Self) { <$t>::sin_cos(self) } #[cfg(feature = "std")] fn sinh(self) -> Self { <$t>::sinh(self) } #[cfg(feature = "std")] fn cosh(self) -> Self { <$t>::cosh(self) } #[cfg(feature = "std")] fn tanh(self) -> Self { <$t>::tanh(self) } #[cfg(feature = "std")] fn asinh(self) -> Self::Codomain { <$t>::asinh(self) } #[cfg(feature = "std")] fn acosh(self) -> Self::Codomain { <$t>::acosh(self) } #[cfg(feature = "std")] fn atanh(self) -> Self::Codomain { <$t>::atanh(self) } } }; } impl_primitive!(); decorum-0.4.0/src/proxy/constrained.rs000064400000000000000000002064771046102023000161320ustar 00000000000000#[cfg(feature = "approx")] use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use core::cmp::Ordering; use core::fmt::{self, Debug, Display, Formatter, LowerExp, UpperExp}; use core::hash::{Hash, Hasher}; use core::iter::{Product, Sum}; use core::marker::PhantomData; use core::mem; use core::num::FpCategory; use core::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; use core::str::FromStr; #[cfg(not(feature = "std"))] use num_traits::float::FloatCore as Float; #[cfg(feature = "std")] use num_traits::Float; use num_traits::{ Bounded, FloatConst, FromPrimitive, Num, NumCast, One, Signed, ToPrimitive, Zero, }; #[cfg(feature = "serde")] use serde_derive::{Deserialize, Serialize}; use crate::cmp::{CanonicalEq, CanonicalOrd, EmptyInhabitant, EmptyOrd}; use crate::constraint::{ Constraint, ExpectConstrained, InfinitySet, IsExtendedReal, IsFloat, IsReal, Member, NanSet, SubsetOf, SupersetOf, }; use crate::divergence::{self, Divergence, NonResidual}; use crate::expression::Expression; use crate::hash::CanonicalHash; use crate::proxy::Proxy; #[cfg(feature = "serde")] use crate::proxy::Serde; use crate::real::{BinaryRealFunction, Function, Sign, UnaryRealFunction}; use crate::sealed::StaticDebug; use crate::{ with_binary_operations, with_primitives, BaseEncoding, ExtendedReal, InfinityEncoding, NanEncoding, Primitive, Real, ToCanonical, Total, }; pub type OutputFor

= divergence::OutputFor, P, ErrorFor

>; pub type ConstraintFor

=

::Constraint; pub type DivergenceFor

= as Constraint>::Divergence; pub type ErrorFor

= as Constraint>::Error; pub type ExpressionFor

= Expression>; /// A constrained IEEE 754 floating-point proxy type. pub trait ConstrainedProxy: Proxy { type Constraint: Constraint; } /// IEEE 754 floating-point proxy that provides total ordering, equivalence, hashing, constraints, /// and error handling. /// /// `Constrained` types wrap primitive floating-point type and extend their behavior. For example, /// all proxy types implement the standard [`Eq`], [`Hash`], and [`Ord`] traits, sometimes via the /// non-standard relations described in the [`cmp`] module when `NaN`s must be considered. /// Constraints and divergence can be composed to determine the subset of floating-point values /// that a proxy supports and how the proxy behaves when those constraints are violated. /// /// Various type definitions are provided for various useful proxy constructions, such as the /// [`Total`] type, which extends floating-point types with a non-standard total ordering. /// /// [`cmp`]: crate::cmp #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr( feature = "serde", serde( bound( deserialize = "T: serde::Deserialize<'de> + Primitive, \ C: Constraint, \ C::Error: Display", serialize = "T: Primitive + serde::Serialize, \ C: Constraint" ), try_from = "Serde", into = "Serde" ) )] #[repr(transparent)] pub struct Constrained { inner: T, #[cfg_attr(feature = "serde", serde(skip))] phantom: PhantomData C>, } impl Constrained { pub(crate) const fn unchecked(inner: T) -> Self { Constrained { inner, phantom: PhantomData, } } pub(crate) fn with_inner(self, f: F) -> U where F: FnOnce(T) -> U, { f(self.inner) } } impl Constrained where T: Copy, { /// Converts a proxy into its underlying primitive floating-point type. /// /// # Examples /// /// ```rust /// use decorum::R64; /// /// fn f() -> R64 { /// # use decorum::real::UnaryRealFunction; /// # R64::ZERO /// // ... /// } /// /// let x: f64 = f().into_inner(); /// // The standard `From` and `Into` traits can also be used. /// let y: f64 = f().into(); /// ``` pub const fn into_inner(self) -> T { self.inner } pub(crate) fn map_unchecked(self, f: F) -> Self where F: FnOnce(T) -> T, { Constrained::unchecked(f(self.into_inner())) } } impl Constrained where T: Debug, C: StaticDebug, { /// Writes a thorough [debugging][`Debug`] description of the proxy to the given [`Formatter`]. /// /// This function is similar to [`debug`], but writes a verbose description of the proxy into a /// [`Formatter`] rather than returning a [`Debug`] implementation. /// /// [`debug`]: crate::proxy::Constrained::debug pub fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "Constrained<")?; C::fmt(formatter)?; write!(formatter, ">({:?})", self.inner) } /// Gets a [`Debug`] implementation that thoroughly describes the proxy. /// /// `Constrained` types implement [`Display`] and [`Debug`], but these implementations omit /// more specific information about [constraints][`constraint`] and [divergence]. This function /// provides an instance of a verbose [`Debug`] type that more thoroughly describes the /// behavior of the proxy. /// /// [`constraint`]: crate::constraint pub const fn debug(&self) -> impl '_ + Copy + Debug { struct Formatted<'a, T, C>(&'a Constrained); impl Clone for Formatted<'_, T, C> { fn clone(&self) -> Self { *self } } impl Copy for Formatted<'_, T, C> {} impl Debug for Formatted<'_, T, C> where T: Debug, C: StaticDebug, { fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { Constrained::fmt(self.0, formatter) } } Formatted(self) } } impl Constrained where T: Primitive, C: Constraint, { /// Constructs a proxy from a primitive IEEE 754 floating-point value. /// /// This function returns the output type of the [divergence] of the proxy and invokes its /// error behavior if the floating-point value does not satisfy constraints. Note that this /// function never fails for [`Total`], which has no constraints. /// /// The distinctions in output and behavior are static and are determined by the type /// parameters of the `Constrained` type constructor. /// /// # Panics /// /// This function panics if the primitive floating-point value does not satisfy the constraints /// of the proxy **and** the [divergence] of the proxy panics. For example, the [`OrPanic`] /// divergence asserts constraints and panics. /// /// # Errors /// /// Returns an error if the primitive floating-point value does not satisfy the constraints of /// the proxy **and** the [divergence] of the proxy encodes errors in its output type. For /// example, the output type of the `OrError` divergence is [`Expression`] and /// this function returns the [`Undefined`] variant if the constraint is violated. /// /// # Examples /// /// Fallibly constructing proxies from primitive floating-point values: /// /// ```rust /// use decorum::constraint::IsReal; /// use decorum::divergence::{AsResult, OrError}; /// use decorum::proxy::Constrained; /// /// // The output type of `Real` is `Result`. /// type Real = Constrained>>; /// /// let x = Real::new(2.0).unwrap(); // The output type of `new` is `Result` per `TryResult`. /// ``` /// /// Asserting proxy construction from primitive floating-point values: /// /// ```rust,should_panic /// use decorum::constraint::IsReal; /// use decorum::divergence::OrPanic; /// use decorum::proxy::Constrained; /// /// // The output type of `OrPanic` is `Real`. /// type Real = Constrained>; /// /// let x = Real::new(2.0); // The output type of `new` is `Real` per `OrPanic`. /// let y = Real::new(0.0 / 0.0); // Panics. /// ``` /// /// [`OrPanic`]: crate::divergence::OrPanic /// [`Undefined`]: crate::expression::Expression::Undefined pub fn new(inner: T) -> OutputFor { C::map(inner, |inner| Constrained { inner, phantom: PhantomData, }) } /// Fallibly constructs a proxy from a primitive IEEE 754 floating-point value. /// /// This construction mirrors the [`TryFrom`] implementation and is independent of the /// [divergence] of the proxy; it always outputs a [`Result`] and never panics. /// /// # Errors /// /// Returns an error if the primitive floating-point value does not satisfy the constraints of /// the proxy. Note that the error type of the [`IsFloat`] constraint is [`Infallible`] and the /// construction of [`Total`]s cannot fail here. /// /// # Examples /// /// Constructing proxies from primitive floating-point values: /// /// ```rust /// use decorum::constraint::IsReal; /// use decorum::divergence::OrPanic; /// use decorum::proxy::Constrained; /// /// type Real = Constrained>; /// /// fn f(x: Real) -> Real { /// x * 2.0 /// } /// /// let y = f(Real::try_new(2.0).unwrap()); /// // The `TryFrom` and `TryInto` traits can also be used. /// let z = f(2.0.try_into().unwrap()); /// ``` /// /// A proxy construction that fails: /// /// ```rust,should_panic /// use decorum::constraint::IsReal; /// use decorum::divergence::OrPanic; /// use decorum::proxy::Constrained; /// /// type Real = Constrained>; /// /// // `IsReal` does not allow `NaN`s, but `0.0 / 0.0` produces a `NaN`. /// let x = Real::try_new(0.0 / 0.0).unwrap(); // Panics when unwrapping. /// ``` /// /// [`Infallible`]: core::convert::Infallible pub fn try_new(inner: T) -> Result { C::check(inner).map(|_| Constrained { inner, phantom: PhantomData, }) } /// Constructs a proxy from a primitive IEEE 754 floating-point value and asserts that its /// constraints are satisfied. /// /// This construction is independent of the [divergence] of the proxy and always asserts /// constraints (even when the divergence is fallible). Note that this function never fails /// (panics) for [`Total`], which has no constraints. /// /// # Panics /// /// **This construction panics if the primitive floating-point value does not satisfy the /// constraints of the proxy.** /// /// # Examples /// /// Constructing proxies from primitive floating-point values: /// /// ```rust /// use decorum::constraint::IsReal; /// use decorum::divergence::OrPanic; /// use decorum::proxy::Constrained; /// /// type Real = Constrained>; /// /// fn f(x: Real) -> Real { /// x * 2.0 /// } /// /// let y = f(Real::assert(2.0)); /// ``` /// /// A proxy construction that fails: /// /// ```rust,should_panic /// use decorum::constraint::IsReal; /// use decorum::divergence::OrPanic; /// use decorum::proxy::Constrained; /// /// type Real = Constrained>; /// /// // `IsReal` does not allow `NaN`s, but `0.0 / 0.0` produces a `NaN`. /// let x = Real::assert(0.0 / 0.0); // Panics. /// ``` pub fn assert(inner: T) -> Self { Self::try_new(inner).expect_constrained() } /// Converts a slice of primitive IEEE 754 floating-point values into a slice of proxies. /// /// This conversion must check the constraints of the proxy against each floating-point value /// and so has `O(N)` time complexity. **When using the [`IsFloat`] constraint, prefer the /// infallible and `O(1)` [`from_slice`] function.** /// /// # Errors /// /// Returns an error if any of the primitive floating-point values in the slice do not satisfy /// the constraints of the proxy. /// /// [`from_slice`]: crate::Total::from_slice pub fn try_from_slice<'a>(slice: &'a [T]) -> Result<&'a [Self], C::Error> { slice.iter().try_for_each(|inner| C::check(*inner))?; // SAFETY: `Constrained` is `repr(transparent)` and has the same binary representation // as its input type `T`. This means that it is safe to transmute `T` to // `Constrained`. Ok(unsafe { mem::transmute::<&'a [T], &'a [Self]>(slice) }) } /// Converts a mutable slice of primitive IEEE 754 floating-point values into a mutable slice /// of proxies. /// /// This conversion must check the constraints of the proxy against each floating-point value /// and so has `O(N)` time complexity. **When using the [`IsFloat`] constraint, prefer the /// infallible and `O(1)` [`from_mut_slice`] function.** /// /// # Errors /// /// Returns an error if any of the primitive floating-point values in the /// slice do not satisfy the constraints of the proxy. /// /// [`from_mut_slice`]: crate::Total::from_mut_slice pub fn try_from_mut_slice<'a>(slice: &'a mut [T]) -> Result<&'a mut [Self], C::Error> { slice.iter().try_for_each(|inner| C::check(*inner))?; // SAFETY: `Constrained` is `repr(transparent)` and has the same binary representation // as its input type `T`. This means that it is safe to transmute `T` to // `Constrained`. Ok(unsafe { mem::transmute::<&'a mut [T], &'a mut [Self]>(slice) }) } /// Converts a proxy into another proxy that is capable of representing a superset of its /// values per its constraint. /// /// # Examples /// /// ```rust /// use decorum::divergence::OrPanic; /// use decorum::real::UnaryRealFunction; /// use decorum::{E64, R64}; /// /// let x = R64::::ZERO; /// let y = E64::from_subset(x); // `E64` allows a superset of the values of `R64`. /// ``` pub fn from_subset(other: Constrained) -> Self where C2: Constraint + SubsetOf, { Self::unchecked(other.into_inner()) } /// Converts a proxy into another proxy that is capable of representing a superset of its /// values per its constraint. /// /// # Examples /// /// ```rust /// use decorum::real::UnaryRealFunction; /// use decorum::{E64, R64}; /// /// let x = R64::ZERO; /// let y: E64 = x.into_superset(); // `E64` allows a superset of the values of `R64`. /// ``` pub fn into_superset(self) -> Constrained where C2: Constraint + SupersetOf, { Constrained::unchecked(self.into_inner()) } /// Converts a proxy into its corresponding [`Expression`]. /// /// The output of this function is always the [`Defined`] variant. /// /// [`Defined`]: crate::expression::Expression::Defined pub fn into_expression(self) -> ExpressionFor { Expression::from(self) } pub(crate) fn map(self, f: F) -> OutputFor where F: FnOnce(T) -> T, { Self::new(f(self.into_inner())) } pub(crate) fn zip_map(self, other: Constrained, f: F) -> OutputFor where C2: Constraint, F: FnOnce(T, T) -> T, { Self::new(f(self.into_inner(), other.into_inner())) } } impl Total where T: Primitive, { /// Converts a slice of primitive IEEE 754 floating-point values into a slice of `Total`s. /// /// Unlike [`try_from_slice`], this conversion is infallible and trivial and so has `O(1)` time /// complexity. /// /// [`try_from_slice`]: crate::proxy::Constrained::try_from_slice pub fn from_slice<'a>(slice: &'a [T]) -> &'a [Self] { // SAFETY: `Constrained` is `repr(transparent)` and has the same binary representation // as its input type `T`. This means that it is safe to transmute `T` to // `Constrained`. unsafe { mem::transmute::<&'a [T], &'a [Self]>(slice) } } /// Converts a mutable slice of primitive floating-point values into a mutable slice of /// `Total`s. /// /// Unlike [`try_from_mut_slice`], this conversion is infallible and trivial and so has `O(1)` /// time complexity. /// /// [`try_from_mut_slice`]: crate::proxy::Constrained::try_from_mut_slice pub fn from_mut_slice<'a>(slice: &'a mut [T]) -> &'a mut [Self] { // SAFETY: `Constrained` is `repr(transparent)` and has the same binary representation // as its input type `T`. This means that it is safe to transmute `T` to // `Constrained`. unsafe { mem::transmute::<&'a mut [T], &'a mut [Self]>(slice) } } } #[cfg(feature = "approx")] impl AbsDiffEq for Constrained where T: AbsDiffEq + Primitive, C: Constraint, { type Epsilon = Self; fn default_epsilon() -> Self::Epsilon { Self::assert(T::default_epsilon()) } fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { self.into_inner() .abs_diff_eq(&other.into_inner(), epsilon.into_inner()) } } impl Add for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn add(self, other: Self) -> Self::Output { self.zip_map(other, Add::add) } } impl Add for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn add(self, other: T) -> Self::Output { self.map(|inner| inner + other) } } impl AddAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn add_assign(&mut self, other: Self) { *self = *self + other; } } impl AddAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn add_assign(&mut self, other: T) { *self = self.map(|inner| inner + other); } } impl AsRef for Constrained { fn as_ref(&self) -> &T { &self.inner } } impl BinaryRealFunction for Constrained where T: Primitive, C: Constraint, { #[cfg(feature = "std")] fn div_euclid(self, n: Self) -> Self::Codomain { self.zip_map(n, BinaryRealFunction::div_euclid) } #[cfg(feature = "std")] fn rem_euclid(self, n: Self) -> Self::Codomain { self.zip_map(n, BinaryRealFunction::rem_euclid) } #[cfg(feature = "std")] fn pow(self, n: Self) -> Self::Codomain { self.zip_map(n, BinaryRealFunction::pow) } #[cfg(feature = "std")] fn log(self, base: Self) -> Self::Codomain { self.zip_map(base, BinaryRealFunction::log) } #[cfg(feature = "std")] fn hypot(self, other: Self) -> Self::Codomain { self.zip_map(other, BinaryRealFunction::hypot) } #[cfg(feature = "std")] fn atan2(self, other: Self) -> Self::Codomain { self.zip_map(other, BinaryRealFunction::atan2) } } impl BinaryRealFunction for Constrained where T: Primitive, C: Constraint, { #[cfg(feature = "std")] fn div_euclid(self, n: T) -> Self::Codomain { self.map(|inner| BinaryRealFunction::div_euclid(inner, n)) } #[cfg(feature = "std")] fn rem_euclid(self, n: T) -> Self::Codomain { self.map(|inner| BinaryRealFunction::rem_euclid(inner, n)) } #[cfg(feature = "std")] fn pow(self, n: T) -> Self::Codomain { self.map(|inner| BinaryRealFunction::pow(inner, n)) } #[cfg(feature = "std")] fn log(self, base: T) -> Self::Codomain { self.map(|inner| BinaryRealFunction::log(inner, base)) } #[cfg(feature = "std")] fn hypot(self, other: T) -> Self::Codomain { self.map(|inner| BinaryRealFunction::hypot(inner, other)) } #[cfg(feature = "std")] fn atan2(self, other: T) -> Self::Codomain { self.map(|inner| BinaryRealFunction::atan2(inner, other)) } } impl Bounded for Constrained where T: Primitive, { fn min_value() -> Self { BaseEncoding::MIN_FINITE } fn max_value() -> Self { BaseEncoding::MAX_FINITE } } impl Clone for Constrained where T: Clone, { fn clone(&self) -> Self { Constrained { inner: self.inner.clone(), phantom: PhantomData, } } } impl ConstrainedProxy for Constrained where T: Primitive, C: Constraint, { type Constraint = C; } impl Function for Constrained where T: Primitive, C: Constraint, { type Codomain = OutputFor; } impl Copy for Constrained where T: Copy {} impl Debug for Constrained> where T: Debug, D: Divergence, { fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter .debug_tuple("ExtendedReal") .field(self.as_ref()) .finish() } } impl Debug for Constrained where T: Debug, { fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.debug_tuple("Total").field(self.as_ref()).finish() } } impl Debug for Constrained> where T: Debug, D: Divergence, { fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.debug_tuple("Real").field(self.as_ref()).finish() } } impl Default for Constrained where T: Primitive, C: Constraint, { fn default() -> Self { // There is no constraint that disallows real numbers such as zero. Self::unchecked(T::ZERO) } } impl Display for Constrained where T: Display, { fn fmt(&self, f: &mut Formatter) -> fmt::Result { self.as_ref().fmt(f) } } impl Div for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn div(self, other: Self) -> Self::Output { self.zip_map(other, Div::div) } } impl Div for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn div(self, other: T) -> Self::Output { self.map(|inner| inner / other) } } impl DivAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn div_assign(&mut self, other: Self) { *self = *self / other } } impl DivAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn div_assign(&mut self, other: T) { *self = self.map(|inner| inner / other); } } impl BaseEncoding for Constrained where T: Primitive, { const MAX_FINITE: Self = Constrained::unchecked(T::MAX_FINITE); const MIN_FINITE: Self = Constrained::unchecked(T::MIN_FINITE); const MIN_POSITIVE_NORMAL: Self = Constrained::unchecked(T::MIN_POSITIVE_NORMAL); const EPSILON: Self = Constrained::unchecked(T::EPSILON); fn classify(self) -> FpCategory { T::classify(self.into_inner()) } fn is_normal(self) -> bool { T::is_normal(self.into_inner()) } fn is_sign_positive(self) -> bool { self.into_inner().is_sign_positive() } fn is_sign_negative(self) -> bool { self.into_inner().is_sign_negative() } #[cfg(feature = "std")] fn signum(self) -> Self { self.map_unchecked(|inner| inner.signum()) } fn integer_decode(self) -> (u64, i16, i8) { T::integer_decode(self.into_inner()) } } impl Eq for Constrained where T: Primitive {} impl Float for Constrained where T: Float + Primitive, C: Constraint + Member + Member, divergence::ContinueFor: NonResidual, { fn infinity() -> Self { InfinityEncoding::INFINITY } fn neg_infinity() -> Self { InfinityEncoding::NEG_INFINITY } fn is_infinite(self) -> bool { self.with_inner(Float::is_infinite) } fn is_finite(self) -> bool { self.with_inner(Float::is_finite) } fn nan() -> Self { ::NAN } fn is_nan(self) -> bool { self.with_inner(Float::is_nan) } fn max_value() -> Self { BaseEncoding::MAX_FINITE } fn min_value() -> Self { BaseEncoding::MIN_FINITE } fn min_positive_value() -> Self { BaseEncoding::MIN_POSITIVE_NORMAL } fn epsilon() -> Self { BaseEncoding::EPSILON } fn min(self, other: Self) -> Self { self.zip_map(other, Float::min) } fn max(self, other: Self) -> Self { self.zip_map(other, Float::max) } fn neg_zero() -> Self { -Self::ZERO } fn is_sign_positive(self) -> bool { self.with_inner(Float::is_sign_positive) } fn is_sign_negative(self) -> bool { self.with_inner(Float::is_sign_negative) } fn signum(self) -> Self { self.map(Float::signum) } fn abs(self) -> Self { self.map(Float::abs) } fn classify(self) -> FpCategory { self.with_inner(Float::classify) } fn is_normal(self) -> bool { self.with_inner(Float::is_normal) } fn integer_decode(self) -> (u64, i16, i8) { self.with_inner(Float::integer_decode) } fn floor(self) -> Self { self.map(Float::floor) } fn ceil(self) -> Self { self.map(Float::ceil) } fn round(self) -> Self { self.map(Float::round) } fn trunc(self) -> Self { self.map(Float::trunc) } fn fract(self) -> Self { self.map(Float::fract) } fn recip(self) -> Self { self.map(Float::recip) } #[cfg(feature = "std")] fn mul_add(self, a: Self, b: Self) -> Self { let a = a.into_inner(); let b = b.into_inner(); // TODO: This implementation requires a `Float` bound and forwards to its `mul_add`. // Consider supporting `mul_add` via a trait that is more specific to floating-point // encoding than `BinaryRealFunction` and friends. self.map(|inner| Float::mul_add(inner, a, b)) } #[cfg(feature = "std")] fn abs_sub(self, other: Self) -> Self { self.zip_map(other, Float::abs_sub) } #[cfg(feature = "std")] fn powi(self, n: i32) -> Self { self.map(|inner| Float::powi(inner, n)) } #[cfg(feature = "std")] fn powf(self, n: Self) -> Self { self.zip_map(n, Float::powf) } #[cfg(feature = "std")] fn sqrt(self) -> Self { self.map(Float::sqrt) } #[cfg(feature = "std")] fn cbrt(self) -> Self { self.map(Float::cbrt) } #[cfg(feature = "std")] fn exp(self) -> Self { self.map(Float::exp) } #[cfg(feature = "std")] fn exp2(self) -> Self { self.map(Float::exp2) } #[cfg(feature = "std")] fn exp_m1(self) -> Self { self.map(Float::exp_m1) } #[cfg(feature = "std")] fn log(self, base: Self) -> Self { self.zip_map(base, Float::log) } #[cfg(feature = "std")] fn ln(self) -> Self { self.map(Float::ln) } #[cfg(feature = "std")] fn log2(self) -> Self { self.map(Float::log2) } #[cfg(feature = "std")] fn log10(self) -> Self { self.map(Float::log10) } #[cfg(feature = "std")] fn ln_1p(self) -> Self { self.map(Float::ln_1p) } #[cfg(feature = "std")] fn hypot(self, other: Self) -> Self { BinaryRealFunction::hypot(self, other) } #[cfg(feature = "std")] fn sin(self) -> Self { self.map(Float::sin) } #[cfg(feature = "std")] fn cos(self) -> Self { self.map(Float::cos) } #[cfg(feature = "std")] fn tan(self) -> Self { self.map(Float::tan) } #[cfg(feature = "std")] fn asin(self) -> Self { self.map(Float::asin) } #[cfg(feature = "std")] fn acos(self) -> Self { self.map(Float::acos) } #[cfg(feature = "std")] fn atan(self) -> Self { self.map(Float::atan) } #[cfg(feature = "std")] fn atan2(self, other: Self) -> Self { BinaryRealFunction::atan2(self, other) } #[cfg(feature = "std")] fn sin_cos(self) -> (Self, Self) { let (sin, cos) = Float::sin_cos(self.into_inner()); (Constrained::<_, C>::new(sin), Constrained::<_, C>::new(cos)) } #[cfg(feature = "std")] fn sinh(self) -> Self { self.map(Float::sinh) } #[cfg(feature = "std")] fn cosh(self) -> Self { self.map(Float::cosh) } #[cfg(feature = "std")] fn tanh(self) -> Self { self.map(Float::tanh) } #[cfg(feature = "std")] fn asinh(self) -> Self { self.map(Float::asinh) } #[cfg(feature = "std")] fn acosh(self) -> Self { self.map(Float::acosh) } #[cfg(feature = "std")] fn atanh(self) -> Self { self.map(Float::atanh) } #[cfg(not(feature = "std"))] fn to_degrees(self) -> Self { self.map(Float::to_degrees) } #[cfg(not(feature = "std"))] fn to_radians(self) -> Self { self.map(Float::to_radians) } } impl FloatConst for Constrained where T: Primitive, C: Constraint, { fn E() -> Self { ::E } fn PI() -> Self { ::PI } fn SQRT_2() -> Self { ::SQRT_2 } fn FRAC_1_PI() -> Self { ::FRAC_1_PI } fn FRAC_2_PI() -> Self { ::FRAC_2_PI } fn FRAC_1_SQRT_2() -> Self { ::FRAC_1_SQRT_2 } fn FRAC_2_SQRT_PI() -> Self { ::FRAC_2_SQRT_PI } fn FRAC_PI_2() -> Self { ::FRAC_PI_2 } fn FRAC_PI_3() -> Self { ::FRAC_PI_3 } fn FRAC_PI_4() -> Self { ::FRAC_PI_4 } fn FRAC_PI_6() -> Self { ::FRAC_PI_6 } fn FRAC_PI_8() -> Self { ::FRAC_PI_8 } fn LN_10() -> Self { ::LN_10 } fn LN_2() -> Self { ::LN_2 } fn LOG10_E() -> Self { ::LOG10_E } fn LOG2_E() -> Self { ::LOG2_E } } impl From> for ExtendedReal where T: Primitive, { fn from(other: Real) -> Self { Self::from_subset(other) } } impl<'a, T> From<&'a T> for &'a Total where T: Primitive, { fn from(inner: &'a T) -> Self { // SAFETY: `Constrained` is `repr(transparent)` and has the same binary representation // as its input type `T`. This means that it is safe to transmute `T` to // `Constrained`. unsafe { &*(inner as *const T as *const Total) } } } impl<'a, T> From<&'a mut T> for &'a mut Total where T: Primitive, { fn from(inner: &'a mut T) -> Self { // SAFETY: `Constrained` is `repr(transparent)` and has the same binary representation // as its input type `T`. This means that it is safe to transmute `T` to // `Constrained`. unsafe { &mut *(inner as *mut T as *mut Total) } } } impl From> for Total where T: Primitive, { fn from(other: Real) -> Self { Self::from_subset(other) } } impl From> for Total where T: Primitive, { fn from(other: ExtendedReal) -> Self { Self::from_subset(other) } } impl From> for f32 { fn from(proxy: Constrained) -> Self { proxy.into_inner() } } impl From> for f64 { fn from(proxy: Constrained) -> Self { proxy.into_inner() } } #[cfg(feature = "serde")] impl From> for Serde where T: Copy, { fn from(proxy: Constrained) -> Self { Serde { inner: proxy.into_inner(), } } } impl From for Total where T: Primitive, { fn from(inner: T) -> Self { Self::unchecked(inner) } } impl FromPrimitive for Constrained where T: FromPrimitive + Primitive, C: Constraint, { fn from_i8(value: i8) -> Option { T::from_i8(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_u8(value: u8) -> Option { T::from_u8(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_i16(value: i16) -> Option { T::from_i16(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_u16(value: u16) -> Option { T::from_u16(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_i32(value: i32) -> Option { T::from_i32(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_u32(value: u32) -> Option { T::from_u32(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_i64(value: i64) -> Option { T::from_i64(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_u64(value: u64) -> Option { T::from_u64(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_isize(value: isize) -> Option { T::from_isize(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_usize(value: usize) -> Option { T::from_usize(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_f32(value: f32) -> Option { T::from_f32(value).and_then(|inner| Constrained::try_new(inner).ok()) } fn from_f64(value: f64) -> Option { T::from_f64(value).and_then(|inner| Constrained::try_new(inner).ok()) } } impl FromStr for Constrained where T: FromStr + Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { type Err = ::Err; fn from_str(string: &str) -> Result { T::from_str(string).map(Self::new) } } impl Hash for Constrained where T: Primitive + ToCanonical, { fn hash(&self, state: &mut H) where H: Hasher, { self.hash_canonical(state) } } impl InfinityEncoding for Constrained where T: Primitive, C: Constraint + Member, { const INFINITY: Self = Constrained::unchecked(T::INFINITY); const NEG_INFINITY: Self = Constrained::unchecked(T::NEG_INFINITY); fn is_infinite(self) -> bool { self.into_inner().is_infinite() } fn is_finite(self) -> bool { self.into_inner().is_finite() } } impl EmptyOrd for Constrained where T: Primitive, C: Constraint, { type Empty = C::Empty; #[inline(always)] fn from_empty(empty: Self::Empty) -> Self { C::from_empty(empty) } #[inline(always)] fn is_empty(&self) -> bool { C::is_empty(self.as_ref()) } fn cmp_empty(&self, other: &Self) -> Result { match self.as_ref().cmp_empty(other.as_ref()) { Ok(ordering) => Ok(ordering), Err(_) => Err(C::empty()), } } } impl EmptyInhabitant for Total where T: Primitive, { fn empty() -> Self { Total::NAN } } impl LowerExp for Constrained where T: LowerExp + Primitive, { fn fmt(&self, f: &mut Formatter) -> fmt::Result { self.as_ref().fmt(f) } } impl Mul for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn mul(self, other: Self) -> Self::Output { self.zip_map(other, Mul::mul) } } impl Mul for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn mul(self, other: T) -> Self::Output { self.map(|a| a * other) } } impl MulAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn mul_assign(&mut self, other: Self) { *self = *self * other; } } impl MulAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn mul_assign(&mut self, other: T) { *self = *self * other; } } impl NanEncoding for Constrained where T: Primitive, C: Constraint + Member, { type Nan = Self; const NAN: Self::Nan = Constrained::unchecked(T::NAN.into_inner()); fn is_nan(self) -> bool { self.into_inner().is_nan() } } impl Neg for Constrained where T: Primitive, { type Output = Self; fn neg(self) -> Self::Output { // There is no constraint for which negating a value produces an invalid value. Constrained::unchecked(-self.into_inner()) } } impl Num for Constrained where T: Num + Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { // TODO: Differentiate between parse and contraint errors. type FromStrRadixErr = (); fn from_str_radix(source: &str, radix: u32) -> Result { T::from_str_radix(source, radix) .map_err(|_| ()) .and_then(|inner| Constrained::try_new(inner).map_err(|_| ())) } } impl NumCast for Constrained where T: NumCast + Primitive + ToPrimitive, C: Constraint, { fn from(value: U) -> Option where U: ToPrimitive, { T::from(value).and_then(|inner| Constrained::try_new(inner).ok()) } } impl One for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn one() -> Self { UnaryRealFunction::ONE } } impl Ord for Constrained where T: Primitive, { fn cmp(&self, other: &Self) -> Ordering { CanonicalOrd::cmp_canonical(self.as_ref(), other.as_ref()) } } impl PartialEq for Constrained where T: Primitive, { fn eq(&self, other: &Self) -> bool { self.eq_canonical(other) } } impl PartialEq for Constrained where T: Primitive, C: Constraint, { fn eq(&self, other: &T) -> bool { if let Ok(other) = Self::try_new(*other) { Self::eq(self, &other) } else { false } } } impl PartialOrd for Constrained where T: Primitive, { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl PartialOrd for Constrained where T: Primitive, C: Constraint, { fn partial_cmp(&self, other: &T) -> Option { Self::try_new(*other) .ok() .and_then(|other| Self::partial_cmp(self, &other)) } } impl Product for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn product(input: I) -> Self where I: Iterator, { input.fold(UnaryRealFunction::ONE, |a, b| a * b) } } impl Proxy for Constrained where T: Primitive, { type Primitive = T; } #[cfg(feature = "approx")] impl RelativeEq for Constrained where T: Primitive + RelativeEq, C: Constraint, { fn default_max_relative() -> Self::Epsilon { Self::assert(T::default_max_relative()) } fn relative_eq( &self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon, ) -> bool { self.into_inner().relative_eq( &other.into_inner(), epsilon.into_inner(), max_relative.into_inner(), ) } } impl Rem for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn rem(self, other: Self) -> Self::Output { self.zip_map(other, Rem::rem) } } impl Rem for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn rem(self, other: T) -> Self::Output { self.map(|inner| inner % other) } } impl RemAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn rem_assign(&mut self, other: Self) { *self = *self % other; } } impl RemAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn rem_assign(&mut self, other: T) { *self = self.map(|inner| inner % other); } } impl Signed for Constrained where T: Primitive + Signed, C: Constraint, divergence::ContinueFor: NonResidual, { fn abs(&self) -> Self { self.map_unchecked(|inner| Signed::abs(&inner)) } fn abs_sub(&self, other: &Self) -> Self { self.zip_map(*other, |a, b| Signed::abs_sub(&a, &b)) } fn signum(&self) -> Self { self.map_unchecked(|inner| Signed::signum(&inner)) } fn is_positive(&self) -> bool { Signed::is_positive(self.as_ref()) } fn is_negative(&self) -> bool { Signed::is_negative(self.as_ref()) } } impl Sub for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn sub(self, other: Self) -> Self::Output { self.zip_map(other, Sub::sub) } } impl Sub for Constrained where T: Primitive, C: Constraint, { type Output = OutputFor; fn sub(self, other: T) -> Self::Output { self.map(|inner| inner - other) } } impl SubAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn sub_assign(&mut self, other: Self) { *self = *self - other } } impl SubAssign for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn sub_assign(&mut self, other: T) { *self = self.map(|inner| inner - other) } } impl Sum for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn sum(input: I) -> Self where I: Iterator, { input.fold(UnaryRealFunction::ZERO, |a, b| a + b) } } impl ToCanonical for Constrained where T: Primitive, { type Canonical = ::Canonical; fn to_canonical(self) -> Self::Canonical { self.inner.to_canonical() } } impl ToPrimitive for Constrained where T: Primitive + ToPrimitive, { fn to_i8(&self) -> Option { self.as_ref().to_i8() } fn to_u8(&self) -> Option { self.as_ref().to_u8() } fn to_i16(&self) -> Option { self.as_ref().to_i16() } fn to_u16(&self) -> Option { self.as_ref().to_u16() } fn to_i32(&self) -> Option { self.as_ref().to_i32() } fn to_u32(&self) -> Option { self.as_ref().to_u32() } fn to_i64(&self) -> Option { self.as_ref().to_i64() } fn to_u64(&self) -> Option { self.as_ref().to_u64() } fn to_isize(&self) -> Option { self.as_ref().to_isize() } fn to_usize(&self) -> Option { self.as_ref().to_usize() } fn to_f32(&self) -> Option { self.as_ref().to_f32() } fn to_f64(&self) -> Option { self.as_ref().to_f64() } } #[cfg(feature = "serde")] impl TryFrom> for Constrained where T: Primitive, C: Constraint, { type Error = C::Error; fn try_from(container: Serde) -> Result { Self::try_new(container.inner) } } #[cfg(feature = "approx")] impl UlpsEq for Constrained where T: Primitive + UlpsEq, C: Constraint, { fn default_max_ulps() -> u32 { T::default_max_ulps() } fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { self.into_inner() .ulps_eq(&other.into_inner(), epsilon.into_inner(), max_ulps) } } impl UnaryRealFunction for Constrained where T: Primitive, C: Constraint, { const ZERO: Self = Constrained::unchecked(UnaryRealFunction::ZERO); const ONE: Self = Constrained::unchecked(UnaryRealFunction::ONE); const E: Self = Constrained::unchecked(UnaryRealFunction::E); const PI: Self = Constrained::unchecked(UnaryRealFunction::PI); const FRAC_1_PI: Self = Constrained::unchecked(UnaryRealFunction::FRAC_1_PI); const FRAC_2_PI: Self = Constrained::unchecked(UnaryRealFunction::FRAC_2_PI); const FRAC_2_SQRT_PI: Self = Constrained::unchecked(UnaryRealFunction::FRAC_2_SQRT_PI); const FRAC_PI_2: Self = Constrained::unchecked(UnaryRealFunction::FRAC_PI_2); const FRAC_PI_3: Self = Constrained::unchecked(UnaryRealFunction::FRAC_PI_3); const FRAC_PI_4: Self = Constrained::unchecked(UnaryRealFunction::FRAC_PI_4); const FRAC_PI_6: Self = Constrained::unchecked(UnaryRealFunction::FRAC_PI_6); const FRAC_PI_8: Self = Constrained::unchecked(UnaryRealFunction::FRAC_PI_8); const SQRT_2: Self = Constrained::unchecked(UnaryRealFunction::SQRT_2); const FRAC_1_SQRT_2: Self = Constrained::unchecked(UnaryRealFunction::FRAC_1_SQRT_2); const LN_2: Self = Constrained::unchecked(UnaryRealFunction::LN_2); const LN_10: Self = Constrained::unchecked(UnaryRealFunction::LN_10); const LOG2_E: Self = Constrained::unchecked(UnaryRealFunction::LOG2_E); const LOG10_E: Self = Constrained::unchecked(UnaryRealFunction::LOG10_E); fn is_zero(self) -> bool { self.into_inner().is_zero() } fn is_one(self) -> bool { self.into_inner().is_zero() } fn sign(self) -> Sign { self.with_inner(|inner| inner.sign()) } #[cfg(feature = "std")] fn abs(self) -> Self { self.map_unchecked(UnaryRealFunction::abs) } #[cfg(feature = "std")] fn floor(self) -> Self { self.map_unchecked(UnaryRealFunction::floor) } #[cfg(feature = "std")] fn ceil(self) -> Self { self.map_unchecked(UnaryRealFunction::ceil) } #[cfg(feature = "std")] fn round(self) -> Self { self.map_unchecked(UnaryRealFunction::round) } #[cfg(feature = "std")] fn trunc(self) -> Self { self.map_unchecked(UnaryRealFunction::trunc) } #[cfg(feature = "std")] fn fract(self) -> Self { self.map_unchecked(UnaryRealFunction::fract) } fn recip(self) -> Self::Codomain { self.map(UnaryRealFunction::recip) } #[cfg(feature = "std")] fn powi(self, n: i32) -> Self::Codomain { self.map(|inner| UnaryRealFunction::powi(inner, n)) } #[cfg(feature = "std")] fn sqrt(self) -> Self::Codomain { self.map(UnaryRealFunction::sqrt) } #[cfg(feature = "std")] fn cbrt(self) -> Self { self.map_unchecked(UnaryRealFunction::cbrt) } #[cfg(feature = "std")] fn exp(self) -> Self::Codomain { self.map(UnaryRealFunction::exp) } #[cfg(feature = "std")] fn exp2(self) -> Self::Codomain { self.map(UnaryRealFunction::exp2) } #[cfg(feature = "std")] fn exp_m1(self) -> Self::Codomain { self.map(UnaryRealFunction::exp_m1) } #[cfg(feature = "std")] fn ln(self) -> Self::Codomain { self.map(UnaryRealFunction::ln) } #[cfg(feature = "std")] fn log2(self) -> Self::Codomain { self.map(UnaryRealFunction::log2) } #[cfg(feature = "std")] fn log10(self) -> Self::Codomain { self.map(UnaryRealFunction::log10) } #[cfg(feature = "std")] fn ln_1p(self) -> Self::Codomain { self.map(UnaryRealFunction::ln_1p) } #[cfg(feature = "std")] fn to_degrees(self) -> Self::Codomain { self.map(UnaryRealFunction::to_degrees) } #[cfg(feature = "std")] fn to_radians(self) -> Self { self.map_unchecked(UnaryRealFunction::to_radians) } #[cfg(feature = "std")] fn sin(self) -> Self { self.map_unchecked(UnaryRealFunction::sin) } #[cfg(feature = "std")] fn cos(self) -> Self { self.map_unchecked(UnaryRealFunction::cos) } #[cfg(feature = "std")] fn tan(self) -> Self::Codomain { self.map(UnaryRealFunction::tan) } #[cfg(feature = "std")] fn asin(self) -> Self::Codomain { self.map(UnaryRealFunction::asin) } #[cfg(feature = "std")] fn acos(self) -> Self::Codomain { self.map(UnaryRealFunction::acos) } #[cfg(feature = "std")] fn atan(self) -> Self { self.map_unchecked(UnaryRealFunction::atan) } #[cfg(feature = "std")] fn sin_cos(self) -> (Self, Self) { let (sin, cos) = self.into_inner().sin_cos(); (Constrained::unchecked(sin), Constrained::unchecked(cos)) } #[cfg(feature = "std")] fn sinh(self) -> Self { self.map_unchecked(UnaryRealFunction::sinh) } #[cfg(feature = "std")] fn cosh(self) -> Self { self.map_unchecked(UnaryRealFunction::cosh) } #[cfg(feature = "std")] fn tanh(self) -> Self { self.map_unchecked(UnaryRealFunction::tanh) } #[cfg(feature = "std")] fn asinh(self) -> Self::Codomain { self.map(UnaryRealFunction::asinh) } #[cfg(feature = "std")] fn acosh(self) -> Self::Codomain { self.map(UnaryRealFunction::acosh) } #[cfg(feature = "std")] fn atanh(self) -> Self::Codomain { self.map(UnaryRealFunction::atanh) } } impl UpperExp for Constrained where T: UpperExp, { fn fmt(&self, f: &mut Formatter) -> fmt::Result { self.as_ref().fmt(f) } } impl Zero for Constrained where T: Primitive, C: Constraint, divergence::ContinueFor: NonResidual, { fn zero() -> Self { UnaryRealFunction::ZERO } fn is_zero(&self) -> bool { self.as_ref().is_zero() } } macro_rules! impl_binary_operation_for_proxy { () => { with_binary_operations!(impl_binary_operation_for_proxy); }; (operation => $trait:ident :: $method:ident) => { impl_binary_operation_for_proxy!(operation => $trait :: $method, |left, right| { right.map(|inner| $trait::$method(left, inner)) }); }; (operation => $trait:ident :: $method:ident, |$left:ident, $right:ident| $f:block) => { macro_rules! impl_primitive_binary_operation_for_proxy { (primitive => $t:ty) => { impl $trait> for $t where C: Constraint, { type Output = OutputFor>; fn $method(self, other: Constrained<$t, C>) -> Self::Output { let $left = self; let $right = other; $f } } }; } with_primitives!(impl_primitive_binary_operation_for_proxy); }; } impl_binary_operation_for_proxy!(); /// Implements the `Real` trait from [`num-traits`](https://crates.io/crates/num-traits) for /// non-`NaN` proxy types. Does nothing if the `std` feature is disabled. /// /// A blanket implementation is not possible, because it conflicts with a very general blanket /// implementation provided by [`num-traits`]. See the following issues: /// /// - https://github.com/olson-sean-k/decorum/issues/10 /// - https://github.com/rust-num/num-traits/issues/49 macro_rules! impl_num_traits_real_for_proxy { () => { with_primitives!(impl_num_traits_real_for_proxy); }; (primitive => $t:ty) => { impl_num_traits_real_for_proxy!(proxy => Real, primitive => $t); impl_num_traits_real_for_proxy!(proxy => ExtendedReal, primitive => $t); }; (proxy => $p:ident, primitive => $t:ty) => { #[cfg(feature = "std")] impl num_traits::real::Real for $p<$t, D> where D: Divergence, divergence::ContinueFor: NonResidual>, { fn max_value() -> Self { BaseEncoding::MAX_FINITE } fn min_value() -> Self { BaseEncoding::MIN_FINITE } fn min_positive_value() -> Self { BaseEncoding::MIN_POSITIVE_NORMAL } fn epsilon() -> Self { BaseEncoding::EPSILON } fn min(self, other: Self) -> Self { self.zip_map(other, num_traits::real::Real::min) } fn max(self, other: Self) -> Self { self.zip_map(other, num_traits::real::Real::max) } fn is_sign_positive(self) -> bool { self.with_inner(num_traits::real::Real::is_sign_positive) } fn is_sign_negative(self) -> bool { self.with_inner(num_traits::real::Real::is_sign_negative) } fn signum(self) -> Self { self.map(num_traits::real::Real::signum) } fn abs(self) -> Self { self.map(num_traits::real::Real::abs) } fn floor(self) -> Self { self.map(num_traits::real::Real::floor) } fn ceil(self) -> Self { self.map(num_traits::real::Real::ceil) } fn round(self) -> Self { self.map(num_traits::real::Real::round) } fn trunc(self) -> Self { self.map(num_traits::real::Real::trunc) } fn fract(self) -> Self { self.map(num_traits::real::Real::fract) } fn recip(self) -> Self { self.map(num_traits::real::Real::recip) } fn mul_add(self, a: Self, b: Self) -> Self { let a = a.into_inner(); let b = b.into_inner(); self.map(|inner| inner.mul_add(a, b)) } fn abs_sub(self, other: Self) -> Self { self.zip_map(other, num_traits::real::Real::abs_sub) } fn powi(self, n: i32) -> Self { self.map(|inner| num_traits::real::Real::powi(inner, n)) } fn powf(self, n: Self) -> Self { self.zip_map(n, num_traits::real::Real::powf) } fn sqrt(self) -> Self { self.map(num_traits::real::Real::sqrt) } fn cbrt(self) -> Self { self.map(num_traits::real::Real::cbrt) } fn exp(self) -> Self { self.map(num_traits::real::Real::exp) } fn exp2(self) -> Self { self.map(num_traits::real::Real::exp2) } fn exp_m1(self) -> Self { self.map(num_traits::real::Real::exp_m1) } fn log(self, base: Self) -> Self { self.zip_map(base, num_traits::real::Real::log) } fn ln(self) -> Self { self.map(num_traits::real::Real::ln) } fn log2(self) -> Self { self.map(num_traits::real::Real::log2) } fn log10(self) -> Self { self.map(num_traits::real::Real::log10) } fn to_degrees(self) -> Self { self.map(num_traits::real::Real::to_degrees) } fn to_radians(self) -> Self { self.map(num_traits::real::Real::to_radians) } fn ln_1p(self) -> Self { self.map(num_traits::real::Real::ln_1p) } fn hypot(self, other: Self) -> Self { self.zip_map(other, num_traits::real::Real::hypot) } fn sin(self) -> Self { self.map(num_traits::real::Real::sin) } fn cos(self) -> Self { self.map(num_traits::real::Real::cos) } fn tan(self) -> Self { self.map(num_traits::real::Real::tan) } fn asin(self) -> Self { self.map(num_traits::real::Real::asin) } fn acos(self) -> Self { self.map(num_traits::real::Real::acos) } fn atan(self) -> Self { self.map(num_traits::real::Real::atan) } fn atan2(self, other: Self) -> Self { self.zip_map(other, num_traits::real::Real::atan2) } fn sin_cos(self) -> (Self, Self) { let (sin, cos) = self.with_inner(num_traits::real::Real::sin_cos); ($p::<_, D>::new(sin), $p::<_, D>::new(cos)) } fn sinh(self) -> Self { self.map(num_traits::real::Real::sinh) } fn cosh(self) -> Self { self.map(num_traits::real::Real::cosh) } fn tanh(self) -> Self { self.map(num_traits::real::Real::tanh) } fn asinh(self) -> Self { self.map(num_traits::real::Real::asinh) } fn acosh(self) -> Self { self.map(num_traits::real::Real::acosh) } fn atanh(self) -> Self { self.map(num_traits::real::Real::atanh) } } }; } impl_num_traits_real_for_proxy!(); // `TryFrom` cannot be implemented over an open type `T` and cannot be implemented for constraints // in general, because it would conflict with the `From` implementation for `Total`. macro_rules! impl_try_from_for_proxy { () => { with_primitives!(impl_try_from_for_proxy); }; (primitive => $t:ty) => { impl_try_from_for_proxy!(proxy => Real, primitive => $t); impl_try_from_for_proxy!(proxy => ExtendedReal, primitive => $t); }; (proxy => $p:ident, primitive => $t:ty) => { impl TryFrom<$t> for $p<$t, D> where D: Divergence, { type Error = ErrorFor; fn try_from(inner: $t) -> Result { Self::try_new(inner) } } impl<'a, D> TryFrom<&'a $t> for &'a $p<$t, D> where D: Divergence, { type Error = ErrorFor<$p<$t, D>>; fn try_from(inner: &'a $t) -> Result { ConstraintFor::<$p<$t, D>>::check(*inner).map(|_| { // SAFETY: `Constrained` is `repr(transparent)` and has the same binary // representation as its input type `T`. This means that it is safe to // transmute `T` to `Constrained`. unsafe { mem::transmute::<&'a $t, Self>(inner) } }) } } impl<'a, D> TryFrom<&'a mut $t> for &'a mut $p<$t, D> where D: Divergence, { type Error = ErrorFor<$p<$t, D>>; fn try_from(inner: &'a mut $t) -> Result { ConstraintFor::<$p<$t, D>>::check(*inner).map(move |_| { // SAFETY: `Constrained` is `repr(transparent)` and has the same binary // representation as its input type `T`. This means that it is safe to // transmute `T` to `Constrained`. unsafe { mem::transmute::<&'a mut $t, Self>(inner) } }) } } }; } impl_try_from_for_proxy!(); #[cfg(test)] mod tests { use crate::real::RealFunction; use crate::{ExtendedReal, InfinityEncoding, NanEncoding, Real, Total, E32, R32}; #[test] fn total_no_panic_on_inf() { let x: Total = 1.0.into(); let y = x / 0.0; assert!(InfinityEncoding::is_infinite(y)); } #[test] fn total_no_panic_on_nan() { let x: Total = 0.0.into(); let y = x / 0.0; assert!(NanEncoding::is_nan(y)); } // This is the most comprehensive and general test of reference conversions, as there are no // failure conditions. Other similar tests focus solely on success or failure, not completeness // of the APIs under test. This test is an ideal Miri target. #[test] #[allow(clippy::eq_op)] #[allow(clippy::float_cmp)] #[allow(clippy::zero_divided_by_zero)] fn total_no_panic_from_ref_slice() { let x = 0.0f64 / 0.0; let y: &Total<_> = (&x).into(); assert!(y.is_nan()); let mut x = 0.0f64; let y: &mut Total<_> = (&mut x).into(); *y = (0.0f64 / 0.0).into(); assert!(y.is_nan()); let xs = [0.0f64, 1.0]; let ys = Total::from_slice(&xs); assert_eq!(ys, &[0.0f64, 1.0]); let xs = [0.0f64, 1.0]; let ys = Total::from_slice(&xs); assert_eq!(ys, &[0.0f64, 1.0]); } #[test] fn notnan_no_panic_on_inf() { let x: E32 = 1.0.try_into().unwrap(); let y = x / 0.0; assert!(InfinityEncoding::is_infinite(y)); } #[test] #[should_panic] fn notnan_panic_on_nan() { let x: E32 = 0.0.try_into().unwrap(); let _ = x / 0.0; } #[test] #[allow(clippy::eq_op)] #[allow(clippy::float_cmp)] fn notnan_no_panic_from_inf_ref_slice() { let x = 1.0f64 / 0.0; let y: &ExtendedReal<_> = (&x).try_into().unwrap(); assert!(y.is_infinite()); let xs = [0.0f64, 1.0 / 0.0]; let ys = ExtendedReal::::try_from_slice(&xs).unwrap(); assert_eq!(ys, &[0.0f64, InfinityEncoding::INFINITY]); } #[test] #[should_panic] #[allow(clippy::zero_divided_by_zero)] fn notnan_panic_from_nan_ref() { let x = 0.0f64 / 0.0; let _: &ExtendedReal<_> = (&x).try_into().unwrap(); } #[test] #[should_panic] #[allow(clippy::zero_divided_by_zero)] fn notnan_panic_from_nan_slice() { let xs = [1.0f64, 0.0f64 / 0.0]; let _ = ExtendedReal::::try_from_slice(&xs).unwrap(); } #[test] #[should_panic] fn finite_panic_on_nan() { let x: R32 = 0.0.try_into().unwrap(); let _ = x / 0.0; } #[test] #[should_panic] fn finite_panic_on_inf() { let x: R32 = 1.0.try_into().unwrap(); let _ = x / 0.0; } #[test] #[should_panic] fn finite_panic_on_neg_inf() { let x: R32 = (-1.0).try_into().unwrap(); let _ = x / 0.0; } #[test] #[should_panic] fn finite_panic_from_inf_ref() { let x = 1.0f64 / 0.0; let _: &Real<_> = (&x).try_into().unwrap(); } #[test] #[should_panic] fn finite_panic_from_inf_slice() { let xs = [1.0f64, 1.0f64 / 0.0]; let _ = Real::::try_from_slice(&xs).unwrap(); } #[test] #[allow(clippy::eq_op)] #[allow(clippy::float_cmp)] #[allow(clippy::zero_divided_by_zero)] fn total_nan_eq() { let x: Total = (0.0 / 0.0).into(); let y: Total = (0.0 / 0.0).into(); assert_eq!(x, y); let z: Total = (::INFINITY + ::NEG_INFINITY).into(); assert_eq!(x, z); #[cfg(feature = "std")] { use crate::real::UnaryRealFunction; let w: Total = (UnaryRealFunction::sqrt(-1.0f32)).into(); assert_eq!(x, w); } } #[test] #[allow(clippy::eq_op)] #[allow(clippy::float_cmp)] #[allow(clippy::zero_divided_by_zero)] #[allow(invalid_nan_comparisons)] fn cmp_proxy_primitive() { // Compare a canonicalized `NaN` with a primitive `NaN` with a different representation. let x: Total = (0.0 / 0.0).into(); assert_eq!(x, f32::sqrt(-1.0)); // Compare a canonicalized `INF` with a primitive `NaN`. let y: Total = (1.0 / 0.0).into(); assert!(y < (0.0 / 0.0)); // Compare a proxy that disallows `INF` to a primitive `INF`. let z: R32 = 0.0.try_into().unwrap(); assert_eq!(z.partial_cmp(&(1.0 / 0.0)), None); } #[test] fn sum() { let xs = [ 1.0.try_into().unwrap(), 2.0.try_into().unwrap(), 3.0.try_into().unwrap(), ]; assert_eq!(xs.iter().cloned().sum::(), R32::assert(6.0)); } #[test] fn product() { let xs = [ 1.0.try_into().unwrap(), 2.0.try_into().unwrap(), 3.0.try_into().unwrap(), ]; assert_eq!(xs.iter().cloned().product::(), R32::assert(6.0),); } // TODO: This test is questionable. #[test] fn impl_traits() { fn as_infinite(_: T) where T: InfinityEncoding, { } fn as_nan(_: T) where T: NanEncoding, { } fn as_real(_: T) where T: RealFunction, { } let finite = Real::::default(); as_real(finite); let notnan = ExtendedReal::::default(); as_infinite(notnan); as_real(notnan); let ordered = Total::::default(); as_infinite(ordered); as_nan(ordered); } #[test] fn fmt() { let x: Total = 1.0.into(); format_args!("{0} {0:e} {0:E} {0:?} {0:#?}", x); let y: ExtendedReal = 1.0.try_into().unwrap(); format_args!("{0} {0:e} {0:E} {0:?} {0:#?}", y); let z: Real = 1.0.try_into().unwrap(); format_args!("{0} {0:e} {0:E} {0:?} {0:#?}", z); } #[cfg(feature = "serde")] #[test] fn deserialize() { assert_eq!( R32::assert(1.0), serde_json::from_str::("1.0").unwrap() ); } #[cfg(feature = "serde")] #[test] #[should_panic] fn deserialize_panic_on_violation() { // TODO: See `Serde`. This does not test a value that violates `E32`'s constraints; // instead, this simply fails to deserialize `f32` from `"null"`. let _: E32 = serde_json::from_str("null").unwrap(); } #[cfg(feature = "serde")] #[test] fn serialize() { use crate::divergence::OrPanic; assert_eq!( "1.0", serde_json::to_string(&E32::::assert(1.0)).unwrap() ); // TODO: See `Serde`. assert_eq!( "null", serde_json::to_string(&E32::::INFINITY).unwrap() ); } } decorum-0.4.0/src/proxy/mod.rs000064400000000000000000000063751046102023000143730ustar 00000000000000//! IEEE 754 floating-point proxy types that apply ordering and configurable contraints and //! divergence. //! //! [`Constrained`] types wrap primitive floating-point types and change their behavior with //! respect to ordering, equivalence, hashing, supported values, and error behaviors. These types //! have the same representation as primitive floating-point types and can often be used as drop-in //! replacements. //! //! [Constraints][`constraint`] configure the behavior of [`Constrained`]s by determining the set //! of IEEE //! 754 floating-point values that can be represented and how to [diverge][`divergence`] if a value //! that is not in this set is encountered. The following table summarizes the proxy type //! definitions and their constraints: //! //! | Type Definition | Sized Definitions | Trait Implementations | Disallowed Values | //! |------------------|-------------------|-------------------------------------------------|-----------------------| //! | [`Total`] | | `BaseEncoding + InfinityEncoding + NanEncoding` | | //! | [`ExtendedReal`] | `E32`, `E64` | `BaseEncoding + InfinityEncoding` | `NaN` | //! | [`Real`] | `R32`, `R64` | `BaseEncoding` | `NaN`, `-INF`, `+INF` | //! //! The [`ExtendedReal`] and [`Real`] types disallow values that represent not-a-number, $\infin$, //! and $-\infin$. These types diverge if such a value is encountered, which may result in an error //! encoding output (e.g., a `Result::Err`) or even a panic. Notably, the [`Total`] type applies no //! constraints and is infallible (never diverges). //! //! [`constraint`]: crate::constraint //! [`divergence`]: crate::divergence //! [`ExtendedReal`]: crate::ExtendedReal //! [`Real`]: crate::Real //! [`Total`]: crate::Total mod constrained; mod nan; #[cfg(feature = "serde")] use serde_derive::{Deserialize, Serialize}; use crate::Primitive; pub use crate::proxy::constrained::{ Constrained, ConstrainedProxy, ErrorFor, ExpressionFor, OutputFor, }; pub use crate::proxy::nan::Nan; /// An IEEE 754 floating-point proxy type. pub trait Proxy: Sized { type Primitive: Primitive; } // TODO: By default, Serde serializes floating-point primitives representing `NaN` and infinities // as `"null"`. Moreover, Serde cannot deserialize `"null"` as a floating-point primitive. // This means that information is lost when serializing and deserializing is impossible for // non-real values. /// Serialization container. /// /// This type is represented and serialized transparently as its inner type `T`. `Constrained` uses /// this type for its own serialization and deserialization. Importantly, this uses a conversion /// when deserializing that upholds the constraints on proxy types, so it is not possible to /// deserialize a floating-point value into a proxy type that does not support that value. /// /// See the following for more context and details: /// /// - https://github.com/serde-rs/serde/issues/642 /// - https://github.com/serde-rs/serde/issues/939 #[cfg(feature = "serde")] #[derive(Deserialize, Serialize)] #[serde(transparent)] #[derive(Clone, Copy)] #[repr(transparent)] struct Serde { inner: T, } decorum-0.4.0/src/proxy/nan.rs000064400000000000000000000012651046102023000143610ustar 00000000000000use crate::proxy::Proxy; use crate::Primitive; /// An incomparable primitive IEEE 754 floating-point `NaN`. #[derive(Clone, Copy, Debug)] #[repr(transparent)] pub struct Nan where T: Primitive, { inner: T, } impl Nan where T: Primitive, { pub(crate) const fn unchecked(inner: T) -> Self { Nan { inner } } pub const fn into_inner(self) -> T { self.inner } } impl From> for f32 { fn from(nan: Nan) -> Self { nan.into_inner() } } impl From> for f64 { fn from(nan: Nan) -> Self { nan.into_inner() } } impl Proxy for Nan where T: Primitive, { type Primitive = T; } decorum-0.4.0/src/real.rs000064400000000000000000000122611046102023000133450ustar 00000000000000//! Constants and functions over real numbers. use core::ops::{Add, Div, Mul, Neg, Rem, Sub}; use crate::Primitive; pub trait Function { type Codomain; } pub trait Endofunction: Function {} impl Endofunction for T where T: Function {} // This trait is implemented by trivial `Copy` types. #[allow(clippy::wrong_self_convention)] pub trait UnaryRealFunction: Function + Neg + PartialEq + PartialOrd + Sized { const ZERO: Self; const ONE: Self; const E: Self; const PI: Self; const FRAC_1_PI: Self; const FRAC_2_PI: Self; const FRAC_2_SQRT_PI: Self; const FRAC_PI_2: Self; const FRAC_PI_3: Self; const FRAC_PI_4: Self; const FRAC_PI_6: Self; const FRAC_PI_8: Self; const SQRT_2: Self; const FRAC_1_SQRT_2: Self; const LN_2: Self; const LN_10: Self; const LOG2_E: Self; const LOG10_E: Self; fn is_zero(self) -> bool; fn is_one(self) -> bool; fn sign(self) -> Sign; #[cfg(feature = "std")] fn abs(self) -> Self; #[cfg(feature = "std")] fn floor(self) -> Self; #[cfg(feature = "std")] fn ceil(self) -> Self; #[cfg(feature = "std")] fn round(self) -> Self; #[cfg(feature = "std")] fn trunc(self) -> Self; #[cfg(feature = "std")] fn fract(self) -> Self; fn recip(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn powi(self, n: i32) -> Self::Codomain; // Floating-point exception or undefined. #[cfg(feature = "std")] fn sqrt(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn cbrt(self) -> Self; #[cfg(feature = "std")] fn exp(self) -> Self::Codomain; // Floating-point exception. #[cfg(feature = "std")] fn exp2(self) -> Self::Codomain; // Floating-point exception. #[cfg(feature = "std")] fn exp_m1(self) -> Self::Codomain; // Floating-point exception. #[cfg(feature = "std")] fn ln(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn log2(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn log10(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn ln_1p(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn to_degrees(self) -> Self::Codomain; // Floating-point exception. #[cfg(feature = "std")] fn to_radians(self) -> Self; #[cfg(feature = "std")] fn sin(self) -> Self; #[cfg(feature = "std")] fn cos(self) -> Self; #[cfg(feature = "std")] fn tan(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn asin(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn acos(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn atan(self) -> Self; #[cfg(feature = "std")] fn sin_cos(self) -> (Self, Self); #[cfg(feature = "std")] fn sinh(self) -> Self; #[cfg(feature = "std")] fn cosh(self) -> Self; #[cfg(feature = "std")] fn tanh(self) -> Self; #[cfg(feature = "std")] fn asinh(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn acosh(self) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn atanh(self) -> Self::Codomain; // Undefined. } // NOTE: Because `T` is not constrained, it isn't possible for functions that always map reals to // reals to express their output as `Self`. The `T` input may not be real and that may result // in a non-real output. pub trait BinaryRealFunction: Add + Div + Mul + Rem + Sub + UnaryRealFunction { #[cfg(feature = "std")] fn div_euclid(self, n: T) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn rem_euclid(self, n: T) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn pow(self, n: T) -> Self::Codomain; // Floating-point exception or undefined. #[cfg(feature = "std")] fn log(self, base: T) -> Self::Codomain; // Undefined. #[cfg(feature = "std")] fn hypot(self, other: T) -> Self::Codomain; // Floating-point exception. #[cfg(feature = "std")] fn atan2(self, other: T) -> Self::Codomain; } pub trait RealFunction: BinaryRealFunction {} impl RealFunction for T where T: BinaryRealFunction {} pub trait FloatFunction: BinaryRealFunction + Into + RealFunction + TryFrom where T: Primitive, { } impl FloatFunction for U where T: Primitive, U: BinaryRealFunction + Into + RealFunction + TryFrom, { } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Sign { Positive, Negative, Zero, } impl Sign { pub fn is_non_zero_positive(&self) -> bool { matches!(self, Sign::Positive) } pub fn is_zero_or_positive(&self) -> bool { matches!(self, Sign::Zero | Sign::Positive) } pub fn is_non_zero_negative(&self) -> bool { matches!(self, Sign::Negative) } pub fn is_zero_or_negative(&self) -> bool { matches!(self, Sign::Zero | Sign::Negative) } pub fn is_zero(&self) -> bool { matches!(self, Sign::Zero) } }