fast_posit/lib.rs
1#![cfg_attr(not(test), no_std)]
2
3//! This crate provides a correct, clean, flexible, and 🚀 *fast* software implementation of
4//! [Posit arithmetic](https://posithub.org/docs/posit_standard-2.pdf).
5//!
6//! # Introduction
7//!
8//! Posits are an alternative floating point format proposed by John Gustafson in 2017, with the
9//! first standard published in 2022. They have several interesting features that make them an
10//! excellent replacement for traditional IEEE754 floats, in domains such as neural networks or
11//! HPC.
12//!
13//! The following references are useful if you are not yet familiar with posits:
14//!
15//! - [Posit standard](https://posithub.org/docs/posit_standard-2.pdf) (2022)
16//! - [Original extended paper](https://posithub.org/docs/Posits4.pdf) (2017)
17//! - [Book](https://doi.org/10.1201/9781003466024 ) (2024)
18//!
19//! This crate aims to implement the entire posit standard and beyond, including features such as
20//! arbitrary posit and quire sizes, bounded posits, etc. Versions prior to 1.0.0, however,
21//! may be incomplete. Correctness is ensured via extensive testing against an oracle.
22//!
23//! # Usage
24//!
25//! The following is an extended tour over the main functionality of the crate, sort of in the
26//! style of "learn X in Y minutes". For more information, refer to the documentation of specific
27//! types and functions.
28//!
29//! Use standard posit types, or define your own.
30//! ```
31//! # use fast_posit::*;
32//! use fast_posit::{p8, p16, p32, p64}; // Standard: n bits, 2 exponent bits
33//! type MyPosit = Posit<24, 3, i32>; // Non-standard: 24 bits, 3 exponent bits
34//! type MyBoundedPosit = Posit<32, 3, i32, 6>; // b-posit: 32 bits, 3 exponent bits, 6 max regime
35//! ```
36//!
37//! Create posits from ints, IEEE floats, strings, constants, or a raw bit representation.
38//! ```
39//! # use fast_posit::*;
40//! use fast_posit::{RoundFrom, RoundInto};
41//! let a = p32::round_from(2.71_f64);
42//! let b = p32::round_from(42_i32);
43//! let c = p32::from_bits(0x7f001337);
44//! let d = p32::MIN_POSITIVE;
45//! ```
46//!
47//! Perform basic arithmetic and comparisons using the usual operators.
48//! ```
49//! # use fast_posit::*;
50//! assert!(p16::round_from(2.14) + p16::ONE == p16::round_from(3.14));
51//! assert!(p16::MIN_POSITIVE < 1e-15.round_into());
52//! assert!(p16::round_from(-1.1).floor() == p16::round_from(-2));
53//! ```
54//!
55//! Convert posits back to ints, IEEE floats, strings, or a raw bit representation.
56//! ```
57//! # use fast_posit::*;
58//! assert_eq!(p8::ONE.to_bits(), 0b01000000);
59//! assert_eq!(4_i32, p16::round_from(3.5).round_into());
60//! assert_eq!(-f32::exp2(56.), p16::MIN.round_into());
61//! ```
62//!
63//! Use a quire to calculate sums and dot products _without loss of precision_!
64//! ```
65//! # use fast_posit::*;
66//! use fast_posit::{q8, q16, q32, q64};
67//! let mut quire = q16::ZERO;
68//! quire += p16::MAX;
69//! quire += p16::round_from(0.1);
70//! quire -= p16::MAX;
71//! let result: p16 = quire.round_into();
72//! // Correct result with the quire, no issues with rounding errors.
73//! assert_eq!(result, p16::round_from(0.1));
74//! // The same sum without the quire would give a wrong result, due to double rounding.
75//! let posit = p16::MAX + p16::round_from(0.1) - p16::MAX;
76//! assert_eq!(posit, p16::ZERO);
77//! ```
78//!
79//! Dot products in the quire can give correct results with 32 bits even where IEEE floats fail
80//! with 64 bits.
81//! ```
82//! # use fast_posit::*;
83//! let a = [3.2e7, 1., -1., 8.0e7];
84//! let b = [4.0e8, 1., -1., -1.6e8];
85//! // Calculating the dot product with 64-bit IEEE floats yields an incorrect result.
86//! let float: f64 = a.iter().zip(b.iter())
87//! .map(|(x, y)| x * y)
88//! .sum();
89//! assert_eq!(float, 0.);
90//! // Calculating the dot product with 32-bit posits and a quire yields the correct result.
91//! let posit: p32 = a.iter().zip(b.iter())
92//! .map(|(x, y)| (p32::round_from(*x), p32::round_from(*y)))
93//! .fold(q32::ZERO, |mut q, (x, y)| { q.add_prod(x, y); q })
94//! .round_into();
95//! assert_eq!(posit, 2.round_into());
96//! ```
97//!
98//! Use a quire per thread to ensure the result is the same _regardless of parallelisation_!
99//! ```
100//! # use fast_posit::*;
101//! let mut quires = [q16::ZERO; 8];
102//! for thread in 0..8 {
103//! let local_quire = &mut quires[thread];
104//! *local_quire += p16::round_from(123);
105//! *local_quire += p16::round_from(456);
106//! // ...
107//! }
108//! // Assemble the final result by summing the thread-local quires first, then converting to posit.
109//! let [mut first, rest @ ..] = quires;
110//! for i in rest {
111//! first += &i
112//! }
113//! let result: p16 = first.round_into();
114//! assert_eq!(result, p16::round_from(8 * (123 + 456)));
115//! ```
116//!
117//! Use mixed-precision with no hassle; it's especially cheap when the ES is the same, such as
118//! among the standard types.
119//! ```
120//! # use fast_posit::*;
121//! let terms = [3, 7, 15, 1].map(p8::round_from); // https://oeis.org/A001203
122//! let pi = {
123//! let mut partial = p64::ZERO;
124//! for i in terms[1..].iter().rev() {
125//! partial = p64::ONE / (i.convert() + partial) // `i` upcasted p8→p64 essentially for free
126//! }
127//! terms[0].convert() + partial
128//! };
129//! assert!((3.141592.round_into() .. 3.141593.round_into()).contains(&pi));
130//! ```
131//!
132//! Please refer to the documentation for each specific item for more details. Wherever a function
133//! corresponds to a function in [the standard](https://posithub.org/docs/posit_standard-2.pdf),
134//! this will be noted.
135//!
136//! # Performance
137//!
138//! In terms of performance, you can expect as a *very rough estimate* 70 to 350 Mops/s
139//! (corresponding to about a 4–20× slowdown relative to native hw FPU operations) on an 11th gen
140//! Intel x86 core at 3.80GHz. This is, as far as we're aware, faster (or at least as fast) than
141//! any freely available software implementation of posit arithmetic.
142//!
143//! 
145//!
146//! Needless to say, both absolute performance and relative performance vs the FPU will vary
147//! depending on your system.
148//!
149//! This crate includes benchmarks; run them with `cargo bench -F bench`.
150
151mod posit;
152mod underlying;
153
154pub use posit::Posit;
155pub use posit::quire::Quire;
156pub use underlying::Int;
157pub use posit::convert::{RoundFrom, RoundInto};
158
159/// Standard-defined 8-bit posit (with 2-bit exponent and unbounded regime).
160///
161/// This type is defined in the [2022 standard](https://posithub.org/docs/posit_standard-2.pdf); it
162/// is a special case of the generic [`Posit`] type. If you want a posit with arbitrary size `N`,
163/// exponent size `ES`, and optionally maximum regime size `RS`, see [`Posit<N, ES, RS>`].
164#[allow(non_camel_case_types)]
165pub type p8 = Posit<8, 2, i8>;
166
167/// Standard-defined 16-bit posit (with 2-bit exponent and unbounded regime).
168///
169/// This type is defined in the [2022 standard](https://posithub.org/docs/posit_standard-2.pdf); it
170/// is a special case of the generic [`Posit`] type. If you want a posit with arbitrary size `N`,
171/// exponent size `ES`, and optionally maximum regime size `RS`, see [`Posit<N, ES, RS>`].
172#[allow(non_camel_case_types)]
173pub type p16 = Posit<16, 2, i16>;
174
175/// Standard-defined 32-bit posit (with 2-bit exponent and unbounded regime).
176///
177/// This type is defined in the [2022 standard](https://posithub.org/docs/posit_standard-2.pdf); it
178/// is a special case of the generic [`Posit`] type. If you want a posit with arbitrary size `N`,
179/// exponent size `ES`, and optionally maximum regime size `RS`, see [`Posit<N, ES, RS>`].
180#[allow(non_camel_case_types)]
181pub type p32 = Posit<32, 2, i32>;
182
183/// Standard-defined 64-bit posit (with 2-bit exponent and unbounded regime).
184///
185/// This type is defined in the [2022 standard](https://posithub.org/docs/posit_standard-2.pdf); it
186/// is a special case of the generic [`Posit`] type. If you want a posit with arbitrary size `N`,
187/// exponent size `ES`, and optionally maximum regime size `RS`, see [`Posit<N, ES, RS>`].
188#[allow(non_camel_case_types)]
189pub type p64 = Posit<64, 2, i64>;
190
191/// Standard-defined 128-bit quire for a [p8].
192#[allow(non_camel_case_types)]
193pub type q8 = Quire<8, 2, 16>;
194
195/// Standard-defined 256-bit quire for a [p16].
196#[allow(non_camel_case_types)]
197pub type q16 = Quire<16, 2, 32>;
198
199/// Standard-defined 512-bit quire for a [p32].
200#[allow(non_camel_case_types)]
201pub type q32 = Quire<32, 2, 64>;
202
203/// Standard-defined 1024-bit quire for a [p64].
204#[allow(non_camel_case_types)]
205pub type q64 = Quire<64, 2, 128>;
206
207/// Re-export some internals for benchmarking purposes, only on `feature = "bench"`.
208#[cfg(feature = "bench")]
209mod bench;
210
211/// Set the number of proptest cases globally.
212///
213/// This number strikes a balance between coverage and practicality.
214#[cfg(test)]
215const PROPTEST_CASES: u32 = if cfg!(debug_assertions) {0x1_0000} else {0x80_0000};
216
217mod utl;