Skip to main content

the_q/
convert.rs

1//! The crate's edges: `f64` in and out, `Display`, `FromStr`, `serde`.
2//!
3//! Two functions touch a float and both are `external_body` (`TRUSTED.md`):
4//! [`f64_decompose`] extracts the IEEE-754 fields, and [`to_f64`] is for
5//! display only. Everything between is verified integer arithmetic: an `f64`
6//! is `±m · 2^e`, so [`from_parts_dir`] builds the exact integer pair and hands
7//! it to the same rounder every operation uses, and its contract pins the
8//! result to `round_frac` of that pair.
9
10use verus_builtin_macros::verus;
11
12#[allow(unused_imports)]
13use verus_builtin::*;
14#[allow(unused_imports)]
15use vstd::prelude::*;
16
17#[allow(unused_imports)]
18use crate::model::*;
19#[allow(unused_imports)]
20use crate::round::*;
21use crate::types::{Dir, MAX_DECIMAL_MANTISSA, MAX_DECIMAL_SCALE, Rat};
22
23verus! {
24
25/// The IEEE-754 fields of a finite float, `(negative, mantissa, exponent)`;
26/// `None` for NaN and infinities. TRUSTED: Verus has no model of `to_bits`.
27///
28/// Public only because Verus's visibility rules require it; not semver-stable.
29///
30/// ```
31/// use the_q::convert::f64_decompose;
32///
33/// assert_eq!(f64_decompose(1.0f64), Some((false, 4503599627370496u64, -52)));
34/// assert_eq!(f64_decompose(f64::NAN), None);
35/// ```
36#[verifier::external_body]
37pub fn f64_decompose(v: f64) -> (r: Option<(bool, u64, i32)>)
38    ensures
39        r.is_some() ==> {
40            let t = r.unwrap();
41            &&& t.1 <= 9007199254740992u64  // 2^53
42            &&& -1074 <= t.2 <= 971
43        },
44{
45    let bits: u64 = v.to_bits();
46    let neg: bool = (bits >> 63) != 0;
47    let raw_exp: u64 = (bits >> 52) & 0x7ff;
48    let frac: u64 = bits & 0x000f_ffff_ffff_ffff;
49    if raw_exp == 0x7ff {
50        // NaN or +/- infinity.
51        return None;
52    }
53    if raw_exp == 0 {
54        // Subnormal (and zero): value == frac * 2^-1074.
55        Some((neg, frac, -1074))
56    } else {
57        // Normal: value == (2^52 + frac) * 2^(raw_exp - 1075).
58        Some((neg, frac + 0x0010_0000_0000_0000u64, raw_exp as i32 - 1075))
59    }
60}
61
62/// Convert an `f64` to a `Rat`, rounding in direction `dir`. `None` on NaN,
63/// infinity, or `|v| > 2^61`. Below `2^-62` the result is the endpoint of the
64/// first grid cell on the rounding side.
65///
66/// No postcondition can mention `v`: Verus has no model of `f64`. The contract
67/// lives on [`from_parts_dir`]; this is its composition with [`f64_decompose`].
68///
69/// ```
70/// use the_q::{Rat, Dir, from_f64_dir};
71///
72/// assert_eq!(from_f64_dir(0.5, Dir::Nearest), Some(Rat::new(1, 2).unwrap()));
73/// assert_eq!(from_f64_dir(f64::NAN, Dir::Nearest), None);
74/// assert_eq!(from_f64_dir(f64::INFINITY, Dir::Nearest), None);
75/// ```
76pub fn from_f64_dir(v: f64, dir: Dir) -> (r: Option<Rat>)
77    ensures
78        r.is_some() ==> r.unwrap().wf(),
79{
80    match f64_decompose(v) {
81        None => None,
82        Some((neg, mant, e)) => from_parts_dir(neg, mant, e, dir),
83    }
84}
85
86/// The exact numerator of the value an IEEE-754 decomposition denotes:
87/// `(-1)^neg · mant · 2^e`, with the `2^e` folded in when `e >= 0`.
88pub open spec fn parts_num(neg: bool, mant: u64, e: i32) -> int {
89    let m = if neg {
90        -(mant as int)
91    } else {
92        mant as int
93    };
94    if e >= 0 {
95        m * pow2(e as nat)
96    } else {
97        m
98    }
99}
100
101/// The exact denominator of the same value, always positive.
102pub open spec fn parts_den(e: i32) -> int {
103    if e >= 0 {
104        1int
105    } else {
106        pow2((-e) as nat)
107    }
108}
109
110/// The verified core of [`from_f64_dir`]: `(neg, mant, e)` to a `Rat`.
111///
112/// The result is pinned to `round_frac` of the exact rational the triple
113/// denotes, with R2 and R3 restated; the sub-grid branch (`2^s`, `s > 124`)
114/// is covered by `round::lemma_round_frac_subgrid`. The `requires` is ghost and
115/// this function is `pub`, so the body re-checks the bounds and returns `None`
116/// outside them rather than overflow for an unverified caller.
117///
118/// Public only because Verus's visibility rules require it; not semver-stable.
119///
120/// ```
121/// use the_q::Dir;
122/// use the_q::convert::from_parts_dir;
123///
124/// // 1 * 2^0 == 1.
125/// assert_eq!(from_parts_dir(false, 1, 0, Dir::Nearest).unwrap().to_string(), "1/1");
126/// ```
127pub fn from_parts_dir(neg: bool, mant: u64, e: i32, dir: Dir) -> (r: Option<Rat>)
128    requires
129        mant <= 9007199254740992u64,
130        -1074 <= e <= 971,
131    ensures
132        r.is_some() ==> r.unwrap().wf(),
133        // The value pin, over the *decomposed* rational.
134        r.is_some() ==> r.unwrap() == round_frac(parts_num(neg, mant, e), parts_den(e), dir),
135        // R2 and R3, guarded on `!saturated`, exactly as the crate's rounding
136        // contract is scoped. This function discharges the guard for every
137        // value it accepts rather than assumes it. It returns `None` above
138        // `2^61`, so every value with a result is far inside the ceiling.
139        r.is_some() ==> !saturated(parts_num(neg, mant, e), parts_den(e)),
140        (r.is_some() && dir == Dir::Down) ==> q_le_frac(
141            r.unwrap(),
142            parts_num(neg, mant, e),
143            parts_den(e),
144        ),
145        (r.is_some() && dir == Dir::Up) ==> q_ge_frac(
146            r.unwrap(),
147            parts_num(neg, mant, e),
148            parts_den(e),
149        ),
150        r.is_some() ==> within_error_bound(
151            r.unwrap(),
152            parts_num(neg, mant, e),
153            parts_den(e),
154        ),
155        // Completeness: the documented magnitude restriction is the *only*
156        // reason this returns `None`.
157        r.is_none() ==> abs_int(parts_num(neg, mant, e)) > pow2(61) * parts_den(e),
158{
159    // The `requires` above, enforced for callers Verus never sees. This branch
160    // is dead under verification. The postconditions below are therefore
161    // unweakened by it.
162    if mant > 9007199254740992u64 || e < -1074 || e > 971 {
163        return None;
164    }
165    if mant == 0 {
166        proof {
167            if e >= 0 {
168                assert(parts_num(neg, mant, e) == 0) by (nonlinear_arith)
169                    requires
170                        parts_num(neg, mant, e) == 0 * pow2(e as nat),
171                ;
172            }
173            assert(parts_den(e) > 0) by {
174                lemma_pow2_pos((-e) as nat);
175            }
176            lemma_r2_r3_directed(parts_num(neg, mant, e), parts_den(e), dir);
177            assert(gcd_int(0, 1) == 1) by {
178                reveal_with_fuel(gcd_nat, 3);
179            }
180            Rat::lemma_from_raw_spec_wf(0, 1);
181        }
182        return Some(Rat::from_raw_parts(0, 1));
183    }
184    proof {
185        lemma_pow2_61();
186        lemma_pow2_62();
187        lemma_pow2_124();
188        lemma_pow2_126();
189    }
190    if e >= 0 {
191        proof {
192            lemma_pow2_pos(e as nat);
193            // `|±mant · 2^e| == mant · 2^e`. Every branch below needs this
194            // fact. It is a fact about `abs_int` of a product, so it is
195            // established here rather than assumed inside a nonlinear block.
196            let m = if neg {
197                -(mant as int)
198            } else {
199                mant as int
200            };
201            lemma_abs_mul_pos(m, pow2(e as nat));
202            assert(abs_int(m) == mant as int);
203            assert(abs_int(parts_num(neg, mant, e)) == (mant as int) * pow2(e as nat));
204        }
205        if e > 64 {
206            proof {
207                // `mant >= 1` and `e >= 65`, so the value is at least `2^65`.
208                // The postcondition needs a *strict* excess over `2^61`, so the
209                // proof goes through `2^62`. Monotonicity alone gives only
210                // `<=`.
211                lemma_pow2_mono(62nat, e as nat);
212                lemma_pow2_pos(61nat);
213                lemma_pow2_pos(e as nat);
214                assert(pow2(62) == 2 * pow2(61));
215                assert(abs_int(parts_num(neg, mant, e)) >= pow2(e as nat))
216                    by (nonlinear_arith)
217                    requires
218                        abs_int(parts_num(neg, mant, e)) == (mant as int) * pow2(e as nat),
219                        mant >= 1,
220                        pow2(e as nat) > 0,
221                ;
222                assert(parts_den(e) == 1);
223                assert(abs_int(parts_num(neg, mant, e)) > pow2(61) * parts_den(e))
224                    by (nonlinear_arith)
225                    requires
226                        abs_int(parts_num(neg, mant, e)) >= pow2(e as nat),
227                        pow2(62) <= pow2(e as nat),
228                        pow2(62) == 2 * pow2(61),
229                        pow2(61) > 0,
230                        parts_den(e) == 1,
231                ;
232            }
233            return None;
234        }
235        let p: i128 = pow2_i128(e as u32);
236        proof {
237            // mant <= 2^53 and p == 2^e <= 2^64, so the product is at most
238            // 2^117, far inside i128.
239            lemma_pow2_mono(e as nat, 64nat);
240            lemma_pow2_64();
241            assert((mant as int) * (p as int) <= 9007199254740992int
242                * 18446744073709551616int) by (nonlinear_arith)
243                requires
244                    0 <= mant <= 9007199254740992int,
245                    0 < p <= 18446744073709551616int,
246            ;
247        }
248        let mag: i128 = (mant as i128) * p;
249        proof {
250            assert(abs_int(parts_num(neg, mant, e)) == mag as int) by (nonlinear_arith)
251                requires
252                    abs_int(parts_num(neg, mant, e)) == (mant as int) * pow2(e as nat),
253                    mag as int == (mant as int) * (p as int),
254                    p as int == pow2(e as nat),
255            ;
256        }
257        if mag > 2305843009213693952i128 {
258            // > 2^61
259            return None;
260        }
261        let n: i128 = if neg {
262            0 - mag
263        } else {
264            mag
265        };
266        proof {
267            assert(pow2(0) == 1);
268            // `-(mant · 2^e) == (-mant) · 2^e`. The code applies the sign to
269            // the product, and the spec applies it to the mantissa.
270            assert(-((mant as int) * pow2(e as nat)) == (-(mant as int)) * pow2(e as nat))
271                by (nonlinear_arith);
272            assert(n as int == parts_num(neg, mant, e));
273            assert(parts_den(e) == 1);
274            // Inside `2^61`, so nowhere near the `2^62 - 1` ceiling.
275            assert(!saturated(n as int, 1int)) by (nonlinear_arith)
276                requires
277                    abs_int(n as int) <= pow2(61),
278                    max_mag() == pow2(62) - 1,
279                    pow2(62) == 2 * pow2(61),
280                    pow2(61) > 0,
281            ;
282            lemma_r2_r3_directed(n as int, 1int, dir);
283        }
284        Some(round_frac_exec(n, 1, dir))
285    } else if e >= -124 {
286        let s: u32 = (0 - e) as u32;
287        let d: i128 = pow2_i128(s);
288        let n: i128 = if neg {
289            0 - (mant as i128)
290        } else {
291            mant as i128
292        };
293        proof {
294            // d == 2^s with s <= 124, which is exactly the denominator bound
295            // `round_frac_exec` asks for. Also, |n| <= 2^53 is far below the
296            // numerator bound.
297            lemma_pow2_mono(s as nat, 124nat);
298            lemma_pow2_124();
299            lemma_pow2_126();
300            lemma_pow2_pos(s as nat);
301            assert(n as int == parts_num(neg, mant, e));
302            assert(d as int == parts_den(e));
303            // `|n| <= 2^53 <= max_mag <= max_mag · d`, since `d >= 1`. The
304            // proof runs on the literals. The mantissa bound is already a
305            // literal, so there is no `pow2(53)` to pin.
306            assert(abs_int(n as int) <= max_mag() * (d as int)) by (nonlinear_arith)
307                requires
308                    abs_int(n as int) <= 9007199254740992int,
309                    max_mag() == pow2(62) - 1,
310                    pow2(62) == 4611686018427387904int,
311                    d as int >= 1,
312            ;
313            lemma_r2_r3_directed(n as int, d as int, dir);
314        }
315        Some(round_frac_exec(n, d, dir))
316    } else {
317        // |v| <= 2^53 · 2^-125 == 2^-72, strictly inside the first grid cell of
318        // width 2^-61. `round_frac` therefore lands on that cell's endpoint.
319        // `tiny` returns that endpoint, by `lemma_round_frac_subgrid`.
320        proof {
321            // `s` is ghost-only here. This branch computes nothing. It only
322            // shows that `tiny` returns the value `round_frac` produces.
323            let s = (-e) as nat;
324            let n = parts_num(neg, mant, e);
325            let d = parts_den(e);
326            assert(d == pow2(s));
327            lemma_pow2_pos(s);
328            lemma_pow2_mono(125nat, s);
329            lemma_pow2_125();
330            // |n|·2^62 <= 2^53 · 2^62 == 2^115 < 2^125 <= d. The proof runs on
331            // the literals rather than through a `pow2(53) + pow2(62)`
332            // addition lemma. `2^53` is already a literal here, as the
333            // mantissa bound in the precondition, and `2^62`/`2^125` are
334            // pinned. Nothing remains to add.
335            assert(abs_int(n) * pow2(62) <= 9007199254740992int
336                * 4611686018427387904int) by (nonlinear_arith)
337                requires
338                    abs_int(n) <= 9007199254740992int,
339                    pow2(62) == 4611686018427387904int,
340            ;
341            assert(abs_int(n) * pow2(62) < d) by (nonlinear_arith)
342                requires
343                    abs_int(n) * pow2(62) <= 9007199254740992int * 4611686018427387904int,
344                    pow2(125) == 42535295865117307932921825928971026432int,
345                    pow2(125) <= d,
346            ;
347            assert(abs_int(n) >= 1);
348            assert(n != 0);
349            assert((n > 0) == !neg);
350            // `magnitude_fits`, and hence `!saturated`, comes back out of the
351            // subgrid lemma. That lemma cannot reach `round_frac` without a
352            // proof of `magnitude_fits` first. A re-derivation here duplicates
353            // the same `nonlinear_arith` block in a second file.
354            lemma_round_frac_subgrid(n, d, dir);
355            lemma_r2_r3_directed(n, d, dir);
356        }
357        Some(tiny(neg, dir))
358    }
359}
360
361/// The endpoint of the first dyadic cell, for a magnitude below `2^-62`.
362/// Pinned to `round::subgrid_endpoint`, which `round::lemma_round_frac_subgrid`
363/// identifies with `round_frac`.
364///
365/// Public only because Verus's visibility rules require it; not semver-stable.
366///
367/// ```
368/// use the_q::Dir;
369/// use the_q::convert::tiny;
370///
371/// let up = tiny(false, Dir::Up);
372/// assert_eq!(up.to_string(), "1/2305843009213693952");
373/// ```
374pub fn tiny(neg: bool, dir: Dir) -> (r: Rat)
375    ensures
376        r.wf(),
377        r == crate::round::subgrid_endpoint(!neg, dir),
378{
379    let eps_den: i64 = 2305843009213693952;  // 2^61
380    proof {
381        lemma_max_mag_pow2();
382        lemma_pow2_61();
383        lemma_pow2_62();
384        // I1 for the two endpoints: `gcd(±1, 2^61)` is between 1 and 1.
385        crate::gcd::lemma_gcd_pos(1nat, 2305843009213693952nat);
386        crate::gcd::lemma_gcd_le(1nat, 2305843009213693952nat);
387        assert(gcd_int(0, 1) == 1) by {
388            reveal_with_fuel(gcd_nat, 3);
389        }
390        Rat::lemma_from_raw_spec_wf(0, 1);
391        Rat::lemma_from_raw_spec_wf((-1int) as i64, eps_den);
392        Rat::lemma_from_raw_spec_wf(1, eps_den);
393    }
394    match dir {
395        Dir::Nearest => Rat::from_raw_parts(0, 1),
396        Dir::Down => {
397            if neg {
398                Rat::from_raw_parts(-1, eps_den)
399            } else {
400                Rat::from_raw_parts(0, 1)
401            }
402        },
403        Dir::Up => {
404            if neg {
405                Rat::from_raw_parts(0, 1)
406            } else {
407                Rat::from_raw_parts(1, eps_den)
408            }
409        },
410    }
411}
412
413/// A `Rat` as an `f64`, for display only. TRUSTED. Three roundings, so about
414/// `3·2^-53` relative; do not feed the result back into `Rat`.
415///
416/// ```
417/// use the_q::{Rat, to_f64};
418///
419/// assert_eq!(to_f64(Rat::new(1, 2).unwrap()), 0.5);
420/// ```
421#[verifier::external_body]
422pub fn to_f64(q: Rat) -> f64 {
423    (q.numerator() as f64) / (q.denominator() as f64)
424}
425
426// ---------------------------------------------------------------------------
427// The decimal boundary (issue #33): `mantissa · 10^-scale`, exact where the
428// budget allows it, rounded per `dir` otherwise. `rust_decimal::Decimal`
429// guarantees `scale() <= MAX_DECIMAL_SCALE` and `|mantissa()| <=
430// MAX_DECIMAL_MANTISSA`, exactly the domain accepted here, so the boundary in
431// `convert.rs` below never observes `None` for a real `Decimal`.
432// ---------------------------------------------------------------------------
433
434/// `10^n` for `n <= MAX_DECIMAL_SCALE`, as a literal table (a loop needs an
435/// invariant and an overflow lemma for the same result; `q::pow10_i64` is the
436/// same idea one order of magnitude short of what a `Decimal` scale needs).
437///
438/// ```
439/// use the_q::convert::pow10_i128;
440///
441/// assert_eq!(pow10_i128(0), 1);
442/// assert_eq!(pow10_i128(3), 1000);
443/// ```
444pub fn pow10_i128(n: u32) -> (r: i128)
445    requires
446        n <= MAX_DECIMAL_SCALE,
447    ensures
448        1 <= r <= 10000000000000000000000000000i128,
449        r == pow10(n as nat),
450{
451    reveal_with_fuel(pow10, 30);
452    match n {
453        0 => 1,
454        1 => 10,
455        2 => 100,
456        3 => 1000,
457        4 => 10000,
458        5 => 100000,
459        6 => 1000000,
460        7 => 10000000,
461        8 => 100000000,
462        9 => 1000000000,
463        10 => 10000000000,
464        11 => 100000000000,
465        12 => 1000000000000,
466        13 => 10000000000000,
467        14 => 100000000000000,
468        15 => 1000000000000000,
469        16 => 10000000000000000,
470        17 => 100000000000000000,
471        18 => 1000000000000000000,
472        19 => 10000000000000000000,
473        20 => 100000000000000000000,
474        21 => 1000000000000000000000,
475        22 => 10000000000000000000000,
476        23 => 100000000000000000000000,
477        24 => 1000000000000000000000000,
478        25 => 10000000000000000000000000,
479        26 => 100000000000000000000000000,
480        27 => 1000000000000000000000000000,
481        _ => 10000000000000000000000000000,
482    }
483}
484
485/// Convert the exact decimal `mantissa · 10^-scale` to a `Rat`, rounding in
486/// direction `dir` when the value does not fit the width budget. `None` only
487/// outside the domain a `rust_decimal::Decimal` can produce (`scale >
488/// MAX_DECIMAL_SCALE`, or a mantissa wider than a 96-bit magnitude), so this
489/// is total on every `Decimal`.
490///
491/// Unlike [`from_f64_dir`], there is no separate magnitude cutoff: a mantissa
492/// whose exact value is beyond the crate's budget is not refused, it saturates
493/// to the same `±MAX_MAG/1` boundary every other operation saturates to (R3,
494/// scoped by `!saturated`; see `round.rs`). `crate::ext::Q`-level ingestion
495/// (behind the `rust_decimal` feature) tests that condition separately and
496/// reports it as `PosSat`/`NegSat`, matching `q_from_f64`.
497///
498/// ```
499/// use the_q::{Dir, Rat};
500/// use the_q::convert::from_decimal128_dir;
501///
502/// // 0.85 == 85 * 10^-2 == 17/20.
503/// assert_eq!(from_decimal128_dir(85, 2, Dir::Nearest), Some(Rat::new(17, 20).unwrap()));
504/// assert_eq!(from_decimal128_dir(1, 29, Dir::Nearest), None); // scale > MAX_DECIMAL_SCALE
505/// ```
506pub fn from_decimal128_dir(mantissa: i128, scale: u32, dir: Dir) -> (r: Option<Rat>)
507    ensures
508        r.is_some() ==> r.unwrap().wf(),
509        r.is_some() ==> r.unwrap() == round_frac(mantissa as int, pow10(scale as nat), dir),
510        (r.is_some() && !crate::round::saturated(mantissa as int, pow10(scale as nat))) ==> {
511            &&& dir == Dir::Down ==> q_le_frac(r.unwrap(), mantissa as int, pow10(scale as nat))
512            &&& dir == Dir::Up ==> q_ge_frac(r.unwrap(), mantissa as int, pow10(scale as nat))
513            &&& within_error_bound(r.unwrap(), mantissa as int, pow10(scale as nat))
514        },
515        r.is_none() <==> (scale > MAX_DECIMAL_SCALE || mantissa > MAX_DECIMAL_MANTISSA
516            || mantissa < -MAX_DECIMAL_MANTISSA),
517{
518    if scale > MAX_DECIMAL_SCALE {
519        return None;
520    }
521    if mantissa > MAX_DECIMAL_MANTISSA || mantissa < -MAX_DECIMAL_MANTISSA {
522        return None;
523    }
524    let d: i128 = pow10_i128(scale);
525    proof {
526        lemma_pow2_124();
527        lemma_pow2_126();
528        assert(abs_int(mantissa as int) <= MAX_DECIMAL_MANTISSA as int);
529        assert((MAX_DECIMAL_MANTISSA as int) < (pow2(126)));
530        assert(d as int <= 10000000000000000000000000000int);
531        assert(10000000000000000000000000000int <= pow2(124));
532        if !crate::round::saturated(mantissa as int, d as int) {
533            crate::round::lemma_r2_r3_directed(mantissa as int, d as int, dir);
534        }
535    }
536    Some(round_frac_exec(mantissa, d, dir))
537}
538
539/// The exact decimal `mantissa · 10^-scale` as a `Rat`, `None` unless the
540/// reduced `(mantissa, 10^scale)` pair already fits the width budget — the
541/// domain [`from_decimal128_dir`] takes without rounding or saturating.
542///
543/// Wrapping the result in `crate::exact::Exact` then denotes the same value
544/// as the decimal, not merely a `Rat` that happens not to need *further*
545/// rounding: unlike `from_decimal128_dir`, this refuses exactly the cases
546/// where the *ingestion itself* would have rounded, matching how `Exact`'s
547/// own arithmetic reports rounding instead of performing it silently.
548///
549/// ```
550/// use the_q::Rat;
551/// use the_q::convert::from_decimal128_exact;
552///
553/// assert_eq!(from_decimal128_exact(85, 2), Some(Rat::new(17, 20).unwrap()));
554/// // MAX_DECIMAL_MANTISSA at the maximum scale reduces to a denominator
555/// // far past the width budget: the exact path refuses instead of rounding.
556/// assert_eq!(from_decimal128_exact(79228162514264337593543950335, 28), None);
557/// ```
558pub fn from_decimal128_exact(mantissa: i128, scale: u32) -> (r: Option<Rat>)
559    ensures
560        r.is_some() ==> r.unwrap().wf(),
561        r.is_some() ==> q_is(r.unwrap(), mantissa as int, pow10(scale as nat)),
562        r.is_none() <==> !(scale <= MAX_DECIMAL_SCALE && mantissa <= MAX_DECIMAL_MANTISSA
563            && mantissa >= -MAX_DECIMAL_MANTISSA
564            && crate::round::exact_path(mantissa as int, pow10(scale as nat))),
565{
566    if scale > MAX_DECIMAL_SCALE {
567        return None;
568    }
569    if mantissa > MAX_DECIMAL_MANTISSA || mantissa < -MAX_DECIMAL_MANTISSA {
570        return None;
571    }
572    let d: i128 = pow10_i128(scale);
573    proof {
574        lemma_pow2_124();
575        lemma_pow2_126();
576        assert(abs_int(mantissa as int) <= MAX_DECIMAL_MANTISSA as int);
577        assert((MAX_DECIMAL_MANTISSA as int) < (pow2(126)));
578        assert(d as int <= 10000000000000000000000000000int);
579        assert(10000000000000000000000000000int <= pow2(124));
580    }
581    if crate::round::exact_path_exec(mantissa, d) {
582        proof {
583            crate::round::lemma_r1_identity(mantissa as int, d as int, Dir::Nearest);
584        }
585        Some(round_frac_exec(mantissa, d, Dir::Nearest))
586    } else {
587        None
588    }
589}
590
591// ---------------------------------------------------------------------------
592// The shared ratio core (issue #33 follow-up): every other library's exact
593// fraction reduces to an `(n, d)` pair over `i128` and calls one of the two
594// functions below. `from_decimal128_dir`/`from_decimal128_exact` above predate
595// this and are left as they were verified (`d = 10^scale`, always positive,
596// never needs the sign normalisation below); every *new* adapter in this
597// module is built on this pair instead of repeating that proof shape.
598// ---------------------------------------------------------------------------
599
600/// `2^126 - 1`: the largest numerator magnitude [`from_ratio128_dir`] and
601/// [`from_ratio128_exact`] accept. Chosen (rather than `i128::MAX`) so that
602/// `abs(n) <= RATIO_N_LIMIT` implies the strict `abs(n) < num_input_bound()`
603/// (`2^126`) `round_frac_exec` requires, and so that negating `n` below —
604/// needed to fold a negative `d`'s sign onto it — never overflows.
605pub const RATIO_N_LIMIT: i128 = 85070591730234615865843651857942052863;
606
607/// `2^124`, exactly `den_input_bound()`: the largest denominator magnitude
608/// the two functions below accept.
609pub const RATIO_D_LIMIT: i128 = 21267647932558653966460912964485513216;
610
611/// Convert the exact ratio `n / d` to a `Rat`, rounding in direction `dir`
612/// once it no longer fits the width budget. `None` only when `d == 0`, or
613/// either magnitude is too wide for `round_frac_exec` to accept at all
614/// (`abs(n) <= 2^126 - 1`, `abs(d) <= 2^124`) — wider than that and no
615/// `i128` pair can name the value in the first place, regardless of which
616/// library produced it.
617///
618/// ```
619/// use the_q::{Dir, Rat};
620/// use the_q::convert::from_ratio128_dir;
621///
622/// // -3 / -4 == 3 / 4, not -(3/4): the negative denominator's sign folds
623/// // onto the numerator.
624/// assert_eq!(from_ratio128_dir(-3, -4, Dir::Nearest), Some(Rat::new(3, 4).unwrap()));
625/// assert_eq!(from_ratio128_dir(1, 0, Dir::Nearest), None); // zero denominator
626/// ```
627pub fn from_ratio128_dir(n: i128, d: i128, dir: Dir) -> (r: Option<Rat>)
628    ensures
629        r.is_some() ==> r.unwrap().wf(),
630        r.is_some() ==> r.unwrap() == round_frac(
631            crate::q::signed_den_num(n as int, d as int),
632            abs_int(d as int),
633            dir,
634        ),
635        (r.is_some() && !crate::round::saturated(
636            crate::q::signed_den_num(n as int, d as int),
637            abs_int(d as int),
638        )) ==> {
639            &&& dir == Dir::Down ==> q_le_frac(
640                r.unwrap(),
641                crate::q::signed_den_num(n as int, d as int),
642                abs_int(d as int),
643            )
644            &&& dir == Dir::Up ==> q_ge_frac(
645                r.unwrap(),
646                crate::q::signed_den_num(n as int, d as int),
647                abs_int(d as int),
648            )
649            &&& within_error_bound(
650                r.unwrap(),
651                crate::q::signed_den_num(n as int, d as int),
652                abs_int(d as int),
653            )
654        },
655        r.is_none() <==> (d == 0 || n > RATIO_N_LIMIT || n < -RATIO_N_LIMIT || d > RATIO_D_LIMIT
656            || d < -RATIO_D_LIMIT),
657{
658    if d == 0 {
659        return None;
660    }
661    if n > RATIO_N_LIMIT || n < -RATIO_N_LIMIT {
662        return None;
663    }
664    if d > RATIO_D_LIMIT || d < -RATIO_D_LIMIT {
665        return None;
666    }
667    let mut nn: i128 = n;
668    let mut dd: i128 = d;
669    if dd < 0 {
670        nn = 0 - nn;
671        dd = 0 - dd;
672    }
673    proof {
674        lemma_pow2_124();
675        lemma_pow2_126();
676        if !crate::round::saturated(nn as int, dd as int) {
677            crate::round::lemma_r2_r3_directed(nn as int, dd as int, dir);
678        }
679    }
680    Some(round_frac_exec(nn, dd, dir))
681}
682
683/// The exact ratio `n / d` as a `Rat`, `None` unless the reduced pair already
684/// fits the width budget — the domain [`from_ratio128_dir`] takes without
685/// rounding or saturating. Wrapping the result in `crate::exact::Exact` then
686/// denotes the same value as the original `n / d`, not merely a `Rat` that
687/// happens not to need *further* rounding.
688///
689/// ```
690/// use the_q::Rat;
691/// use the_q::convert::from_ratio128_exact;
692///
693/// assert_eq!(from_ratio128_exact(22, 7), Some(Rat::new(22, 7).unwrap()));
694/// // A denominator just past MAX_MAG, coprime to the numerator: on the
695/// // R2/R3 snap path, not the exact one.
696/// assert_eq!(from_ratio128_exact(1, 4_611_686_018_427_387_904 + 1), None);
697/// ```
698pub fn from_ratio128_exact(n: i128, d: i128) -> (r: Option<Rat>)
699    ensures
700        r.is_some() ==> r.unwrap().wf(),
701        r.is_some() ==> q_is(
702            r.unwrap(),
703            crate::q::signed_den_num(n as int, d as int),
704            abs_int(d as int),
705        ),
706        r.is_none() <==> !(d != 0 && n <= RATIO_N_LIMIT && n >= -RATIO_N_LIMIT
707            && d <= RATIO_D_LIMIT && d >= -RATIO_D_LIMIT && crate::round::exact_path(
708            crate::q::signed_den_num(n as int, d as int),
709            abs_int(d as int),
710        )),
711{
712    if d == 0 {
713        return None;
714    }
715    if n > RATIO_N_LIMIT || n < -RATIO_N_LIMIT {
716        return None;
717    }
718    if d > RATIO_D_LIMIT || d < -RATIO_D_LIMIT {
719        return None;
720    }
721    let mut nn: i128 = n;
722    let mut dd: i128 = d;
723    if dd < 0 {
724        nn = 0 - nn;
725        dd = 0 - dd;
726    }
727    proof {
728        lemma_pow2_124();
729        lemma_pow2_126();
730    }
731    if crate::round::exact_path_exec(nn, dd) {
732        proof {
733            crate::round::lemma_r1_identity(nn as int, dd as int, Dir::Nearest);
734        }
735        Some(round_frac_exec(nn, dd, Dir::Nearest))
736    } else {
737        None
738    }
739}
740
741} // verus!
742
743// ---------------------------------------------------------------------------
744// Display and serde. Outside the verified region, thin and total.
745// ---------------------------------------------------------------------------
746
747#[cfg_attr(verus_keep_ghost, verifier::external)]
748impl core::fmt::Display for Rat {
749    /// `"num/den"`, always in canonical form. The string is thus a faithful and
750    /// unambiguous rendering of the value.
751    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
752        write!(f, "{}/{}", self.numerator(), self.denominator())
753    }
754}
755
756/// The `(num, den)` pair, exact; decoding re-canonicalises through [`Rat::new`]
757/// and rejects a malformed payload.
758#[cfg(feature = "serde")]
759#[cfg_attr(verus_keep_ghost, verifier::external)]
760impl serde::Serialize for Rat {
761    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
762        use serde::ser::SerializeTuple;
763        let mut t = s.serialize_tuple(2)?;
764        t.serialize_element(&self.numerator())?;
765        t.serialize_element(&self.denominator())?;
766        t.end()
767    }
768}
769
770#[cfg(feature = "serde")]
771#[cfg_attr(verus_keep_ghost, verifier::external)]
772impl<'de> serde::Deserialize<'de> for Rat {
773    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Rat, D::Error> {
774        use serde::de::Error;
775        let (num, den) = <(i64, i64) as serde::Deserialize>::deserialize(d)?;
776        Rat::new(num, den).ok_or_else(|| {
777            D::Error::custom("the-q: (num, den) pair is not a representable rational")
778        })
779    }
780}
781
782// ---------------------------------------------------------------------------
783// The extended `Q`: Display, FromStr and serde (issue #26 §8)
784//
785// All three share one spelling, rather than two spellings that can drift:
786//
787//     nan   inf   -inf   >max   <-max
788//
789// The saturation spellings are not readable as numbers. A numeral for `PosSat`
790// is wrong in either direction. The value is finite, so `inf` is wrong. The
791// value is also unknown, so `4611686018427387903` is worse: it claims an exact
792// value that the type explicitly does not have.
793// ---------------------------------------------------------------------------
794
795/// The spelling of `Q::PosSat` in `Display`, `FromStr` and serde.
796const POS_SAT_STR: &str = ">max";
797/// The spelling of `Q::NegSat`.
798const NEG_SAT_STR: &str = "<-max";
799/// The spelling of `Q::PosInf`.
800const POS_INF_STR: &str = "inf";
801/// The spelling of `Q::NegInf`.
802const NEG_INF_STR: &str = "-inf";
803/// The spelling of `Q::Nan`.
804const NAN_STR: &str = "nan";
805
806#[cfg_attr(verus_keep_ghost, verifier::external)]
807impl core::fmt::Display for crate::ext::Q {
808    /// `"num/den"` for a number, and the fixed spelling above for each special.
809    ///
810    /// Every output round-trips through [`FromStr`](core::str::FromStr).
811    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
812        match self {
813            crate::ext::Q::Number(x) => write!(f, "{}", x),
814            crate::ext::Q::PosSat => f.write_str(POS_SAT_STR),
815            crate::ext::Q::NegSat => f.write_str(NEG_SAT_STR),
816            crate::ext::Q::PosInf => f.write_str(POS_INF_STR),
817            crate::ext::Q::NegInf => f.write_str(NEG_INF_STR),
818            crate::ext::Q::Nan => f.write_str(NAN_STR),
819        }
820    }
821}
822
823/// Why a string could not be parsed as a [`Q`](crate::ext::Q).
824#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
825pub enum ParseQError {
826    /// The input matched no special spelling and was not of the form
827    /// `int` or `int/int`.
828    Malformed,
829    /// A numeral did not fit an `i64`.
830    IntOverflow,
831    /// The denominator was zero. `"1/0"` in input is a malformed numeral, not a
832    /// computation, so it is rejected rather than read as `PosInf`.
833    ZeroDenominator,
834    /// The pair does not reduce to a value inside the width budget.
835    OutOfBudget,
836}
837
838#[cfg_attr(verus_keep_ghost, verifier::external)]
839impl core::fmt::Display for ParseQError {
840    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
841        f.write_str(match self {
842            ParseQError::Malformed => "the-q: not a rational or a recognised special",
843            ParseQError::IntOverflow => "the-q: numeral does not fit an i64",
844            ParseQError::ZeroDenominator => "the-q: denominator is zero",
845            ParseQError::OutOfBudget => "the-q: value is outside the width budget",
846        })
847    }
848}
849
850impl std::error::Error for ParseQError {}
851
852#[cfg_attr(verus_keep_ghost, verifier::external)]
853impl core::str::FromStr for crate::ext::Q {
854    type Err = ParseQError;
855
856    /// Parses what [`Display`](core::fmt::Display) produces, plus a bare integer.
857    /// Specials are case-insensitive; surrounding whitespace is rejected.
858    fn from_str(s: &str) -> Result<Self, ParseQError> {
859        use crate::ext::Q;
860
861        if s.eq_ignore_ascii_case(NAN_STR) {
862            return Ok(Q::Nan);
863        }
864        if s.eq_ignore_ascii_case(POS_INF_STR) {
865            return Ok(Q::PosInf);
866        }
867        if s.eq_ignore_ascii_case(NEG_INF_STR) {
868            return Ok(Q::NegInf);
869        }
870        // The saturation spellings contain no letters, so the case-insensitive
871        // comparison is only for uniformity of treatment.
872        if s.eq_ignore_ascii_case(POS_SAT_STR) {
873            return Ok(Q::PosSat);
874        }
875        if s.eq_ignore_ascii_case(NEG_SAT_STR) {
876            return Ok(Q::NegSat);
877        }
878
879        let (num_str, den_str) = match s.split_once('/') {
880            Some((n, d)) => (n, d),
881            None => (s, "1"),
882        };
883        // `split_once` on `"//"` yields `("", "/")`, and on `"1/2/3"` yields
884        // `("1", "2/3")`. Both inputs fail here, which is the intent.
885        let num: i64 = parse_i64(num_str)?;
886        let den: i64 = parse_i64(den_str)?;
887        if den == 0 {
888            return Err(ParseQError::ZeroDenominator);
889        }
890        match crate::types::Rat::new(num, den) {
891            Some(x) => Ok(Q::Number(x)),
892            None => Err(ParseQError::OutOfBudget),
893        }
894    }
895}
896
897/// `i64::from_str`, distinguishing overflow from a malformed numeral by a
898/// syntactic check rather than a wider re-parse.
899#[cfg_attr(verus_keep_ghost, verifier::external)]
900fn parse_i64(s: &str) -> Result<i64, ParseQError> {
901    match s.parse::<i64>() {
902        Ok(v) => Ok(v),
903        Err(_) => {
904            let digits = s.strip_prefix(['+', '-']).unwrap_or(s);
905            if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()) {
906                Err(ParseQError::IntOverflow)
907            } else {
908                Err(ParseQError::Malformed)
909            }
910        }
911    }
912}
913
914/// A number as the `(num, den)` pair, a special as its string. Untagged, so
915/// it decodes only in self-describing formats; `bincode` fails at run time.
916#[cfg(feature = "serde")]
917#[cfg_attr(verus_keep_ghost, verifier::external)]
918impl serde::Serialize for crate::ext::Q {
919    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
920        match self {
921            crate::ext::Q::Number(x) => x.serialize(s),
922            crate::ext::Q::PosSat => s.serialize_str(POS_SAT_STR),
923            crate::ext::Q::NegSat => s.serialize_str(NEG_SAT_STR),
924            crate::ext::Q::PosInf => s.serialize_str(POS_INF_STR),
925            crate::ext::Q::NegInf => s.serialize_str(NEG_INF_STR),
926            crate::ext::Q::Nan => s.serialize_str(NAN_STR),
927        }
928    }
929}
930
931#[cfg(feature = "serde")]
932#[cfg_attr(verus_keep_ghost, verifier::external)]
933impl<'de> serde::Deserialize<'de> for crate::ext::Q {
934    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
935        struct V;
936
937        impl<'de> serde::de::Visitor<'de> for V {
938            type Value = crate::ext::Q;
939
940            fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
941                f.write_str(
942                    "a [num, den] pair or one of \"nan\", \"inf\", \"-inf\", \">max\", \"<-max\"",
943                )
944            }
945
946            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
947                use crate::ext::Q;
948                // Exact match, not the case-insensitive `FromStr` spelling. A
949                // wire format is machine-written. A case variant therefore
950                // means an encoder disagrees with this one. The mismatch is
951                // caught rather than absorbed.
952                match v {
953                    NAN_STR => Ok(Q::Nan),
954                    POS_INF_STR => Ok(Q::PosInf),
955                    NEG_INF_STR => Ok(Q::NegInf),
956                    POS_SAT_STR => Ok(Q::PosSat),
957                    NEG_SAT_STR => Ok(Q::NegSat),
958                    _ => Err(E::custom("the-q: unrecognised special-value string")),
959                }
960            }
961
962            fn visit_seq<A: serde::de::SeqAccess<'de>>(
963                self,
964                mut seq: A,
965            ) -> Result<Self::Value, A::Error> {
966                use serde::de::Error;
967                let num: i64 = seq
968                    .next_element()?
969                    .ok_or_else(|| A::Error::custom("the-q: missing numerator"))?;
970                let den: i64 = seq
971                    .next_element()?
972                    .ok_or_else(|| A::Error::custom("the-q: missing denominator"))?;
973                if seq.next_element::<serde::de::IgnoredAny>()?.is_some() {
974                    return Err(A::Error::custom("the-q: expected exactly two elements"));
975                }
976                // Through `Rat::new`, so `[2, 4]` decodes as `1/2` and an unrepresentable
977                // pair is an error, not a saturation.
978                crate::types::Rat::new(num, den)
979                    .map(crate::ext::Q::Number)
980                    .ok_or_else(|| {
981                        A::Error::custom("the-q: (num, den) pair is not a representable rational")
982                    })
983            }
984        }
985
986        // `deserialize_any` is what confines this to self-describing formats.
987        d.deserialize_any(V)
988    }
989}
990
991// ---------------------------------------------------------------------------
992// The f64 boundary for the extended type (issue #26 §8)
993// ---------------------------------------------------------------------------
994
995/// An `f64` as a `Q`, total: NaN → `Nan`, `±inf` → `±Inf`, an over-budget
996/// finite value saturates by sign. There is deliberately no `Q → f64`: no
997/// float honestly denotes `PosSat`. Outside the verified region because it
998/// uses `is_nan`/`is_infinite`/`is_sign_negative`; the value path is
999/// [`from_f64_dir`].
1000///
1001/// ```
1002/// use the_q::{Q, Rat, q_from_f64};
1003///
1004/// assert_eq!(q_from_f64(0.5), Q::Number(Rat::new(1, 2).unwrap()));
1005/// assert_eq!(q_from_f64(f64::NAN), Q::Nan);
1006/// assert_eq!(q_from_f64(f64::INFINITY), Q::PosInf);
1007/// ```
1008#[cfg_attr(verus_keep_ghost, verifier::external)]
1009pub fn q_from_f64(v: f64) -> crate::ext::Q {
1010    use crate::ext::Q;
1011    if v.is_nan() {
1012        return Q::Nan;
1013    }
1014    if v.is_infinite() {
1015        return if v.is_sign_negative() {
1016            Q::NegInf
1017        } else {
1018            Q::PosInf
1019        };
1020    }
1021    match from_f64_dir(v, crate::types::Dir::Nearest) {
1022        Some(x) => Q::Number(x),
1023        // Finite but outside the budget: saturate by sign rather than fail.
1024        // `v` is finite and non-representable, so it is genuinely large.
1025        None => {
1026            if v.is_sign_negative() {
1027                Q::NegSat
1028            } else {
1029                Q::PosSat
1030            }
1031        }
1032    }
1033}
1034
1035// ---------------------------------------------------------------------------
1036// The `rust_decimal` boundary (issue #33)
1037//
1038// `rust_decimal::Decimal` stores `mantissa · 10^-scale` exactly, with no
1039// rounding of its own (`scale() <= 28`, `|mantissa()| <= 2^96 - 1`). That
1040// domain is exactly what `from_decimal128_dir` accepts, so the conversion
1041// below is total on every `Decimal` and never takes the defensive `None`
1042// branch that function keeps for a malformed `(mantissa, scale)` pair.
1043// ---------------------------------------------------------------------------
1044
1045/// `rust_decimal::Decimal` as a `Rat`, rounding in direction `dir` when the
1046/// exact value does not fit the width budget (it then saturates to
1047/// `±MAX_MAG/1`, per R3). Exact whenever the reduced value fits, regardless of
1048/// `dir` — the crate's `mantissa()`/`scale()` pair is fed straight to
1049/// [`from_decimal128_dir`], the verified core.
1050///
1051/// ```
1052/// use rust_decimal::Decimal;
1053/// use the_q::{Dir, Rat, from_rust_decimal_dir};
1054///
1055/// // 0.85 == 85/100 == 17/20.
1056/// let d = Decimal::new(85, 2);
1057/// assert_eq!(from_rust_decimal_dir(d, Dir::Nearest), Rat::new(17, 20).unwrap());
1058/// ```
1059#[cfg(feature = "rust_decimal")]
1060#[cfg_attr(verus_keep_ghost, verifier::external)]
1061pub fn from_rust_decimal_dir(v: rust_decimal::Decimal, dir: Dir) -> Rat {
1062    match from_decimal128_dir(v.mantissa(), v.scale(), dir) {
1063        Some(x) => x,
1064        // Unreachable for a genuine `Decimal`: `scale() <= MAX_DECIMAL_SCALE`
1065        // and `|mantissa()| <= MAX_DECIMAL_MANTISSA` are its own invariants.
1066        None => unreachable!(
1067            "the-q: rust_decimal::Decimal violated its own (mantissa, scale) invariant"
1068        ),
1069    }
1070}
1071
1072/// `rust_decimal::Decimal` as a `Q`, rounding to nearest. Saturates by sign
1073/// once the magnitude leaves the budget, mirroring [`q_from_f64`], rather than
1074/// silently returning the clamped boundary `Rat` that [`from_rust_decimal_dir`]
1075/// would.
1076///
1077/// ```
1078/// use rust_decimal::Decimal;
1079/// use the_q::{Q, Rat, q_from_rust_decimal};
1080///
1081/// let d = Decimal::new(85, 2);
1082/// assert_eq!(q_from_rust_decimal(d), Q::Number(Rat::new(17, 20).unwrap()));
1083/// assert_eq!(q_from_rust_decimal(Decimal::MAX), Q::PosSat);
1084/// ```
1085#[cfg(feature = "rust_decimal")]
1086#[cfg_attr(verus_keep_ghost, verifier::external)]
1087pub fn q_from_rust_decimal(v: rust_decimal::Decimal) -> crate::ext::Q {
1088    use crate::ext::Q;
1089    let mantissa = v.mantissa();
1090    let scale = v.scale();
1091    let d = pow10_i128(scale);
1092    if crate::q::magnitude_fits_exec(mantissa, d) {
1093        Q::Number(from_rust_decimal_dir(v, crate::types::Dir::Nearest))
1094    } else if mantissa > 0 {
1095        Q::PosSat
1096    } else {
1097        Q::NegSat
1098    }
1099}
1100
1101#[cfg(feature = "rust_decimal")]
1102#[cfg_attr(verus_keep_ghost, verifier::external)]
1103impl From<rust_decimal::Decimal> for crate::ext::Q {
1104    /// Rounds to nearest; see [`q_from_rust_decimal`].
1105    fn from(v: rust_decimal::Decimal) -> Self {
1106        q_from_rust_decimal(v)
1107    }
1108}
1109
1110/// `rust_decimal::Decimal` as an [`Exact`](crate::exact::Exact) —
1111/// `Err(ExactError::Inexact)` when the reduced `(mantissa, 10^scale)` pair
1112/// does not already fit the width budget, rather than silently handing back
1113/// an `Exact` that has already rounded on the way in. `Exact`'s own
1114/// `add`/`sub`/`mul`/`div` report rounding the same way; this makes ingestion
1115/// consistent with them, instead of a `Rat` conversion (which does round or
1116/// saturate) wrapped in `Exact::new` after the fact.
1117///
1118/// ```
1119/// use rust_decimal::Decimal;
1120/// use the_q::{Exact, ExactError, Rat, exact_from_rust_decimal};
1121///
1122/// let d = Decimal::new(85, 2);
1123/// assert_eq!(exact_from_rust_decimal(d), Ok(Exact::new(Rat::new(17, 20).unwrap())));
1124/// // Its own magnitude alone already exceeds the budget.
1125/// assert_eq!(exact_from_rust_decimal(Decimal::MAX), Err(ExactError::Inexact));
1126/// ```
1127#[cfg(feature = "rust_decimal")]
1128#[cfg_attr(verus_keep_ghost, verifier::external)]
1129pub fn exact_from_rust_decimal(
1130    v: rust_decimal::Decimal,
1131) -> Result<crate::exact::Exact, crate::exact::ExactError> {
1132    match from_decimal128_exact(v.mantissa(), v.scale()) {
1133        Some(x) => Ok(crate::exact::Exact::new(x)),
1134        None => Err(crate::exact::ExactError::Inexact),
1135    }
1136}
1137
1138#[cfg(feature = "rust_decimal")]
1139#[cfg_attr(verus_keep_ghost, verifier::external)]
1140impl TryFrom<rust_decimal::Decimal> for crate::exact::Exact {
1141    type Error = crate::exact::ExactError;
1142
1143    /// See [`exact_from_rust_decimal`].
1144    fn try_from(v: rust_decimal::Decimal) -> Result<Self, Self::Error> {
1145        exact_from_rust_decimal(v)
1146    }
1147}
1148
1149// ---------------------------------------------------------------------------
1150// Shared tails for every external-library `Q`/`Exact` adapter below. Each
1151// adapter reduces its own representation to an `(n, d)` pair (`d > 0`) and
1152// calls one of these two, the same way each `Rat` adapter calls
1153// `from_ratio128_dir`/`from_ratio128_exact`.
1154// ---------------------------------------------------------------------------
1155
1156/// `n / d` (`d > 0`) as a `Q`, rounding to nearest and saturating by sign
1157/// once the magnitude alone leaves the budget — mirroring [`q_from_f64`].
1158#[cfg(any(
1159    feature = "fixed",
1160    feature = "num-rational",
1161    feature = "num-bigint",
1162    feature = "bigdecimal"
1163))]
1164#[cfg_attr(verus_keep_ghost, verifier::external)]
1165fn q_from_ratio128(n: i128, d: i128) -> crate::ext::Q {
1166    use crate::ext::Q;
1167    if n > RATIO_N_LIMIT || n < -RATIO_N_LIMIT {
1168        return if n < 0 { Q::NegSat } else { Q::PosSat };
1169    }
1170    if crate::q::magnitude_fits_exec(n, d) {
1171        Q::Number(
1172            from_ratio128_dir(n, d, crate::types::Dir::Nearest)
1173                .expect("the-q: n and d were just bound-checked above"),
1174        )
1175    } else if n > 0 {
1176        Q::PosSat
1177    } else {
1178        Q::NegSat
1179    }
1180}
1181
1182/// `n / d` as an [`Exact`](crate::exact::Exact), `Err(ExactError::Inexact)`
1183/// unless the reduced pair already fits the width budget.
1184#[cfg(any(
1185    feature = "fixed",
1186    feature = "num-rational",
1187    feature = "num-bigint",
1188    feature = "bigdecimal"
1189))]
1190#[cfg_attr(verus_keep_ghost, verifier::external)]
1191fn exact_from_ratio128(n: i128, d: i128) -> Result<crate::exact::Exact, crate::exact::ExactError> {
1192    match from_ratio128_exact(n, d) {
1193        Some(x) => Ok(crate::exact::Exact::new(x)),
1194        None => Err(crate::exact::ExactError::Inexact),
1195    }
1196}
1197
1198// ---------------------------------------------------------------------------
1199// The `fixed` boundary: any 128-bit-backed fixed-point type as `Rat`/`Q`/
1200// `Exact`. `fixed::types::I*F*` aliases whose bits total 128 (`I0F128` through
1201// `I127F1`, e.g. `I64F64`) all share `Bits = i128`, so one generic function
1202// covers the whole family: the value is `bits · 2^-FRAC_NBITS`, exactly the
1203// dyadic sibling of the decimal boundary's `mantissa · 10^-scale`.
1204// ---------------------------------------------------------------------------
1205
1206/// Any 128-bit-backed `fixed` type as a `Rat`, rounding in direction `dir`
1207/// once `bits / 2^FRAC_NBITS` no longer fits the width budget. `FRAC_NBITS >
1208/// 126` refuses outright: `2^127` and up does not fit an `i128` denominator
1209/// at all, so no `(n, d)` pair can name the value here in the first place —
1210/// this is a limit of representing the value as an exact `i128` ratio, not a
1211/// further approximation on top of one.
1212///
1213/// ```
1214/// use fixed::types::I64F64;
1215/// use the_q::{Dir, Rat, from_fixed_dir};
1216///
1217/// let v = I64F64::from_num(3.5);
1218/// assert_eq!(from_fixed_dir(v, Dir::Nearest), Some(Rat::new(7, 2).unwrap()));
1219/// ```
1220#[cfg(feature = "fixed")]
1221#[cfg_attr(verus_keep_ghost, verifier::external)]
1222pub fn from_fixed_dir<F>(v: F, dir: Dir) -> Option<Rat>
1223where
1224    F: fixed::traits::Fixed<Bits = i128>,
1225{
1226    if F::FRAC_NBITS > 126 {
1227        return None;
1228    }
1229    let d = crate::round::pow2_i128(F::FRAC_NBITS);
1230    from_ratio128_dir(v.to_bits(), d, dir)
1231}
1232
1233/// The exact value of a 128-bit-backed `fixed` type as a `Rat`, `None` unless
1234/// the reduced `(bits, 2^FRAC_NBITS)` pair already fits the width budget.
1235///
1236/// ```
1237/// use fixed::types::I64F64;
1238/// use the_q::{Rat, from_fixed_exact};
1239///
1240/// let v = I64F64::from_num(-1.25);
1241/// assert_eq!(from_fixed_exact(v), Some(Rat::new(-5, 4).unwrap()));
1242/// ```
1243#[cfg(feature = "fixed")]
1244#[cfg_attr(verus_keep_ghost, verifier::external)]
1245pub fn from_fixed_exact<F>(v: F) -> Option<Rat>
1246where
1247    F: fixed::traits::Fixed<Bits = i128>,
1248{
1249    if F::FRAC_NBITS > 126 {
1250        return None;
1251    }
1252    let d = crate::round::pow2_i128(F::FRAC_NBITS);
1253    from_ratio128_exact(v.to_bits(), d)
1254}
1255
1256/// Any 128-bit-backed `fixed` type as a `Q`, rounding to nearest and
1257/// saturating by sign past the budget. Past `FRAC_NBITS > 126` (where no
1258/// `i128` denominator can name the value, per [`from_fixed_dir`]) this falls
1259/// back to a lossy `to_num::<f64>()` conversion through [`q_from_f64`] rather
1260/// than guessing a saturation sign: that domain is `INT_NBITS < 2`, values
1261/// bounded near `[-1, 1)`, nowhere near the budget's ceiling, so a magnitude
1262/// this function cannot represent exactly is never actually saturated.
1263///
1264/// A named function, not `impl From<F> for Q`: a blanket `impl<F: Fixed<..>>
1265/// From<F> for Q` cannot coexist with the concrete `impl From<Decimal> for Q`
1266/// above (or any future concrete one) — the compiler cannot rule out some
1267/// downstream type implementing both `Fixed` and, say, being `Decimal`
1268/// itself, so it refuses the overlap outright (`E0119`). The same reasoning
1269/// rules out a blanket `TryFrom<F> for Exact` below.
1270///
1271/// ```
1272/// use fixed::types::I64F64;
1273/// use the_q::{Q, Rat, q_from_fixed};
1274///
1275/// let v = I64F64::from_num(12345);
1276/// assert_eq!(q_from_fixed(v), Q::Number(Rat::new(12345, 1).unwrap()));
1277/// // i64::MAX is past MAX_MAG, even though it fits I64F64's integer part.
1278/// assert_eq!(q_from_fixed(I64F64::from_num(i64::MAX)), Q::PosSat);
1279/// ```
1280#[cfg(feature = "fixed")]
1281#[cfg_attr(verus_keep_ghost, verifier::external)]
1282pub fn q_from_fixed<F>(v: F) -> crate::ext::Q
1283where
1284    F: fixed::traits::Fixed<Bits = i128>,
1285{
1286    if F::FRAC_NBITS > 126 {
1287        return q_from_f64(v.to_num::<f64>());
1288    }
1289    let d = crate::round::pow2_i128(F::FRAC_NBITS);
1290    q_from_ratio128(v.to_bits(), d)
1291}
1292
1293/// Any 128-bit-backed `fixed` type as an [`Exact`](crate::exact::Exact) —
1294/// `Err(ExactError::Inexact)` when the reduced pair does not already fit the
1295/// width budget, `FRAC_NBITS > 126` included.
1296///
1297/// ```
1298/// use fixed::types::I64F64;
1299/// use the_q::{Exact, ExactError, Rat, exact_from_fixed};
1300///
1301/// let v = I64F64::from_num(-1.25);
1302/// assert_eq!(exact_from_fixed(v), Ok(Exact::new(Rat::new(-5, 4).unwrap())));
1303/// assert_eq!(
1304///     exact_from_fixed(I64F64::from_num(i64::MAX)),
1305///     Err(ExactError::Inexact)
1306/// );
1307/// ```
1308#[cfg(feature = "fixed")]
1309#[cfg_attr(verus_keep_ghost, verifier::external)]
1310pub fn exact_from_fixed<F>(v: F) -> Result<crate::exact::Exact, crate::exact::ExactError>
1311where
1312    F: fixed::traits::Fixed<Bits = i128>,
1313{
1314    if F::FRAC_NBITS > 126 {
1315        return Err(crate::exact::ExactError::Inexact);
1316    }
1317    let d = crate::round::pow2_i128(F::FRAC_NBITS);
1318    exact_from_ratio128(v.to_bits(), d)
1319}
1320
1321// ---------------------------------------------------------------------------
1322// The `num-rational` boundary: `Ratio<i64>` (always exact, no truncation
1323// needed) and `BigRational` (arbitrary precision; needs a fallible `i128`
1324// extraction first).
1325// ---------------------------------------------------------------------------
1326
1327/// `num_rational::Ratio<i64>` as a `Rat`, rounding in direction `dir` once it
1328/// leaves the width budget. `Ratio`'s own invariant (a nonzero denominator)
1329/// is exactly what `from_ratio128_dir` needs to never return `None` on an
1330/// `i64` pair, so this is total.
1331///
1332/// ```
1333/// use num_rational::Ratio;
1334/// use the_q::{Dir, Rat, from_num_rational_i64_dir};
1335///
1336/// let v = Ratio::new(3i64, 4);
1337/// assert_eq!(from_num_rational_i64_dir(v, Dir::Nearest), Rat::new(3, 4).unwrap());
1338/// ```
1339#[cfg(feature = "num-rational")]
1340#[cfg_attr(verus_keep_ghost, verifier::external)]
1341pub fn from_num_rational_i64_dir(v: num_rational::Ratio<i64>, dir: Dir) -> Rat {
1342    from_ratio128_dir(i128::from(*v.numer()), i128::from(*v.denom()), dir)
1343        .expect("the-q: an i64 pair is always within from_ratio128_dir's domain")
1344}
1345
1346/// The exact value of a `num_rational::Ratio<i64>` as a `Rat`, `None` unless
1347/// the reduced pair already fits the width budget.
1348///
1349/// ```
1350/// use num_rational::Ratio;
1351/// use the_q::{Rat, from_num_rational_i64_exact};
1352///
1353/// let v = Ratio::new(5i64, 8);
1354/// assert_eq!(from_num_rational_i64_exact(v), Some(Rat::new(5, 8).unwrap()));
1355/// ```
1356#[cfg(feature = "num-rational")]
1357#[cfg_attr(verus_keep_ghost, verifier::external)]
1358pub fn from_num_rational_i64_exact(v: num_rational::Ratio<i64>) -> Option<Rat> {
1359    from_ratio128_exact(i128::from(*v.numer()), i128::from(*v.denom()))
1360}
1361
1362/// `num_rational::Ratio<i64>` as a `Q`, rounding to nearest and saturating by
1363/// sign past the budget.
1364///
1365/// ```
1366/// use num_rational::Ratio;
1367/// use the_q::{Q, Rat, q_from_num_rational_i64};
1368///
1369/// let v = Ratio::new(3i64, 4);
1370/// assert_eq!(q_from_num_rational_i64(v), Q::Number(Rat::new(3, 4).unwrap()));
1371/// ```
1372#[cfg(feature = "num-rational")]
1373#[cfg_attr(verus_keep_ghost, verifier::external)]
1374pub fn q_from_num_rational_i64(v: num_rational::Ratio<i64>) -> crate::ext::Q {
1375    q_from_ratio128(i128::from(*v.numer()), i128::from(*v.denom()))
1376}
1377
1378#[cfg(feature = "num-rational")]
1379#[cfg_attr(verus_keep_ghost, verifier::external)]
1380impl From<num_rational::Ratio<i64>> for crate::ext::Q {
1381    /// Rounds to nearest; see [`q_from_num_rational_i64`].
1382    fn from(v: num_rational::Ratio<i64>) -> Self {
1383        q_from_num_rational_i64(v)
1384    }
1385}
1386
1387/// `num_rational::Ratio<i64>` as an [`Exact`](crate::exact::Exact) —
1388/// `Err(ExactError::Inexact)` unless the reduced pair already fits the width
1389/// budget.
1390///
1391/// ```
1392/// use num_rational::Ratio;
1393/// use the_q::{Exact, Rat, exact_from_num_rational_i64};
1394///
1395/// let v = Ratio::new(5i64, 8);
1396/// assert_eq!(
1397///     exact_from_num_rational_i64(v),
1398///     Ok(Exact::new(Rat::new(5, 8).unwrap()))
1399/// );
1400/// ```
1401#[cfg(feature = "num-rational")]
1402#[cfg_attr(verus_keep_ghost, verifier::external)]
1403pub fn exact_from_num_rational_i64(
1404    v: num_rational::Ratio<i64>,
1405) -> Result<crate::exact::Exact, crate::exact::ExactError> {
1406    exact_from_ratio128(i128::from(*v.numer()), i128::from(*v.denom()))
1407}
1408
1409#[cfg(feature = "num-rational")]
1410#[cfg_attr(verus_keep_ghost, verifier::external)]
1411impl TryFrom<num_rational::Ratio<i64>> for crate::exact::Exact {
1412    type Error = crate::exact::ExactError;
1413
1414    /// See [`exact_from_num_rational_i64`].
1415    fn try_from(v: num_rational::Ratio<i64>) -> Result<Self, Self::Error> {
1416        exact_from_num_rational_i64(v)
1417    }
1418}
1419
1420/// `num_rational::BigRational` as a `Rat`, rounding in direction `dir`.
1421/// `None` when either the (already-reduced, by `Ratio`'s own invariant)
1422/// numerator or denominator does not fit an `i128` — arbitrary precision
1423/// means there is always a `BigRational` outside any fixed-width
1424/// representation.
1425///
1426/// ```
1427/// use num_bigint::BigInt;
1428/// use num_rational::BigRational;
1429/// use the_q::{Dir, Rat, from_big_rational_dir};
1430///
1431/// let v = BigRational::new(BigInt::from(22), BigInt::from(7));
1432/// assert_eq!(from_big_rational_dir(&v, Dir::Nearest), Some(Rat::new(22, 7).unwrap()));
1433/// ```
1434#[cfg(feature = "num-rational")]
1435#[cfg_attr(verus_keep_ghost, verifier::external)]
1436pub fn from_big_rational_dir(v: &num_rational::BigRational, dir: Dir) -> Option<Rat> {
1437    let n = i128::try_from(v.numer()).ok()?;
1438    let d = i128::try_from(v.denom()).ok()?;
1439    from_ratio128_dir(n, d, dir)
1440}
1441
1442/// The exact value of a `BigRational` as a `Rat`, `None` unless both the
1443/// reduced numerator and denominator fit an `i128` and that pair already
1444/// fits the width budget.
1445///
1446/// ```
1447/// use num_bigint::BigInt;
1448/// use num_rational::BigRational;
1449/// use the_q::{Rat, from_big_rational_exact};
1450///
1451/// let v = BigRational::new(BigInt::from(22), BigInt::from(7));
1452/// assert_eq!(from_big_rational_exact(&v), Some(Rat::new(22, 7).unwrap()));
1453/// ```
1454#[cfg(feature = "num-rational")]
1455#[cfg_attr(verus_keep_ghost, verifier::external)]
1456pub fn from_big_rational_exact(v: &num_rational::BigRational) -> Option<Rat> {
1457    let n = i128::try_from(v.numer()).ok()?;
1458    let d = i128::try_from(v.denom()).ok()?;
1459    from_ratio128_exact(n, d)
1460}
1461
1462/// `BigRational` as a `Q`, rounding to nearest. When the reduced numerator
1463/// and denominator both fit an `i128`, this saturates by sign past the
1464/// budget like every other adapter here; short of that it falls back to a
1465/// lossy `to_f64` conversion (through [`q_from_f64`]) rather than guessing a
1466/// saturation sign, because `Ratio` reduces its terms to lowest form and
1467/// nothing bounds *those* by the value's own magnitude — `(10^100 + 1) /
1468/// 10^100` is a value near `1`, not one anywhere near the budget's ceiling,
1469/// even though neither term fits `i128`.
1470///
1471/// ```
1472/// use num_bigint::BigInt;
1473/// use num_rational::BigRational;
1474/// use the_q::{Q, Rat, q_from_big_rational};
1475///
1476/// let v = BigRational::new(BigInt::from(22), BigInt::from(7));
1477/// assert_eq!(q_from_big_rational(&v), Q::Number(Rat::new(22, 7).unwrap()));
1478/// // Genuinely huge: saturates rather than falling back to f64.
1479/// let huge = BigRational::new(BigInt::from(10).pow(30), BigInt::from(1));
1480/// assert_eq!(q_from_big_rational(&huge), Q::PosSat);
1481/// ```
1482#[cfg(feature = "num-rational")]
1483#[cfg_attr(verus_keep_ghost, verifier::external)]
1484pub fn q_from_big_rational(v: &num_rational::BigRational) -> crate::ext::Q {
1485    match (i128::try_from(v.numer()), i128::try_from(v.denom())) {
1486        (Ok(n), Ok(d)) => q_from_ratio128(n, d),
1487        _ => q_from_f64(num_traits::ToPrimitive::to_f64(v).unwrap_or(f64::NAN)),
1488    }
1489}
1490
1491#[cfg(feature = "num-rational")]
1492#[cfg_attr(verus_keep_ghost, verifier::external)]
1493impl From<num_rational::BigRational> for crate::ext::Q {
1494    /// Rounds to nearest; see [`q_from_big_rational`].
1495    fn from(v: num_rational::BigRational) -> Self {
1496        q_from_big_rational(&v)
1497    }
1498}
1499
1500/// `BigRational` as an [`Exact`](crate::exact::Exact) —
1501/// `Err(ExactError::Inexact)` unless both the reduced numerator and
1502/// denominator fit an `i128` and that pair already fits the width budget.
1503///
1504/// ```
1505/// use num_bigint::BigInt;
1506/// use num_rational::BigRational;
1507/// use the_q::{Exact, Rat, exact_from_big_rational};
1508///
1509/// let v = BigRational::new(BigInt::from(3), BigInt::from(4));
1510/// assert_eq!(exact_from_big_rational(&v), Ok(Exact::new(Rat::new(3, 4).unwrap())));
1511/// ```
1512#[cfg(feature = "num-rational")]
1513#[cfg_attr(verus_keep_ghost, verifier::external)]
1514pub fn exact_from_big_rational(
1515    v: &num_rational::BigRational,
1516) -> Result<crate::exact::Exact, crate::exact::ExactError> {
1517    let n = i128::try_from(v.numer()).map_err(|_| crate::exact::ExactError::Inexact)?;
1518    let d = i128::try_from(v.denom()).map_err(|_| crate::exact::ExactError::Inexact)?;
1519    exact_from_ratio128(n, d)
1520}
1521
1522#[cfg(feature = "num-rational")]
1523#[cfg_attr(verus_keep_ghost, verifier::external)]
1524impl TryFrom<num_rational::BigRational> for crate::exact::Exact {
1525    type Error = crate::exact::ExactError;
1526
1527    /// See [`exact_from_big_rational`].
1528    fn try_from(v: num_rational::BigRational) -> Result<Self, Self::Error> {
1529        exact_from_big_rational(&v)
1530    }
1531}
1532
1533// ---------------------------------------------------------------------------
1534// The `num-bigint` boundary: a bare `BigInt`, i.e. `n / 1`. Unlike
1535// `BigRational`, an integer's own magnitude *is* the value — there is no
1536// reduced-terms ambiguity — so "does not fit `i128`" always means "the
1537// magnitude itself is too wide", and saturation by sign is exact, not a
1538// fallback.
1539// ---------------------------------------------------------------------------
1540
1541/// `num_bigint::BigInt` as a `Rat`, rounding in direction `dir`. `None` when
1542/// the integer itself does not fit an `i128`.
1543///
1544/// ```
1545/// use num_bigint::BigInt;
1546/// use the_q::{Dir, Rat, from_bigint_dir};
1547///
1548/// let v = BigInt::from(42);
1549/// assert_eq!(from_bigint_dir(&v, Dir::Nearest), Some(Rat::new(42, 1).unwrap()));
1550/// let huge = BigInt::from(10).pow(40);
1551/// assert_eq!(from_bigint_dir(&huge, Dir::Nearest), None);
1552/// ```
1553#[cfg(feature = "num-bigint")]
1554#[cfg_attr(verus_keep_ghost, verifier::external)]
1555pub fn from_bigint_dir(v: &num_bigint::BigInt, dir: Dir) -> Option<Rat> {
1556    let n = i128::try_from(v).ok()?;
1557    from_ratio128_dir(n, 1, dir)
1558}
1559
1560/// The exact value of a `BigInt` as a `Rat`, `None` unless it fits an `i128`
1561/// and is within `MAX_MAG`.
1562///
1563/// ```
1564/// use num_bigint::BigInt;
1565/// use the_q::{Rat, from_bigint_exact};
1566///
1567/// let v = BigInt::from(-42);
1568/// assert_eq!(from_bigint_exact(&v), Some(Rat::new(-42, 1).unwrap()));
1569/// ```
1570#[cfg(feature = "num-bigint")]
1571#[cfg_attr(verus_keep_ghost, verifier::external)]
1572pub fn from_bigint_exact(v: &num_bigint::BigInt) -> Option<Rat> {
1573    let n = i128::try_from(v).ok()?;
1574    from_ratio128_exact(n, 1)
1575}
1576
1577/// `BigInt` as a `Q`, saturating by sign once the integer itself no longer
1578/// fits an `i128` (equivalently, once it exceeds `MAX_MAG`, since past that
1579/// this is still exactly the "too big" case, not a resolution limit).
1580///
1581/// ```
1582/// use num_bigint::BigInt;
1583/// use the_q::{Q, Rat, q_from_bigint};
1584///
1585/// let v = BigInt::from(42);
1586/// assert_eq!(q_from_bigint(&v), Q::Number(Rat::new(42, 1).unwrap()));
1587/// let huge = BigInt::from(10).pow(40);
1588/// assert_eq!(q_from_bigint(&huge), Q::PosSat);
1589/// ```
1590#[cfg(feature = "num-bigint")]
1591#[cfg_attr(verus_keep_ghost, verifier::external)]
1592pub fn q_from_bigint(v: &num_bigint::BigInt) -> crate::ext::Q {
1593    use crate::ext::Q;
1594    match i128::try_from(v) {
1595        Ok(n) => q_from_ratio128(n, 1),
1596        Err(_) => {
1597            if v.sign() == num_bigint::Sign::Minus {
1598                Q::NegSat
1599            } else {
1600                Q::PosSat
1601            }
1602        }
1603    }
1604}
1605
1606#[cfg(feature = "num-bigint")]
1607#[cfg_attr(verus_keep_ghost, verifier::external)]
1608impl From<num_bigint::BigInt> for crate::ext::Q {
1609    /// See [`q_from_bigint`].
1610    fn from(v: num_bigint::BigInt) -> Self {
1611        q_from_bigint(&v)
1612    }
1613}
1614
1615/// `BigInt` as an [`Exact`](crate::exact::Exact) — `Err(ExactError::Inexact)`
1616/// unless it fits an `i128` and is within `MAX_MAG`.
1617///
1618/// ```
1619/// use num_bigint::BigInt;
1620/// use the_q::{Exact, ExactError, Rat, exact_from_bigint};
1621///
1622/// let v = BigInt::from(123456);
1623/// assert_eq!(exact_from_bigint(&v), Ok(Exact::new(Rat::new(123456, 1).unwrap())));
1624/// let huge = BigInt::from(10).pow(40);
1625/// assert_eq!(exact_from_bigint(&huge), Err(ExactError::Inexact));
1626/// ```
1627#[cfg(feature = "num-bigint")]
1628#[cfg_attr(verus_keep_ghost, verifier::external)]
1629pub fn exact_from_bigint(
1630    v: &num_bigint::BigInt,
1631) -> Result<crate::exact::Exact, crate::exact::ExactError> {
1632    let n = i128::try_from(v).map_err(|_| crate::exact::ExactError::Inexact)?;
1633    exact_from_ratio128(n, 1)
1634}
1635
1636#[cfg(feature = "num-bigint")]
1637#[cfg_attr(verus_keep_ghost, verifier::external)]
1638impl TryFrom<num_bigint::BigInt> for crate::exact::Exact {
1639    type Error = crate::exact::ExactError;
1640
1641    /// See [`exact_from_bigint`].
1642    fn try_from(v: num_bigint::BigInt) -> Result<Self, Self::Error> {
1643        exact_from_bigint(&v)
1644    }
1645}
1646
1647// ---------------------------------------------------------------------------
1648// The `bigdecimal` boundary: `BigDecimal` is `digits · 10^-scale` with an
1649// arbitrary-precision `digits` and an `i64` `scale` that (unlike
1650// `rust_decimal::Decimal`'s) may be negative, meaning `digits · 10^|scale|`.
1651// Negative-scale values are normalised to that product (still exact, still
1652// arbitrary precision) before the same fallible `i128` extraction the other
1653// big-number boundaries use.
1654// ---------------------------------------------------------------------------
1655
1656/// `(n, 10^scale)` for a `BigDecimal`, both as `i128`, when representable at
1657/// all: `None` when the (possibly negative-scale-normalised) digits do not
1658/// fit an `i128`, or the resulting scale exceeds [`MAX_DECIMAL_SCALE`]. A
1659/// negative scale beyond -40 is refused before doing any arbitrary-precision
1660/// multiplication for it, since `digits · 10^40` is already far past any
1661/// `i128`.
1662#[cfg(feature = "bigdecimal")]
1663#[cfg_attr(verus_keep_ghost, verifier::external)]
1664fn bigdecimal_ratio_i128(v: &bigdecimal::BigDecimal) -> Option<(i128, i128)> {
1665    let (digits, scale) = v.as_bigint_and_scale();
1666    if scale >= 0 {
1667        if scale > i64::from(MAX_DECIMAL_SCALE) {
1668            return None;
1669        }
1670        let n = i128::try_from(digits.as_ref()).ok()?;
1671        let d = pow10_i128(u32::try_from(scale).ok()?);
1672        Some((n, d))
1673    } else {
1674        let shift = scale.checked_neg()?;
1675        if shift > 40 {
1676            return None;
1677        }
1678        let factor = num_bigint::BigInt::from(10u8).pow(u32::try_from(shift).ok()?);
1679        let n_big = digits.into_owned() * factor;
1680        let n = i128::try_from(&n_big).ok()?;
1681        Some((n, 1))
1682    }
1683}
1684
1685/// `bigdecimal::BigDecimal` as a `Rat`, rounding in direction `dir`. `None`
1686/// outside `bigdecimal_ratio_i128`'s domain.
1687///
1688/// ```
1689/// use bigdecimal::BigDecimal;
1690/// use std::str::FromStr;
1691/// use the_q::{Dir, Rat, from_bigdecimal_dir};
1692///
1693/// let v = BigDecimal::from_str("0.85").unwrap();
1694/// assert_eq!(from_bigdecimal_dir(&v, Dir::Nearest), Some(Rat::new(17, 20).unwrap()));
1695/// ```
1696#[cfg(feature = "bigdecimal")]
1697#[cfg_attr(verus_keep_ghost, verifier::external)]
1698pub fn from_bigdecimal_dir(v: &bigdecimal::BigDecimal, dir: Dir) -> Option<Rat> {
1699    let (n, d) = bigdecimal_ratio_i128(v)?;
1700    from_ratio128_dir(n, d, dir)
1701}
1702
1703/// The exact value of a `BigDecimal` as a `Rat`, `None` unless it is inside
1704/// `bigdecimal_ratio_i128`'s domain and that pair already fits the width
1705/// budget.
1706///
1707/// ```
1708/// use bigdecimal::BigDecimal;
1709/// use std::str::FromStr;
1710/// use the_q::{Rat, from_bigdecimal_exact};
1711///
1712/// let v = BigDecimal::from_str("-3.14").unwrap();
1713/// assert_eq!(from_bigdecimal_exact(&v), Some(Rat::new(-157, 50).unwrap()));
1714/// ```
1715#[cfg(feature = "bigdecimal")]
1716#[cfg_attr(verus_keep_ghost, verifier::external)]
1717pub fn from_bigdecimal_exact(v: &bigdecimal::BigDecimal) -> Option<Rat> {
1718    let (n, d) = bigdecimal_ratio_i128(v)?;
1719    from_ratio128_exact(n, d)
1720}
1721
1722/// `BigDecimal` as a `Q`, rounding to nearest. Falls back to a lossy `to_f64`
1723/// conversion (through [`q_from_f64`]) outside `bigdecimal_ratio_i128`'s
1724/// domain, the same way [`q_from_big_rational`] does and for the same
1725/// reason: arbitrarily many digits do not imply an arbitrarily large value.
1726///
1727/// ```
1728/// use bigdecimal::BigDecimal;
1729/// use std::str::FromStr;
1730/// use the_q::{Q, Rat, q_from_bigdecimal};
1731///
1732/// let v = BigDecimal::from_str("0.85").unwrap();
1733/// assert_eq!(q_from_bigdecimal(&v), Q::Number(Rat::new(17, 20).unwrap()));
1734/// let huge = BigDecimal::from_str(&format!("1{}", "0".repeat(30))).unwrap();
1735/// assert_eq!(q_from_bigdecimal(&huge), Q::PosSat);
1736/// ```
1737#[cfg(feature = "bigdecimal")]
1738#[cfg_attr(verus_keep_ghost, verifier::external)]
1739pub fn q_from_bigdecimal(v: &bigdecimal::BigDecimal) -> crate::ext::Q {
1740    match bigdecimal_ratio_i128(v) {
1741        Some((n, d)) => q_from_ratio128(n, d),
1742        None => q_from_f64(num_traits::ToPrimitive::to_f64(v).unwrap_or(f64::NAN)),
1743    }
1744}
1745
1746#[cfg(feature = "bigdecimal")]
1747#[cfg_attr(verus_keep_ghost, verifier::external)]
1748impl From<bigdecimal::BigDecimal> for crate::ext::Q {
1749    /// Rounds to nearest; see [`q_from_bigdecimal`].
1750    fn from(v: bigdecimal::BigDecimal) -> Self {
1751        q_from_bigdecimal(&v)
1752    }
1753}
1754
1755/// `BigDecimal` as an [`Exact`](crate::exact::Exact) —
1756/// `Err(ExactError::Inexact)` outside `bigdecimal_ratio_i128`'s domain or
1757/// when that pair does not already fit the width budget.
1758///
1759/// ```
1760/// use bigdecimal::BigDecimal;
1761/// use std::str::FromStr;
1762/// use the_q::{Exact, Rat, exact_from_bigdecimal};
1763///
1764/// let v = BigDecimal::from_str("2.71828").unwrap();
1765/// assert_eq!(
1766///     exact_from_bigdecimal(&v),
1767///     Ok(Exact::new(Rat::new(271828, 100000).unwrap()))
1768/// );
1769/// ```
1770#[cfg(feature = "bigdecimal")]
1771#[cfg_attr(verus_keep_ghost, verifier::external)]
1772pub fn exact_from_bigdecimal(
1773    v: &bigdecimal::BigDecimal,
1774) -> Result<crate::exact::Exact, crate::exact::ExactError> {
1775    let (n, d) = bigdecimal_ratio_i128(v).ok_or(crate::exact::ExactError::Inexact)?;
1776    exact_from_ratio128(n, d)
1777}
1778
1779#[cfg(feature = "bigdecimal")]
1780#[cfg_attr(verus_keep_ghost, verifier::external)]
1781impl TryFrom<bigdecimal::BigDecimal> for crate::exact::Exact {
1782    type Error = crate::exact::ExactError;
1783
1784    /// See [`exact_from_bigdecimal`].
1785    fn try_from(v: bigdecimal::BigDecimal) -> Result<Self, Self::Error> {
1786        exact_from_bigdecimal(&v)
1787    }
1788}