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 published standard 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 beyond those prescribed by the standard. Versions prior to
21//! 1.0.0, however, may be incomplete. Correctness is ensured via extensive testing against an
22//! oracle.
23//!
24//! # Usage
25//!
26//! The following is an extended tour over the main functionality of the crate, sort of in the
27//! style of "learn X in Y minutes". For more information, refer to the documentation of specific
28//! types and functions.
29//!
30//! Wherever a function corresponds to a function in
31//! [the standard](https://posithub.org/docs/posit_standard-2.pdf), it will be marked accordingly
32//! in its documentation.
33//!
34//! ```
35//! // Use standard posit types, or define your own.
36//! # use fast_posit::Posit;
37//! use fast_posit::{p8, p16, p32, p64}; // Standard: n bits, 2 exponent bits
38//! type MyPosit = Posit<24, 3, i32>; // Non-standard: 24 bits, 3 exponent bits
39//!
40//! // Create posits from ints, IEEE floats, strings, constants, or a raw bit representation.
41//! use fast_posit::{RoundFrom, RoundInto};
42//! let a = p32::round_from(2.71_f64);
43//! let b = p32::round_from(42_i32);
44//! let c = p32::from_bits(0x7f001337);
45//! let d = p32::MIN_POSITIVE;
46//!
47//! // Perform basic arithmetic and comparisons using the usual operators.
48//! assert!(p16::round_from(2.14) + p16::ONE == p16::round_from(3.14));
49//! assert!(p16::MIN_POSITIVE < 1e-15.round_into());
50//! assert!(p16::round_from(-1.1).floor() == p16::round_from(-2));
51//!
52//! // Convert posits back to ints, IEEE floats, strings, or a raw bit representation.
53//! assert_eq!(p8::ONE.to_bits(), 0b01000000);
54//! assert_eq!(4_i32, p16::round_from(3.5).round_into());
55//! assert_eq!(-f32::exp2(56.), p16::MIN.round_into());
56//!
57//! // Use a quire to calculate sums and dot products _without loss of precision_!
58//! use fast_posit::{q8, q16, q32, q64};
59//! let mut quire = q16::ZERO;
60//! quire += p16::MAX;
61//! quire += p16::round_from(0.1);
62//! quire -= p16::MAX;
63//! let result: p16 = (&quire).round_into();
64//! // Correct result with the quire, no issues with rounding errors.
65//! assert_eq!(result, p16::round_from(0.1));
66//! // The same sum without the quire would give a wrong result, due to double rounding.
67//! let posit = p16::MAX + p16::round_from(0.1) - p16::MAX;
68//! assert_eq!(posit, p16::ZERO);
69//!
70//! // Use a quire per thread to ensure the result is the same _regardless of parallelisation_!
71//! let mut quires = [q16::ZERO; 8];
72//! for thread in 0..8 {
73//! let local_quire = &mut quires[thread];
74//! *local_quire += p16::round_from(123);
75//! *local_quire += p16::round_from(456);
76//! // ...
77//! }
78//! // Assemble the final result by summing the thread-local quires first, then converting to posit.
79//! //TODO for thread in 1..8 {
80//! //TODO quires[0] += quire[thread]
81//! //TODO }
82//! let result: p16 = (&quires[0]).round_into();
83//!
84//! // Use mixed-precision with no hassle; it's very cheap when the ES is the same.
85//! let terms = [3, 7, 15, 1].map(p8::round_from); // https://oeis.org/A001203
86//! let pi = {
87//! let mut partial = p64::ZERO;
88//! for i in terms[1..].iter().rev() {
89//! partial = p64::ONE / (i.convert() + partial)
90//! }
91//! terms[0].convert() + partial
92//! };
93//! assert!((3.141592.round_into() .. 3.141593.round_into()).contains(&pi));
94//! ```
95//!
96//! # Performance
97//!
98//! In terms of performance, you can expect as a *very rough estimate* 70 to 350 Mops/s
99//! (corresponding to about a 4–20× slowdown relative to native hw FPU operations) on an 11th gen
100//! Intel x86 core at 3.80GHz. This is, as far as we're aware, faster (or at least as fast) than
101//! any freely available software implementation of posit arithmetic.
102//!
103//! 
105//!
106//! Needless to say, both absolute performance and relative performance vs the FPU will vary
107//! depending on your system.
108//!
109//! This crate includes benchmarks; run them with `cargo bench -F bench`.
110
111mod posit;
112mod underlying;
113
114pub use posit::Posit;
115pub use posit::quire::Quire;
116pub use underlying::Int;
117pub use posit::convert::{RoundFrom, RoundInto};
118
119/// Standard-defined 8-bit posit (with 2-bit exponent).
120#[allow(non_camel_case_types)]
121pub type p8 = Posit<8, 2, i8>;
122
123/// Standard-defined 16-bit posit (with 2-bit exponent).
124#[allow(non_camel_case_types)]
125pub type p16 = Posit<16, 2, i16>;
126
127/// Standard-defined 32-bit posit (with 2-bit exponent).
128#[allow(non_camel_case_types)]
129pub type p32 = Posit<32, 2, i32>;
130
131/// Standard-defined 64-bit posit (with 2-bit exponent).
132#[allow(non_camel_case_types)]
133pub type p64 = Posit<64, 2, i64>;
134
135/// Standard-defined 128-bit quire for a [p8].
136#[allow(non_camel_case_types)]
137pub type q8 = Quire<8, 2, 16>;
138
139/// Standard-defined 256-bit quire for a [p16].
140#[allow(non_camel_case_types)]
141pub type q16 = Quire<16, 2, 32>;
142
143/// Standard-defined 512-bit quire for a [p32].
144#[allow(non_camel_case_types)]
145pub type q32 = Quire<32, 2, 64>;
146
147/// Standard-defined 1024-bit quire for a [p64].
148#[allow(non_camel_case_types)]
149pub type q64 = Quire<64, 2, 128>;
150
151/// Re-export some internals for benchmarking purposes, only on `feature = "bench"`.
152#[cfg(feature = "bench")]
153mod bench;
154
155/// Set the number of proptest cases globally.
156///
157/// This number strikes a balance between coverage and practicality.
158#[cfg(test)]
159const PROPTEST_CASES: u32 = if cfg!(debug_assertions) {0x1_0000} else {0x80_0000};