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}