the_q/lib.rs
1//! # `the-q` — verified bounded rational arithmetic
2//!
3//! [`Rat`] is `num / den` in two `i64` fields, canonical (`den > 0`,
4//! `gcd(|num|, den) == 1`) and bounded (`|num|, den <= 2^62 - 1`). Arithmetic
5//! is exact when the exact result fits and rounded to a proven contract when it
6//! does not; every intermediate is `i128` and cannot overflow.
7//!
8//! ```
9//! use the_q::{Dir, Rat};
10//!
11//! let reliability = Rat::from_decimal(85, 2).unwrap(); // 0.85 == 17/20
12//! let weight = Rat::from_decimal(3, 1).unwrap(); // 0.3 == 3/10
13//! let combined = Rat::mul(reliability, weight);
14//! assert_eq!(combined.to_string(), "51/200");
15//! assert!(combined < reliability);
16//!
17//! // Directed modes bracket the exact value.
18//! let a = Rat::new(1, 3).unwrap();
19//! assert!(Rat::le(Rat::mul_dir(a, a, Dir::Down), Rat::mul_dir(a, a, Dir::Up)));
20//! ```
21//!
22//! `Rat` is partial: `Rat::new(_, 0)` is `None`, `Rat::div(x, 0)` panics, and
23//! an over-budget result saturates. [`Q`] makes each of those a value and is
24//! total: every operation on every input returns a `Q` and nothing panics.
25//!
26//! ```
27//! use the_q::{Q, Rat};
28//!
29//! assert_eq!(Q::div(Q::one(), Q::zero()), Q::PosInf);
30//! assert_eq!(Q::div(Q::zero(), Q::zero()), Q::Nan);
31//! assert_eq!(Q::checked_div(Q::one(), Q::zero()), None);
32//!
33//! // Overflow is reported, and is distinct from division by zero.
34//! let m = Q::Number(Rat::new(the_q::MAX_MAG, 1).unwrap());
35//! let over = Q::add(m, m);
36//! assert!(over.is_saturated() && !over.is_infinite());
37//! assert_eq!(over.to_string(), ">max");
38//!
39//! // Saturation denotes finite reals, so this is exact where `0 * inf` is not.
40//! assert_eq!(Q::mul(Q::zero(), Q::PosSat), Q::zero());
41//! assert_eq!(Q::mul(Q::zero(), Q::PosInf), Q::Nan);
42//!
43//! // The order is total; `Nan` sorts last. Selection propagates `Nan`, so a
44//! // fold of `Q::min` is not `iter().min()`.
45//! let mut v = vec![Q::Nan, Q::PosInf, Q::zero(), Q::NegInf];
46//! v.sort();
47//! assert_eq!(v, vec![Q::NegInf, Q::zero(), Q::PosInf, Q::Nan]);
48//! assert_eq!(Q::min(Q::Nan, Q::one()), Q::Nan);
49//! ```
50//!
51//! ## Limits
52//!
53//! * The rounding bound `2^-61 · max(1, |exact|)` is absolute below 1.
54//! * With rounding, `add` and `mul` are commutative but not associative;
55//! associativity holds on the exact path and the defect is bounded.
56//! * Rounding is monotone on each grid, not across the representable/rounded
57//! boundary. `README.md` has the counterexample.
58//! * Transcendentals are exact at rational points only where a proof says so:
59//! `log2` at `2^k`, `log10` at `10^k` (both signs), `sqrt` of a perfect
60//! square, `exp(0)`, `ln(1)`. `cbrt(8)` and `exp2(3)` are a hair off the
61//! integer, inside the measured bounds.
62//! * `Q` is not a semiring: `add`/`mul` are commutative unconditionally, but
63//! associative, distributive and monotone only on the all-`Number` exact
64//! path — every failure past it has a counterexample in `tests/q_laws.rs`.
65//! `Q::div(a, b) == Q::mul(a, Q::recip(b))` fails in six cells. See
66//! `VERIFICATION.md`'s V11 for what is proven instead: containment of the
67//! special-value propagation tables against an independent ghost model.
68//!
69//! ## Verification and API stability
70//!
71//! Proofs are in the source inside `verus!` blocks; `cargo verus verify`
72//! checks them, `cargo build` erases them. `VERIFICATION.md` maps the
73//! obligations, `TRUSTED.md` lists the three trusted functions.
74//!
75//! [`Rat`], [`Q`], [`Dir`], [`interval::QI`], [`Exact`], their operations and
76//! constructors, and [`nary`] follow semver. Other public items (`gcd`, `model`, `round`,
77//! `lipschitz`, `fx`, and executable helpers such as
78//! `round::round_frac_exec_with_gcd`) are public because Verus's visibility
79//! rules require it and may change shape in patch releases.
80
81#![allow(clippy::needless_range_loop)]
82#![allow(clippy::comparison_chain)]
83// Verus's surface language does not accept compound-assignment operators or
84// `RangeInclusive::contains` in exec code, and the inherent `Rat::add`/`Rat::mul`
85// names are deliberate (the operator traits delegate to them and are not
86// callable from verified code).
87#![allow(clippy::assign_op_pattern)]
88#![allow(clippy::should_implement_trait)]
89#![allow(clippy::manual_range_contains)]
90#![allow(clippy::unusual_byte_groupings)]
91#![allow(clippy::manual_div_ceil)]
92#![allow(clippy::implicit_saturating_sub)]
93// `transcendental::log2`/`log10` match on `Option`/`Q` with a `_ => {}` arm
94// and hang a proof block off each arm. An `if let ... else` would hold the
95// same proofs; `match` is kept because it is the exec shape the rest of the
96// verified modules use, so the discharge sits where a reader expects it.
97#![allow(clippy::single_match)]
98
99// Verus's macro machinery.
100#[allow(unused_imports)]
101use verus_builtin as _;
102#[allow(unused_imports)]
103use vstd as _;
104
105pub mod model;
106pub mod types;
107
108pub mod fx;
109pub mod gcd;
110pub mod round;
111pub mod saturation;
112
113pub mod q;
114
115pub mod convert;
116pub mod denote;
117pub mod exact;
118pub mod ext;
119pub mod interval;
120pub mod laws;
121pub mod laws_q;
122pub mod lipschitz;
123pub mod nary;
124pub mod soundness;
125pub mod soundness_div;
126pub mod soundness_mul;
127pub mod transcendental;
128
129pub use convert::{
130 ParseQError, from_decimal128_dir, from_decimal128_exact, from_f64_dir, from_ratio128_dir,
131 from_ratio128_exact, q_from_f64, to_f64,
132};
133#[cfg(feature = "num-rational")]
134pub use convert::{
135 exact_from_big_rational, exact_from_num_rational_i64, from_big_rational_dir,
136 from_big_rational_exact, from_num_rational_i64_dir, from_num_rational_i64_exact,
137 q_from_big_rational, q_from_num_rational_i64,
138};
139#[cfg(feature = "bigdecimal")]
140pub use convert::{
141 exact_from_bigdecimal, from_bigdecimal_dir, from_bigdecimal_exact, q_from_bigdecimal,
142};
143#[cfg(feature = "num-bigint")]
144pub use convert::{exact_from_bigint, from_bigint_dir, from_bigint_exact, q_from_bigint};
145#[cfg(feature = "fixed")]
146pub use convert::{exact_from_fixed, from_fixed_dir, from_fixed_exact, q_from_fixed};
147#[cfg(feature = "rust_decimal")]
148pub use convert::{exact_from_rust_decimal, from_rust_decimal_dir, q_from_rust_decimal};
149pub use exact::{Exact, ExactError};
150pub use ext::{Q, Sign};
151pub use interval::QI;
152pub use types::{Dir, MAX_DEC_PLACES, MAX_DECIMAL_MANTISSA, MAX_DECIMAL_SCALE, MAX_MAG, Rat};