dashu_float/convert.rs
1use core::{
2 convert::{TryFrom, TryInto},
3 num::FpCategory,
4};
5
6use dashu_base::{
7 AbsOrd, Approximation::*, BitTest, ConversionError, DivExact, DivRemEuclid, EstimatedLog2,
8 FloatEncoding, Sign, Signed,
9};
10use dashu_int::{IBig, UBig, Word};
11
12use crate::{
13 error::{assert_finite, panic_unlimited_precision, FpError, FpResult},
14 fbig::FBig,
15 math::cache::{reborrow_cache, ConstCache},
16 repr::{Context, Repr},
17 round::{
18 mode::{HalfAway, HalfEven, Zero},
19 Round, Rounded, Rounding,
20 Rounding::*,
21 },
22 utils::{factor_base, ilog_exact, shl_digits, shl_digits_in_place, shr_digits},
23};
24
25impl<R: Round> Context<R> {
26 /// Convert an [IBig] instance to a [FBig] instance with precision
27 /// and rounding given by the context.
28 ///
29 /// # Examples
30 ///
31 /// ```
32 /// # use core::str::FromStr;
33 /// # use dashu_base::ParseError;
34 /// # use dashu_float::DBig;
35 /// use dashu_base::Approximation::*;
36 /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
37 ///
38 /// let context = Context::<HalfAway>::new(2);
39 /// assert_eq!(context.convert_int::<10>((-12).into()), Exact(DBig::from_str("-12")?));
40 /// assert_eq!(
41 /// context.convert_int::<10>(5678.into()),
42 /// Inexact(DBig::from_str("5.7e3")?, AddOne)
43 /// );
44 /// # Ok::<(), ParseError>(())
45 /// ```
46 pub fn convert_int<const B: Word>(&self, n: IBig) -> Rounded<FBig<R, B>> {
47 let repr = Repr::<B>::new(n, 0);
48 self.repr_round(repr).map(|v| FBig::new(v, *self))
49 }
50}
51
52macro_rules! impl_from_float_for_fbig {
53 ($t:ty) => {
54 impl TryFrom<$t> for Repr<2> {
55 type Error = ConversionError;
56
57 fn try_from(f: $t) -> Result<Self, Self::Error> {
58 match f.decode() {
59 Ok((man, exp)) => Ok(if man == 0 && f.is_sign_negative() {
60 Self::neg_zero()
61 } else {
62 Repr::new(man.into(), exp as _)
63 }),
64 Err(FpCategory::Infinite) => match f.sign() {
65 Sign::Positive => Ok(Self::infinity()),
66 Sign::Negative => Ok(Self::neg_infinity()),
67 },
68 _ => Err(ConversionError::OutOfBounds), // NaN
69 }
70 }
71 }
72
73 impl<R: Round> TryFrom<$t> for FBig<R, 2> {
74 type Error = ConversionError;
75
76 fn try_from(f: $t) -> Result<Self, Self::Error> {
77 match f.decode() {
78 Ok((man, exp)) => {
79 // preserve the sign of a signed zero (-0.0 -> Repr::neg_zero())
80 let repr = if man == 0 && f.is_sign_negative() {
81 Repr::neg_zero()
82 } else {
83 Repr::new(man.into(), exp as _)
84 };
85
86 // The precision is inferenced from the mantissa, because the mantissa of
87 // normal float is always normalized. This will produce correct precision
88 // for subnormal floats
89 let bits = man.unsigned_abs().bit_len();
90 let context = Context::new(bits);
91 Ok(Self::new(repr, context))
92 }
93 Err(FpCategory::Infinite) => match f.sign() {
94 Sign::Positive => Ok(Self::INFINITY),
95 Sign::Negative => Ok(Self::NEG_INFINITY),
96 },
97 _ => Err(ConversionError::OutOfBounds), // NaN
98 }
99 }
100 }
101 };
102}
103
104impl_from_float_for_fbig!(f32);
105impl_from_float_for_fbig!(f64);
106
107impl<R: Round, const B: Word> FBig<R, B> {
108 /// Convert the float number to base 10 (with decimal exponents) rounding to even
109 /// and tying away from zero.
110 ///
111 /// It's equivalent to `self.with_rounding::<HalfAway>().with_base::<10>()`.
112 /// The output is directly of type [DBig][crate::DBig].
113 ///
114 /// See [with_base()][Self::with_base] for the precision behavior.
115 ///
116 /// # Examples
117 ///
118 /// ```
119 /// # use core::str::FromStr;
120 /// # use dashu_base::ParseError;
121 /// # use dashu_float::{FBig, DBig};
122 /// use dashu_base::Approximation::*;
123 /// use dashu_float::round::Rounding::*;
124 ///
125 /// type Real = FBig;
126 ///
127 /// assert_eq!(
128 /// Real::from_str("0x1234")?.to_decimal(),
129 /// Exact(DBig::from_str("4660")?)
130 /// );
131 /// assert_eq!(
132 /// Real::from_str("0x12.34")?.to_decimal(),
133 /// Inexact(DBig::from_str("18.20")?, NoOp)
134 /// );
135 /// assert_eq!(
136 /// Real::from_str("0x1.234p-4")?.to_decimal(),
137 /// Inexact(DBig::from_str("0.07111")?, AddOne)
138 /// );
139 /// # Ok::<(), ParseError>(())
140 /// ```
141 ///
142 /// # Panics
143 ///
144 /// Panics if the associated context has unlimited precision and the conversion
145 /// cannot be performed losslessly.
146 #[inline]
147 pub fn to_decimal(&self) -> Rounded<FBig<HalfAway, 10>> {
148 self.clone().with_rounding().with_base::<10>()
149 }
150
151 /// Convert the float number to base 2 (with binary exponents) rounding towards zero.
152 ///
153 /// It's equivalent to `self.with_rounding::<Zero>().with_base::<2>()`.
154 ///
155 /// See [with_base()][Self::with_base] for the precision and rounding behavior.
156 ///
157 /// # Examples
158 ///
159 /// ```
160 /// # use core::str::FromStr;
161 /// # use dashu_base::ParseError;
162 /// # use dashu_float::{FBig, DBig};
163 /// use dashu_base::Approximation::*;
164 /// use dashu_float::round::{mode::HalfAway, Rounding::*};
165 ///
166 /// type Real = FBig;
167 ///
168 /// assert_eq!(
169 /// DBig::from_str("1234")?.to_binary(),
170 /// Exact(Real::from_str("0x4d2")?)
171 /// );
172 /// assert_eq!(
173 /// DBig::from_str("12.34")?.to_binary(),
174 /// Inexact(Real::from_str("0xc.57")?, NoOp)
175 /// );
176 /// assert_eq!(
177 /// DBig::from_str("1.234e-1")?.to_binary(),
178 /// Inexact(Real::from_str("0x1.f97p-4")?, NoOp)
179 /// );
180 /// # Ok::<(), ParseError>(())
181 /// ```
182 ///
183 /// # Panics
184 ///
185 /// Panics if the associated context has unlimited precision and the conversion
186 /// cannot be performed losslessly.
187 #[inline]
188 pub fn to_binary(&self) -> Rounded<FBig<Zero, 2>> {
189 self.clone().with_rounding().with_base::<2>()
190 }
191
192 /// Explicitly change the precision of the float number.
193 ///
194 /// If the given precision is less than the current value in the context,
195 /// it will be rounded with the rounding mode specified by the generic parameter.
196 ///
197 /// # Examples
198 ///
199 /// ```rust
200 /// # use core::str::FromStr;
201 /// # use dashu_base::ParseError;
202 /// # use dashu_float::{FBig, DBig};
203 /// use dashu_base::Approximation::*;
204 /// use dashu_float::round::{mode::HalfAway, Rounding::*};
205 ///
206 /// let a = DBig::from_str("2.345")?;
207 /// assert_eq!(a.precision(), 4);
208 /// assert_eq!(
209 /// a.clone().with_precision(3),
210 /// Inexact(DBig::from_str("2.35")?, AddOne)
211 /// );
212 /// assert_eq!(
213 /// a.clone().with_precision(5),
214 /// Exact(DBig::from_str("2.345")?)
215 /// );
216 /// # Ok::<(), ParseError>(())
217 /// ```
218 #[inline]
219 pub fn with_precision(self, precision: usize) -> Rounded<Self> {
220 let new_context = Context::new(precision);
221
222 // round if the source is unlimited or the precision shrinks; infinities stay as-is
223 let repr = if self.repr.is_infinite() {
224 Exact(self.repr)
225 } else if !self.context.is_limited() || self.context.precision > precision {
226 new_context.repr_round(self.repr)
227 } else {
228 Exact(self.repr)
229 };
230
231 repr.map(|v| Self::new(v, new_context))
232 }
233
234 /// Explicitly change the rounding mode of the number.
235 ///
236 /// This operation doesn't modify the underlying representation, it only changes
237 /// the rounding mode in the context.
238 ///
239 /// # Examples
240 ///
241 /// ```rust
242 /// # use core::str::FromStr;
243 /// # use dashu_base::ParseError;
244 /// # use dashu_float::{FBig, DBig};
245 /// use dashu_base::Approximation::*;
246 /// use dashu_float::round::{mode::{HalfAway, Zero}, Rounding::*};
247 ///
248 /// type DBigHalfAway = DBig;
249 /// type DBigZero = FBig::<Zero, 10>;
250 ///
251 /// let a = DBigHalfAway::from_str("2.345")?;
252 /// let b = DBigZero::from_str("2.345")?;
253 /// assert_eq!(a.with_rounding::<Zero>(), b);
254 /// # Ok::<(), ParseError>(())
255 /// ```
256 #[inline]
257 pub fn with_rounding<NewR: Round>(self) -> FBig<NewR, B> {
258 FBig {
259 repr: self.repr,
260 context: Context::new(self.context.precision),
261 }
262 }
263
264 /// Explicitly change the base of the float number.
265 ///
266 /// This function internally calls [with_base_and_precision][Self::with_base_and_precision].
267 /// The precision of the result number will be calculated in such a way that the new
268 /// limit of the significand is less than or equal to before. That is, the new precision
269 /// will be the max integer such that
270 ///
271 /// `NewB ^ new_precision <= B ^ old_precision`
272 ///
273 /// If any rounding happens during the conversion, it follows the rounding mode specified
274 /// by the generic parameter.
275 ///
276 /// # Examples
277 ///
278 /// ```rust
279 /// # use core::str::FromStr;
280 /// # use dashu_base::ParseError;
281 /// # use dashu_float::{FBig, DBig};
282 /// use dashu_base::Approximation::*;
283 /// use dashu_float::round::{mode::Zero, Rounding::*};
284 ///
285 /// type FBin = FBig;
286 /// type FDec = FBig<Zero, 10>;
287 /// type FHex = FBig<Zero, 16>;
288 ///
289 /// let a = FBin::from_str("0x1.234")?; // 0x1234 * 2^-12
290 /// assert_eq!(
291 /// a.clone().with_base::<10>(),
292 /// // 1.1376953125 rounded towards zero
293 /// Inexact(FDec::from_str("1.137")?, NoOp)
294 /// );
295 /// assert_eq!(
296 /// a.clone().with_base::<16>(),
297 /// // conversion is exact when the new base is a power of the old base
298 /// Exact(FHex::from_str("1.234")?)
299 /// );
300 /// # Ok::<(), ParseError>(())
301 /// ```
302 ///
303 /// # Panics
304 ///
305 /// Panics if the associated context has unlimited precision and the conversion
306 /// cannot be performed losslessly.
307 #[inline]
308 #[allow(non_upper_case_globals)]
309 pub fn with_base<const NewB: Word>(self) -> Rounded<FBig<R, NewB>> {
310 // if self.context.precision is zero, then precision is also zero
311 let precision =
312 Repr::<B>::BASE.pow(self.context.precision).log2_bounds().0 / NewB.log2_bounds().1;
313 self.with_base_and_precision(precision as usize)
314 }
315
316 /// Explicitly change the base of the float number with given precision (under the new base).
317 ///
318 /// Infinities are mapped to infinities inexactly, the error will be [Rounding::NoOp].
319 ///
320 /// Conversion for float numbers with unlimited precision is only allowed in following cases:
321 /// - The number is infinite
322 /// - The new base NewB is a power of B
323 /// - B is a power of the new base NewB
324 ///
325 /// # Examples
326 ///
327 /// ```rust
328 /// # use core::str::FromStr;
329 /// # use dashu_base::ParseError;
330 /// # use dashu_float::{FBig, DBig};
331 /// use dashu_base::Approximation::*;
332 /// use dashu_float::round::{mode::Zero, Rounding::*};
333 ///
334 /// type FBin = FBig;
335 /// type FDec = FBig<Zero, 10>;
336 /// type FHex = FBig<Zero, 16>;
337 ///
338 /// let a = FBin::from_str("0x1.234")?; // 0x1234 * 2^-12
339 /// assert_eq!(
340 /// a.clone().with_base_and_precision::<10>(8),
341 /// // 1.1376953125 rounded towards zero
342 /// Inexact(FDec::from_str("1.1376953")?, NoOp)
343 /// );
344 /// assert_eq!(
345 /// a.clone().with_base_and_precision::<16>(8),
346 /// // conversion can be exact when the new base is a power of the old base
347 /// Exact(FHex::from_str("1.234")?)
348 /// );
349 /// assert_eq!(
350 /// a.clone().with_base_and_precision::<16>(2),
351 /// // but the conversion is still inexact if the target precision is smaller
352 /// Inexact(FHex::from_str("1.2")?, NoOp)
353 /// );
354 /// # Ok::<(), ParseError>(())
355 /// ```
356 ///
357 /// # Panics
358 ///
359 /// Panics if the associated context has unlimited precision and the conversion
360 /// cannot be performed losslessly.
361 #[allow(non_upper_case_globals)]
362 #[inline]
363 pub fn with_base_and_precision<const NewB: Word>(
364 self,
365 precision: usize,
366 ) -> Rounded<FBig<R, NewB>> {
367 let context = Context::<R>::new(precision);
368 context
369 .convert_base(self.repr, None)
370 .map(|repr| FBig::new(repr, context))
371 }
372
373 /// Convert the float number to integer, rounding with the mode attached to the type.
374 ///
375 /// Unlike [`FBig::round`], which always rounds half away from zero regardless of
376 /// the type's rounding mode, this method rounds the fractional part with the type's
377 /// own mode. The result is exact if and only if the float is an integer itself.
378 ///
379 /// # Warning
380 ///
381 /// If the float number has a very large exponent, it will be evaluated and result
382 /// in allocating an huge integer and it might eat up all your memory.
383 ///
384 /// To get a rough idea of how big the number is, it's recommended to use [EstimatedLog2].
385 ///
386 /// # Examples
387 ///
388 /// ```
389 /// # use core::str::FromStr;
390 /// # use dashu_base::ParseError;
391 /// # use dashu_float::{FBig, DBig};
392 /// use dashu_base::Approximation::*;
393 /// use dashu_float::round::mode::HalfEven;
394 /// use dashu_float::round::Rounding::*;
395 ///
396 /// assert_eq!(
397 /// DBig::from_str("1234")?.to_int(),
398 /// Exact(1234.into())
399 /// );
400 /// assert_eq!(
401 /// DBig::from_str("1.234e6")?.to_int(),
402 /// Exact(1234000.into())
403 /// );
404 /// assert_eq!(
405 /// DBig::from_str("1.234")?.to_int(),
406 /// Inexact(1.into(), NoOp)
407 /// );
408 ///
409 /// // the mode attached to the type is honored: on a HalfEven type, the tie 2.5
410 /// // rounds to its even neighbor — whereas round() would return 3
411 /// let b = FBig::<HalfEven, 10>::from_str("2.5")?;
412 /// assert_eq!(b.to_int(), Inexact(2.into(), NoOp));
413 /// assert_eq!(b.round(), FBig::<HalfEven, 10>::from_str("3")?);
414 /// # Ok::<(), ParseError>(())
415 /// ```
416 ///
417 /// # Panics
418 ///
419 /// Panics if the number is infinte
420 pub fn to_int(&self) -> Rounded<IBig> {
421 assert_finite(&self.repr);
422
423 // shortcut when the number is already an integer
424 if self.repr.exponent >= 0 {
425 return Exact(shl_digits::<B>(&self.repr.significand, self.repr.exponent as usize));
426 }
427
428 let (hi, lo, precision) = self.split_at_point_internal();
429 let adjust = R::round_fract::<B>(&hi, lo, precision);
430 Inexact(hi + adjust, adjust)
431 }
432
433 /// Convert the float number to [f32] with the rounding mode associated with the type.
434 ///
435 /// Note that the conversion is inexact even if the number is infinite.
436 ///
437 /// # Examples
438 ///
439 /// ```
440 /// # use core::str::FromStr;
441 /// # use dashu_base::ParseError;
442 /// # use dashu_float::DBig;
443 /// assert_eq!(DBig::from_str("1.234")?.to_f32().value(), 1.234);
444 /// assert_eq!(DBig::INFINITY.to_f32().value(), f32::INFINITY);
445 /// # Ok::<(), ParseError>(())
446 /// ```
447 #[inline]
448 pub fn to_f32(&self) -> Rounded<f32> {
449 match Context::<R>::convert_to_f32(self.repr.clone()) {
450 Ok(rounded) => rounded,
451 Err(err) => f32_directed_endpoint::<R>(err),
452 }
453 }
454
455 /// Convert the float number to [f64] with the rounding mode associated with the type.
456 ///
457 /// Note that the conversion is inexact even if the number is infinite.
458 ///
459 /// # Examples
460 ///
461 /// ```
462 /// # use core::str::FromStr;
463 /// # use dashu_base::ParseError;
464 /// # use dashu_float::DBig;
465 /// assert_eq!(DBig::from_str("1.234")?.to_f64().value(), 1.234);
466 /// assert_eq!(DBig::INFINITY.to_f64().value(), f64::INFINITY);
467 /// # Ok::<(), ParseError>(())
468 /// ```
469 #[inline]
470 pub fn to_f64(&self) -> Rounded<f64> {
471 match Context::<R>::convert_to_f64(self.repr.clone()) {
472 Ok(rounded) => rounded,
473 Err(err) => f64_directed_endpoint::<R>(err),
474 }
475 }
476}
477
478/// `isize` exponent arithmetic overflowed during base conversion: the value's magnitude
479/// falls outside the representable exponent range, so the result is ±infinity (`large`) or
480/// ±0 (`!large`). Mirrors the convention `convert_base` already uses in its division path,
481/// keeping the conversion overflow-safe (no panic) at the value level.
482#[allow(non_upper_case_globals)]
483fn converted_overflow_repr<const NewB: Word>(large: bool, sign: Sign) -> Rounded<Repr<NewB>> {
484 Inexact(
485 if large {
486 Repr::<NewB>::infinity_with_sign(sign)
487 } else {
488 Repr::<NewB>::zero_with_sign(sign)
489 },
490 Rounding::NoOp,
491 )
492}
493
494/// Number of significant bits a binary float format keeps for a value whose most-significant bit
495/// sits at position `msb`: `max_bits` across the normal range, but fewer for subnormals, whose
496/// spacing is fixed at `2^subnormal_exp` (e.g. `2^-1074` for f64, `2^-149` for f32). Rounding the
497/// source straight to this width lets the bit-encoding step avoid a second rounding, which would
498/// otherwise double-round subnormals.
499fn significand_bits(v: &Repr<2>, max_bits: usize, subnormal_exp: isize) -> usize {
500 if v.significand.is_zero() {
501 return max_bits;
502 }
503 let msb = v.exponent + v.digits() as isize - 1;
504 (msb - subnormal_exp + 1).clamp(1, max_bits as isize) as usize
505}
506
507/// Convert `repr` to base 2 and reduce to a `width`-bit round-to-odd value: the top `width` bits
508/// with the lowest kept bit forced to 1 whenever the conversion was inexact. Rounding this to any
509/// width up to `width - 2` reproduces the correctly-rounded value for every rounding mode, so the
510/// two-step "convert, then round to the final width" cannot double-round.
511///
512/// The conversion is first evaluated at `width + GUARD` bits (work precision `2·(width + GUARD)`)
513/// and only then round-to-odd'd down to `width`. The extra guard is required for wide source
514/// significands (e.g. a decimal `FBig` with hundreds of bits): the base-conversion logarithm is
515/// *near-correct* — its ln/exp series carry a few-ulp error at the work precision — so converting
516/// straight at `width` can land a value whose true result sits within ~`2^{-2·width}` of a
517/// `width`-bit midpoint on the wrong side of that midpoint, and the subsequent round picks the
518/// wrong neighbor (a decimal→f32 subnormal off by 1 ULP). The guard pushes that residual error
519/// well below one `width`-bit ulp.
520#[allow(non_upper_case_globals)]
521fn convert_base_odd<const B: Word>(repr: Repr<B>, width: usize) -> Repr<2> {
522 const GUARD: usize = 24;
523 match Context::<Zero>::new(width + GUARD).convert_base::<B, 2>(repr, None) {
524 Exact(v) => v,
525 Inexact(v, _) if v.significand.is_zero() => v,
526 Inexact(v, _) => {
527 let digits = v.digits();
528 let (sign, mut mag) = v.significand.into_parts();
529 // collapse onto exactly `width` significant bits (drop or pad), then force the lowest
530 // kept bit to 1 to mark the inexact conversion (round-to-odd).
531 let exp = if digits >= width {
532 let shift = digits - width;
533 if shift > 0 {
534 mag >>= shift;
535 }
536 v.exponent + shift as isize
537 } else {
538 let shift = width - digits;
539 mag <<= shift;
540 v.exponent - shift as isize
541 };
542 mag.set_bit(0);
543 Repr::new(IBig::from_parts(sign, mag), exp)
544 }
545 }
546}
547
548impl<R: Round> Context<R> {
549 // Convert `repr` (base B) to the nearest f64 under this context's rounding mode. A generous
550 // round-to-odd base conversion is rounded once to the target's precision at its own magnitude
551 // (fewer than 53 bits for subnormals), so `into_f64_internal` re-rounds nothing — which would
552 // otherwise double-round subnormals. Handles a source significand of any size.
553 //
554 // Returns `Err(FpError::Overflow/Underflow)` when the value is outside the finite f64 range;
555 // the caller decides whether to saturate that to the directed endpoint (`to_f64`) or report it
556 // as a conversion error (`TryFrom`). This makes `into_f64_internal` the single source of truth
557 // for "is this value in range", shared by both APIs.
558 fn convert_to_f64<const B: Word>(repr: Repr<B>) -> FpResult<f64> {
559 if repr.is_infinite() {
560 return Ok(Inexact(repr.sign() * f64::INFINITY, Rounding::NoOp));
561 }
562 // Underflow short-circuit on the *source* value (before base conversion). For a value far
563 // below half the smallest subnormal (`|x| < 2^-1075`), the result is `±0` under nearest and
564 // the smallest subnormal under outward modes — independent of base conversion. Checking the
565 // source matters because converting a catastrophically tiny value (e.g. a wide-significand
566 // decimal at a hugely negative exponent) drives the conversion's internal `exp` to underflow,
567 // yielding an `odd` with a wildly wrong (too large) magnitude that `encode` then fails to
568 // flag. `log2_bounds` on the source is exact (derived from the significand bit length), so
569 // it sees the true magnitude; `ub < -1075` certifies `|x| < 2^-1075 = ½·MIN_SUBNORMAL`.
570 // (Zero is excluded — it is exactly `0`, not an underflow.)
571 if !repr.significand().is_zero() && repr.log2_bounds().1 < -1075.0 {
572 return Err(FpError::Underflow(repr.sign()));
573 }
574 let odd = convert_base_odd::<B>(repr, 60);
575 // Unified range check (shared by `to_f64` and `TryFrom`): a value beyond f64::MAX is out
576 // of range regardless of rounding mode (the mode only picks the saturation endpoint).
577 // Checked on the base-2 `odd`, BEFORE the significand rounding that could collapse a
578 // beyond-MAX value onto MAX (which `encode` would then miss, breaking mode-independence).
579 // `log2_bounds` fast-rejects the common case; the exact `abs_cmp` runs only near MAX.
580 let (lb, ub) = odd.log2_bounds();
581 if lb > 1024.0
582 || (ub >= 1024.0
583 && odd
584 .abs_cmp(&(UBig::from(0x1FFFFFFFFFFFFFu64) << 971))
585 .is_gt())
586 {
587 return Err(FpError::Overflow(odd.sign()));
588 }
589 let bits = significand_bits(&odd, 53, -1074);
590 // The base conversion's rounding flag must propagate: `1e20 → f64` is inexact at the
591 // round-to-odd step even though the already-rounded significand then `encode`s exactly.
592 // This mirrors `Approximation::and_then` — an inexact input lifts an exact `encode` to
593 // inexact with the input's flag, while an inexact `encode` keeps its own flag.
594 let rounded = Context::<R>::new(bits).repr_round(odd);
595 match rounded {
596 Exact(v) => v.into_f64_internal(),
597 Inexact(v, e) => match v.into_f64_internal()? {
598 Exact(f) => Ok(Inexact(f, e)),
599 Inexact(f, e2) => Ok(Inexact(f, e2)),
600 },
601 }
602 }
603
604 // [convert_to_f64] for f32.
605 fn convert_to_f32<const B: Word>(repr: Repr<B>) -> FpResult<f32> {
606 if repr.is_infinite() {
607 return Ok(Inexact(repr.sign() * f32::INFINITY, Rounding::NoOp));
608 }
609 // See `convert_to_f64`: underflow short-circuit on the source value. f32's smallest
610 // subnormal is `2^-149`, so `|x| < 2^-150 = ½·MIN_SUBNORMAL` rounds to `±0` (nearest) or
611 // the smallest subnormal (outward). The source `log2_bounds` avoids the base-conversion
612 // magnitude corruption that would otherwise hide a catastrophic underflow from `encode`.
613 // (Zero is excluded — it is exactly `0`, not an underflow.)
614 if !repr.significand().is_zero() && repr.log2_bounds().1 < -150.0 {
615 return Err(FpError::Underflow(repr.sign()));
616 }
617 let odd = convert_base_odd::<B>(repr, 32);
618 // See `convert_to_f64`: unified range check on the base-2 `odd`, before the significand
619 // rounding. f32::MAX = (2^24 − 1) × 2^104 ≈ 2^128.
620 let (lb, ub) = odd.log2_bounds();
621 if lb > 128.0 || (ub >= 128.0 && odd.abs_cmp(&(UBig::from(0xFFFFFFu64) << 104)).is_gt()) {
622 return Err(FpError::Overflow(odd.sign()));
623 }
624 let bits = significand_bits(&odd, 24, -149);
625 let rounded = Context::<R>::new(bits).repr_round(odd);
626 match rounded {
627 Exact(v) => v.into_f32_internal(),
628 Inexact(v, e) => match v.into_f32_internal()? {
629 Exact(f) => Ok(Inexact(f, e)),
630 Inexact(f, e2) => Ok(Inexact(f, e2)),
631 },
632 }
633 }
634
635 // Convert the [Repr] from base B to base NewB, with the precision under the target base from this context.
636 #[allow(non_upper_case_globals)]
637 fn convert_base<const B: Word, const NewB: Word>(
638 &self,
639 repr: Repr<B>,
640 mut cache: Option<&mut ConstCache>,
641 ) -> Rounded<Repr<NewB>> {
642 // shortcut if NewB is the same as B
643 if NewB == B {
644 return Exact(Repr {
645 significand: repr.significand,
646 exponent: repr.exponent,
647 });
648 }
649
650 // shortcut for infinities, no rounding happens but the result is inexact
651 if repr.is_infinite() {
652 return Inexact(
653 Repr {
654 significand: repr.significand,
655 exponent: repr.exponent,
656 },
657 Rounding::NoOp,
658 );
659 }
660
661 if NewB > B {
662 // shortcut if NewB is a power of B
663 let n = ilog_exact(NewB, B);
664 if n > 1 {
665 let (exp, rem) = repr.exponent.div_rem_euclid(n as isize);
666 let signif = repr.significand * B.pow(rem as u32);
667 let repr = Repr::new(signif, exp);
668 return self.repr_round(repr);
669 }
670 } else {
671 // shortcut if B is a power of NewB
672 let n = ilog_exact(B, NewB);
673 if n > 1 {
674 let exp = match repr.exponent.checked_mul(n as isize) {
675 Some(e) => e,
676 None => return converted_overflow_repr::<NewB>(repr.exponent > 0, repr.sign()),
677 };
678 // The value is exactly representable in the new base, but its significand can
679 // still be wider than the target precision (e.g. the single base-4 digit 3 is
680 // the two-digit binary 0b11) — round it like every other path does.
681 return self.repr_round(Repr::new(repr.significand, exp));
682 }
683 }
684
685 // Shortcut: when B and NewB share common factors, factor out the common part.
686 // B = NewB^a * r where gcd(r, NewB) = 1, so B^exp = NewB^(a*exp) * r^exp.
687 // For positive exponents the result is always exact (integer multiplication).
688 // For negative exponents, exact only when r^|exp| divides the significand.
689 let (a, r) = factor_base(B, NewB);
690 if a > 0 && r > 1 {
691 if repr.exponent >= 0 {
692 let sign = repr.sign();
693 let r_exp = UBig::from_word(r).pow(repr.exponent as usize);
694 let significand = repr.significand * r_exp;
695 let exp = match (a as isize).checked_mul(repr.exponent) {
696 Some(e) => e,
697 None => return converted_overflow_repr::<NewB>(true, sign),
698 };
699 let new_repr = Repr::<NewB>::new(significand, exp);
700 return self.repr_round(new_repr);
701 } else {
702 let r_exp: IBig = UBig::from_word(r).pow((-repr.exponent) as usize).into();
703 if let Some(quot) = (&repr.significand).div_exact(r_exp, &()) {
704 // the quotient's sign is the dividend's (r_exp > 0)
705 let exp = match (a as isize).checked_mul(repr.exponent) {
706 Some(e) => e,
707 None => return converted_overflow_repr::<NewB>(false, quot.sign()),
708 };
709 let new_repr = Repr::<NewB>::new(quot, exp);
710 return self.repr_round(new_repr);
711 }
712 }
713 }
714
715 // When NewB is a multiple of B: compute significand * B^exp directly
716 // as an integer, then express in base NewB.
717 if NewB % B == 0 && repr.exponent >= 0 {
718 let signif = repr.significand * Repr::<B>::BASE.pow(repr.exponent as usize);
719 let new_repr = Repr::<NewB>::new(signif, 0);
720 return self.repr_round(new_repr);
721 }
722
723 // if the base cannot be converted losslessly, the precision must be set
724 if self.precision == 0 {
725 panic_unlimited_precision();
726 }
727
728 // choose a exponent threshold such that number with exponent smaller than this value
729 // will be converted by directly evaluating the power. The threshold here is chosen such
730 // that the power under base 10 will fit in a double word.
731 const THRESHOLD_SMALL_EXP: isize = (Word::BITS as f32 * 0.60206) as isize; // word bits * 2 / log2(10)
732 if repr.exponent.abs() <= THRESHOLD_SMALL_EXP {
733 // if the exponent is small enough, directly evaluate the exponent
734 if repr.exponent >= 0 {
735 let signif = repr.significand * Repr::<B>::BASE.pow(repr.exponent as usize);
736 // The exact integer may carry more digits than the target precision allows
737 // (e.g. 2·2³ = 16 is the three-digit base-3 number 121), so it still goes
738 // through the rounding step.
739 self.repr_round(Repr::new(signif, 0))
740 } else {
741 let den: Repr<NewB> =
742 Repr::new(Repr::<B>::BASE.pow(-repr.exponent as usize).into(), 0);
743 // `repr_div_any_width` bounds an over-wide dividend itself (exactly — the
744 // dropped low digits are kept as rounding information), so no pre-shrinking
745 // here: rounding the dividend before the division would lie about exactness
746 // and could break tie decisions.
747 let num: Repr<NewB> = Repr::new(repr.significand, 0);
748 match self.repr_div_any_width(num, den) {
749 Ok(v) => v.map(|r: Repr<NewB>| Repr {
750 significand: r.significand,
751 exponent: r.exponent,
752 }),
753 Err(FpError::Overflow(sign)) => {
754 Inexact(Repr::<NewB>::infinity_with_sign(sign), Rounding::NoOp)
755 }
756 Err(FpError::Underflow(sign)) => {
757 Inexact(Repr::<NewB>::zero_with_sign(sign), Rounding::NoOp)
758 }
759 Err(_) => unreachable!(),
760 }
761 }
762 } else {
763 // if the exponent is large, then we first estimate the result exponent as floor(exponent * log(B) / log(NewB)),
764 // then the fractional part is multiplied with the original significand
765 let work_context = Context::<R>::new(2 * self.precision); // double the precision to get the precise logarithm
766 // ln(old base) and ln(new base) — near-correct is sufficient for the exponent estimate,
767 // and using the near-correct `ln_compute`/`ln_base` (R: Round) keeps base conversion
768 // off the `ErrorBounds` bound. Both are computed in base NewB so the euclidean division
769 // has matching bases.
770 let new_exp = repr.exponent
771 * work_context
772 .ln_compute::<NewB>(
773 &Repr::new(Repr::<B>::BASE.into(), 0),
774 work_context.precision,
775 false,
776 reborrow_cache(&mut cache),
777 )
778 .expect("ln(BASE) cannot range-error (the base fits the exponent range)")
779 .to_value_radius::<R>(&work_context)
780 .0;
781 let (exponent, rem) =
782 new_exp.div_rem_euclid(work_context.ln_base::<NewB>(reborrow_cache(&mut cache)));
783 let exponent_sign = exponent.sign();
784 let exponent: isize = match exponent.try_into() {
785 Ok(v) => v,
786 Err(_) => {
787 return converted_overflow_repr::<NewB>(
788 exponent_sign == Sign::Positive,
789 repr.sign(),
790 );
791 }
792 };
793 // exp(fractional exponent) — near-correct is sufficient (it scales the significand),
794 // so use `exp_compute` (R: Round) and stay off the `ErrorBounds` bound.
795 let n = 1usize << (work_context.precision.bit_len() / 2);
796 let exp_rem = work_context
797 .exp_compute::<NewB>(
798 &rem.repr,
799 work_context.precision,
800 false,
801 n,
802 reborrow_cache(&mut cache),
803 )
804 .expect("exp(reduced rem) cannot overflow (|rem| < B^-n)")
805 .mid;
806 let significand = &repr.significand * exp_rem.significand();
807 let repr = Repr::new(significand, exponent + exp_rem.exponent());
808 self.repr_round(repr)
809 }
810 }
811}
812
813impl<const B: Word> Repr<B> {
814 // this method requires that the representation is already rounded to 24 binary bits
815 fn into_f32_internal(self) -> FpResult<f32> {
816 assert!(B == 2);
817 debug_assert!(self.is_finite());
818 debug_assert!(self.significand.bit_len() <= 24);
819
820 let sign = self.sign();
821 if self.is_neg_zero() {
822 // encode() would drop the sign of -0; preserve it exactly
823 return Ok(Exact(sign * 0f32));
824 }
825 let man24: i32 = self.significand.try_into().unwrap();
826 match f32::encode(man24, self.exponent as i16) {
827 Exact(v) => Ok(Exact(v)),
828 Inexact(v, _) if v.is_infinite() => Err(FpError::Overflow(sign)),
829 Inexact(0.0, _) => Err(FpError::Underflow(sign)),
830 Inexact(v, _) => Ok(Inexact(v, Rounding::NoOp)),
831 }
832 }
833
834 /// Convert the float number representation to a [f32] with the default IEEE 754 rounding mode.
835 ///
836 /// The default IEEE 754 rounding mode is [HalfEven] (rounding to nearest, ties to even). To convert
837 /// the float number with a specific rounding mode, please use [FBig::to_f32].
838 ///
839 /// # Examples
840 ///
841 /// ```
842 /// # use dashu_base::Approximation::*;
843 /// # use dashu_float::{Repr, round::Rounding::*};
844 /// assert_eq!(Repr::<2>::one().to_f32(), Exact(1.0));
845 /// assert_eq!(Repr::<10>::infinity().to_f32(), Inexact(f32::INFINITY, NoOp));
846 /// ```
847 #[inline]
848 pub fn to_f32(&self) -> Rounded<f32> {
849 match Context::<HalfEven>::convert_to_f32(self.clone()) {
850 Ok(rounded) => rounded,
851 Err(err) => f32_directed_endpoint::<HalfEven>(err),
852 }
853 }
854
855 // this method requires that the representation is already rounded to 53 binary bits
856 fn into_f64_internal(self) -> FpResult<f64> {
857 assert!(B == 2);
858 debug_assert!(self.is_finite());
859 debug_assert!(self.significand.bit_len() <= 53);
860
861 let sign = self.sign();
862 if self.is_neg_zero() {
863 // encode() would drop the sign of -0; preserve it exactly
864 return Ok(Exact(sign * 0f64));
865 }
866 let man53: i64 = self.significand.try_into().unwrap();
867 match f64::encode(man53, self.exponent as i16) {
868 Exact(v) => Ok(Exact(v)),
869 Inexact(v, _) if v.is_infinite() => Err(FpError::Overflow(sign)),
870 Inexact(0.0, _) => Err(FpError::Underflow(sign)),
871 Inexact(v, _) => Ok(Inexact(v, Rounding::NoOp)),
872 }
873 }
874
875 /// Convert the float number representation to a [f64] with the default IEEE 754 rounding mode.
876 ///
877 /// The default IEEE 754 rounding mode is [HalfEven] (rounding to nearest, ties to even). To convert
878 /// the float number with a specific rounding mode, please use [FBig::to_f64].
879 ///
880 /// # Examples
881 ///
882 /// ```
883 /// # use dashu_base::Approximation::*;
884 /// # use dashu_float::{Repr, round::Rounding::*};
885 /// assert_eq!(Repr::<2>::one().to_f64(), Exact(1.0));
886 /// assert_eq!(Repr::<10>::infinity().to_f64(), Inexact(f64::INFINITY, NoOp));
887 /// ```
888 #[inline]
889 pub fn to_f64(&self) -> Rounded<f64> {
890 match Context::<HalfEven>::convert_to_f64(self.clone()) {
891 Ok(rounded) => rounded,
892 Err(err) => f64_directed_endpoint::<HalfEven>(err),
893 }
894 }
895
896 /// Convert the float number representation to a [IBig].
897 ///
898 /// The fractional part is always rounded to zero. To convert with other rounding modes,
899 /// please use [FBig::to_int()].
900 ///
901 /// # Warning
902 ///
903 /// If the float number has a very large exponent, it will be evaluated and result
904 /// in allocating an huge integer and it might eat up all your memory.
905 ///
906 /// To get a rough idea of how big the number is, it's recommended to use [EstimatedLog2].
907 ///
908 /// # Examples
909 ///
910 /// ```
911 /// # use dashu_base::Approximation::*;
912 /// # use dashu_int::IBig;
913 /// # use dashu_float::{Repr, round::Rounding::*};
914 /// assert_eq!(Repr::<2>::neg_one().to_int(), Exact(IBig::NEG_ONE));
915 /// ```
916 ///
917 /// # Panics
918 ///
919 /// Panics if the number is infinte.
920 pub fn to_int(&self) -> Rounded<IBig> {
921 assert_finite(self);
922
923 if self.exponent >= 0 {
924 // the number is already an integer
925 Exact(shl_digits::<B>(&self.significand, self.exponent as usize))
926 } else if self.smaller_than_one() {
927 // the number is definitely smaller than
928 Inexact(IBig::ZERO, Rounding::NoOp)
929 } else {
930 let int = shr_digits::<B>(&self.significand, (-self.exponent) as usize);
931 Inexact(int, Rounding::NoOp)
932 }
933 }
934}
935
936impl<const B: Word> From<UBig> for Repr<B> {
937 #[inline]
938 fn from(n: UBig) -> Self {
939 Self::new(n.into(), 0)
940 }
941}
942impl<R: Round, const B: Word> From<UBig> for FBig<R, B> {
943 #[inline]
944 fn from(n: UBig) -> Self {
945 Self::from_parts(n.into(), 0)
946 }
947}
948
949impl<const B: Word> From<IBig> for Repr<B> {
950 #[inline]
951 fn from(n: IBig) -> Self {
952 Self::new(n, 0)
953 }
954}
955impl<R: Round, const B: Word> From<IBig> for FBig<R, B> {
956 #[inline]
957 fn from(n: IBig) -> Self {
958 Self::from_parts(n, 0)
959 }
960}
961
962impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for IBig {
963 type Error = ConversionError;
964
965 #[inline]
966 fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
967 if value.repr.is_infinite() {
968 Err(ConversionError::OutOfBounds)
969 } else if value.repr.significand.is_zero() {
970 // A zero significand is integer zero regardless of exponent. This also
971 // accepts IEEE-754 signed zero, whose sign is carried by a -1 exponent
972 // sentinel (not the significand); it is treated as plain 0. The zero
973 // must be handled here rather than in the `else` branch below, which
974 // shifts by `exponent as usize` and would underflow on the -1 sentinel.
975 Ok(value.repr.significand)
976 } else if value.repr.exponent < 0 {
977 Err(ConversionError::LossOfPrecision)
978 } else {
979 let mut int = value.repr.significand;
980 shl_digits_in_place::<B>(&mut int, value.repr.exponent as usize);
981 Ok(int)
982 }
983 }
984}
985
986impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for UBig {
987 type Error = ConversionError;
988
989 #[inline]
990 fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
991 let int: IBig = value.try_into()?;
992 int.try_into()
993 }
994}
995
996macro_rules! fbig_unsigned_conversions {
997 ($($t:ty)*) => {$(
998 impl<const B: Word> From<$t> for Repr<B> {
999 #[inline]
1000 fn from(value: $t) -> Repr<B> {
1001 UBig::from(value).into()
1002 }
1003 }
1004 impl<R: Round, const B: Word> From<$t> for FBig<R, B> {
1005 #[inline]
1006 fn from(value: $t) -> FBig<R, B> {
1007 UBig::from(value).into()
1008 }
1009 }
1010
1011 impl<const B: Word> TryFrom<Repr<B>> for $t {
1012 type Error = ConversionError;
1013
1014 fn try_from(value: Repr<B>) -> Result<Self, Self::Error> {
1015 if value.sign() == Sign::Negative || value.is_infinite() {
1016 Err(ConversionError::OutOfBounds)
1017 } else {
1018 let (log2_lb, _) = value.log2_bounds();
1019 if log2_lb >= <$t>::BITS as f32 {
1020 Err(ConversionError::OutOfBounds)
1021 } else if value.exponent < 0 {
1022 Err(ConversionError::LossOfPrecision)
1023 } else {
1024 shl_digits::<B>(&value.significand, value.exponent as usize).try_into()
1025 }
1026 }
1027 }
1028 }
1029 impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
1030 type Error = ConversionError;
1031
1032 #[inline]
1033 fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
1034 value.repr.try_into()
1035 }
1036 }
1037 )*};
1038}
1039fbig_unsigned_conversions!(u8 u16 u32 u64 u128 usize);
1040
1041macro_rules! fbig_signed_conversions {
1042 ($($t:ty)*) => {$(
1043 impl<R: Round, const B: Word> From<$t> for FBig<R, B> {
1044 #[inline]
1045 fn from(value: $t) -> FBig<R, B> {
1046 IBig::from(value).into()
1047 }
1048 }
1049
1050 impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
1051 type Error = ConversionError;
1052
1053 fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
1054 if value.repr.is_infinite() {
1055 Err(ConversionError::OutOfBounds)
1056 } else {
1057 let (log2_lb, _) = value.repr.log2_bounds();
1058 if log2_lb >= <$t>::BITS as f32 {
1059 Err(ConversionError::OutOfBounds)
1060 } else if value.repr.exponent < 0 {
1061 Err(ConversionError::LossOfPrecision)
1062 } else {
1063 shl_digits::<B>(&value.repr.significand, value.repr.exponent as usize).try_into()
1064 }
1065 }
1066 }
1067 }
1068 )*};
1069}
1070fbig_signed_conversions!(i8 i16 i32 i64 i128 isize);
1071
1072// The directed saturation endpoint for an out-of-range f32/f64 result, chosen from the `FpError`
1073// returned by `into_f*_internal`. Overflow saturates to ±MAX or ±∞ per the mode (outward modes
1074// reach ±∞; toward-zero/opposite/nearest saturate to the largest finite); underflow saturates to
1075// ±0 or the smallest subnormal of that sign. `round_low_part`'s AddOne/SubOne verdict on a
1076// same-sign residual is exactly the outward-vs-inward decision; only its directional verdict is
1077// used. This is the single place that picks the endpoint, shared by `to_f32`/`to_f64` (Repr uses
1078// HalfEven, FBig uses its own mode).
1079macro_rules! impl_float_directed_endpoint {
1080 (
1081 $fn:ident, $t:ty, $max:expr, $min:expr, $inf:expr, $neg_inf:expr,
1082 $smallest_sub:expr, $neg_smallest_sub:expr
1083 ) => {
1084 fn $fn<R: Round>(err: FpError) -> Rounded<$t> {
1085 match err {
1086 FpError::Overflow(sign) => {
1087 let adj = if sign == Sign::Positive {
1088 R::round_low_part(&IBig::ONE, Sign::Positive, || {
1089 core::cmp::Ordering::Greater
1090 })
1091 } else {
1092 R::round_low_part(&IBig::NEG_ONE, Sign::Negative, || {
1093 core::cmp::Ordering::Greater
1094 })
1095 };
1096 Inexact(
1097 match (sign, adj) {
1098 (Sign::Positive, AddOne) => $inf,
1099 (Sign::Positive, _) => $max,
1100 (Sign::Negative, SubOne) => $neg_inf,
1101 (Sign::Negative, _) => $min,
1102 },
1103 adj,
1104 )
1105 }
1106 FpError::Underflow(sign) => {
1107 let adj = if sign == Sign::Positive {
1108 R::round_low_part(&IBig::ZERO, Sign::Positive, || core::cmp::Ordering::Less)
1109 } else {
1110 R::round_low_part(&IBig::ZERO, Sign::Negative, || core::cmp::Ordering::Less)
1111 };
1112 Inexact(
1113 match (sign, adj) {
1114 (Sign::Positive, AddOne) => $smallest_sub, // smallest positive subnormal
1115 (Sign::Positive, _) => 0.0,
1116 (Sign::Negative, SubOne) => $neg_smallest_sub,
1117 (Sign::Negative, _) => -0.0,
1118 },
1119 adj,
1120 )
1121 }
1122 // `into_f*_internal` only returns Overflow/Underflow; the infinite-input case is
1123 // handled by `convert_to_f*` (returning `Ok`) before this is reached.
1124 _ => unreachable!("convert_to_f* only returns Overflow/Underflow here"),
1125 }
1126 }
1127 };
1128}
1129impl_float_directed_endpoint!(
1130 f32_directed_endpoint,
1131 f32,
1132 f32::MAX,
1133 f32::MIN,
1134 f32::INFINITY,
1135 f32::NEG_INFINITY,
1136 f32::from_bits(1),
1137 f32::from_bits(0x8000_0001)
1138);
1139impl_float_directed_endpoint!(
1140 f64_directed_endpoint,
1141 f64,
1142 f64::MAX,
1143 f64::MIN,
1144 f64::INFINITY,
1145 f64::NEG_INFINITY,
1146 f64::from_bits(1),
1147 f64::from_bits(0x8000_0000_0000_0001)
1148);
1149
1150macro_rules! impl_from_fbig_for_float {
1151 ($t:ty, $convert:ident) => {
1152 impl TryFrom<Repr<2>> for $t {
1153 type Error = ConversionError;
1154
1155 #[inline]
1156 fn try_from(value: Repr<2>) -> Result<Self, Self::Error> {
1157 if value.is_infinite() {
1158 return Err(ConversionError::LossOfPrecision);
1159 }
1160 // Range detection is shared with `to_f32`/`to_f64` via `convert_to_f*`: it returns
1161 // `Err(Overflow)` for a value beyond the largest finite `$t`, so `OutOfBounds` is
1162 // reported the same way under every rounding mode (Repr uses HalfEven here).
1163 match Context::<HalfEven>::$convert(value) {
1164 Ok(Exact(v)) => Ok(v),
1165 Ok(Inexact(_, _)) => Err(ConversionError::LossOfPrecision),
1166 Err(FpError::Overflow(_)) => Err(ConversionError::OutOfBounds),
1167 Err(FpError::Underflow(_)) => Err(ConversionError::LossOfPrecision),
1168 Err(_) => unreachable!(),
1169 }
1170 }
1171 }
1172
1173 impl<R: Round> TryFrom<FBig<R, 2>> for $t {
1174 type Error = ConversionError;
1175
1176 #[inline]
1177 fn try_from(value: FBig<R, 2>) -> Result<Self, Self::Error> {
1178 if value.repr.is_infinite() {
1179 return Err(ConversionError::LossOfPrecision);
1180 }
1181 // A value beyond the largest finite `$t` is out of range whatever the rounding mode:
1182 // the mode only selects the saturation endpoint (±MAX vs ±∞), and `convert_to_f*`
1183 // reports that range condition as `Err(Overflow)` regardless of mode — so
1184 // `Err(OutOfBounds)` reliably means "beyond the finite range", unlike the old
1185 // result-infiniteness check (which flipped LossOfPrecision/OutOfBounds with the mode).
1186 match Context::<R>::$convert(value.repr) {
1187 Ok(Exact(v)) => Ok(v),
1188 Ok(Inexact(_, _)) => Err(ConversionError::LossOfPrecision),
1189 Err(FpError::Overflow(_)) => Err(ConversionError::OutOfBounds),
1190 Err(FpError::Underflow(_)) => Err(ConversionError::LossOfPrecision),
1191 Err(_) => unreachable!(),
1192 }
1193 }
1194 }
1195 };
1196}
1197impl_from_fbig_for_float!(f32, convert_to_f32);
1198impl_from_fbig_for_float!(f64, convert_to_f64);
1199
1200#[cfg(test)]
1201mod tests {
1202 use super::*;
1203 use crate::repr::Repr;
1204 use crate::round::mode::{HalfEven, Zero};
1205
1206 // Regression tests for issue #100: base conversion used to return unreduced significands
1207 // from its exact-conversion shortcuts, and pre-rounded the dividend of its division path
1208 // (lying about exactness).
1209 #[test]
1210 fn test_with_base_reduces_and_flags() {
1211 // 2.1 -> base 2 @ p1: 21/10 has no finite binary form, so the conversion is inexact;
1212 // the old division path pre-rounded 21 to 20, and 20/10 = 2 divided exactly.
1213 let r = FBig::<Zero, 10>::from_parts(IBig::from(21), -1).with_base_and_precision::<2>(1);
1214 assert!(matches!(r, Inexact(..)), "2.1 -> base 2 @ p1 must be inexact");
1215
1216 // 3.1 -> base 2 @ p1: the significand 11₂ must be reduced to one digit.
1217 let v = FBig::<Zero, 10>::from_parts(IBig::from(31), -1)
1218 .with_base_and_precision::<2>(1)
1219 .value();
1220 assert!(v.repr().digits() <= v.precision());
1221
1222 // 16 (2·2³) -> base 3 @ p1: the exact conversion produces 121₃ — three digits, which
1223 // must be rounded (Zero -> 1·3² = 9, since 16 lies between 9 and 18).
1224 let v = FBig::<Zero, 2>::from_parts(IBig::from(2), 3)
1225 .with_base_and_precision::<3>(1)
1226 .value();
1227 assert_eq!(v.repr(), &Repr::<3>::new(IBig::from(1), 2));
1228
1229 // 3 (base 4) -> base 2 @ p1: the single base-4 digit 3 is the two-digit binary 0b11,
1230 // so the "B is a power of NewB" shortcut must still round (Zero -> 1·2¹ = 2).
1231 let v = FBig::<Zero, 4>::from_parts(IBig::from(3), 0)
1232 .with_base_and_precision::<2>(1)
1233 .value();
1234 assert_eq!(v.repr(), &Repr::<2>::new(IBig::from(1), 1));
1235 }
1236
1237 // An exact conversion whose result fits the precision stays Exact through the shortcuts.
1238 #[test]
1239 fn test_with_base_exact_fits() {
1240 // 1.5 (base 10) -> base 2 @ p2: 15/100·... = 3/2 divides exactly onto 11·2^-1.
1241 let r =
1242 FBig::<HalfEven, 10>::from_parts(IBig::from(15), -1).with_base_and_precision::<2>(2);
1243 assert!(matches!(r, Exact(..)));
1244 assert_eq!(r.value().repr(), &Repr::<2>::new(IBig::from(3), -1));
1245
1246 // 4 (base 2) -> base 4: conversion to a power of the base is exact (4 = 1·4¹).
1247 let r = FBig::<HalfEven, 2>::from_parts(IBig::from(1), 2).with_base::<4>();
1248 assert!(matches!(r, Exact(..)));
1249 assert_eq!(r.value().repr(), &Repr::<4>::new(IBig::from(1), 1));
1250 }
1251
1252 // Directed overflow must reach the endpoint for a value whose *most*-significant bit straddles
1253 // f32::MAX (not only for values whose lsb exponent is ≥ 128). `3·2¹²⁷` ≈ 1.5·2¹²⁸ overflows
1254 // f32::MAX = (2 − 2⁻²³)·2¹²⁷, yet its lsb exponent is 127 — the old exponent-gated branch fell
1255 // through to `encode`, which saturates to ±∞ mode-blindly.
1256 #[test]
1257 fn f32_directed_overflow_at_msb_boundary() {
1258 use crate::round::mode::{Down, Up};
1259 // f32: 3·2^127 under Zero/Down -> f32::MAX, under Up/Away -> +∞.
1260 let zero = FBig::<Zero, 2>::from_parts(3.into(), 127);
1261 let down = FBig::<Down, 2>::from_parts(3.into(), 127);
1262 let up = FBig::<Up, 2>::from_parts(3.into(), 127);
1263 assert_eq!(zero.to_f32().value().to_bits(), 0x7f7fffff); // f32::MAX
1264 assert_eq!(down.to_f32().value().to_bits(), 0x7f7fffff);
1265 assert!(up.to_f32().value().is_infinite() && up.to_f32().value().is_sign_positive());
1266 // negative mirror
1267 let nzero = FBig::<Zero, 2>::from_parts((-3).into(), 127);
1268 assert_eq!(nzero.to_f32().value().to_bits(), 0xff7fffff); // f32::MIN
1269
1270 // f64: 3·2^1023 overflows f64::MAX; lsb exponent 1023 < 1024.
1271 let z64 = FBig::<Zero, 2>::from_parts(3.into(), 1023);
1272 assert_eq!(z64.to_f64().value().to_bits(), 0x7fefffffffffffff); // f64::MAX
1273 let u64 = FBig::<Up, 2>::from_parts(3.into(), 1023);
1274 assert!(u64.to_f64().value().is_infinite() && u64.to_f64().value().is_sign_positive());
1275 }
1276
1277 // Directed underflow must reach the endpoint through the `encode` path too (not only for the
1278 // extreme exponents caught by the old explicit branch). `2⁻¹⁶⁰` is below the smallest subnormal;
1279 // under Up a positive value must round up to `2⁻¹⁴⁹`, but `encode` returns ±0 mode-blindly.
1280 #[test]
1281 fn f32_directed_underflow_through_encode() {
1282 use crate::round::mode::{Away, Down, Up};
1283 // f32: 2^-160 under Up/Away -> smallest +subnormal, under Zero/Down -> +0.
1284 let up = FBig::<Up, 2>::from_parts(IBig::ONE, -160);
1285 let away = FBig::<Away, 2>::from_parts(IBig::ONE, -160);
1286 let zero = FBig::<Zero, 2>::from_parts(IBig::ONE, -160);
1287 let down = FBig::<Down, 2>::from_parts(IBig::ONE, -160);
1288 assert_eq!(up.to_f32().value().to_bits(), 0x00000001); // smallest +subnormal
1289 assert_eq!(away.to_f32().value().to_bits(), 0x00000001);
1290 assert_eq!(zero.to_f32().value().to_bits(), 0x0);
1291 assert_eq!(down.to_f32().value().to_bits(), 0x0);
1292 // negative: Down/Away -> smallest -subnormal, Zero/Up -> -0.
1293 let ndown = FBig::<Down, 2>::from_parts(-IBig::ONE, -160);
1294 assert_eq!(ndown.to_f32().value().to_bits(), 0x80000001);
1295
1296 // f64: 2^-1100 under Up -> smallest +subnormal.
1297 let u64 = FBig::<Up, 2>::from_parts(IBig::ONE, -1100);
1298 assert_eq!(u64.to_f64().value().to_bits(), 0x0000_0000_0000_0001);
1299 }
1300
1301 // A decimal FBig whose value exceeds f32::MAX must saturate to the largest *finite* f32 under
1302 // toward-zero (Zero), not +∞ — directed overflow picks the endpoint per mode.
1303 #[test]
1304 fn f32_overflow_saturates_to_max_under_toward_zero() {
1305 use crate::round::mode::Up;
1306 // value ≈ 1.8e67 ≫ f32::MAX
1307 let sig: IBig = "18113714167384154970503568309331051197800435559900844351020490293248000000000000000000000000000000000000000000000000000000000000000000000040081793412673420422647135518538018600544982168035325793404432580816367361955984101780462905766719198411690240".parse().unwrap();
1308 let down = FBig::<Zero, 10>::from_parts(sig.clone(), -180);
1309 assert_eq!(down.to_f32().value().to_bits(), 0x7f7fffff); // f32::MAX, not +∞
1310 // The same value toward +∞ does reach +∞.
1311 let up = FBig::<Up, 10>::from_parts(sig, -180);
1312 assert!(up.to_f32().value().is_infinite());
1313 }
1314
1315 // A wide decimal significand near an f32 subnormal midpoint must round to the correct
1316 // neighbor. The near-correct base-conversion logarithm previously landed on the wrong side of a
1317 // 32-bit midpoint, producing a result 1 ULP off (0x00040008 instead of 0x00040007).
1318 #[test]
1319 fn f32_decimal_subnormal_rounds_correctly() {
1320 let v = FBig::<HalfEven, 10>::from_parts(
1321 "367352494370447282365772889742681992006459962834329374643515088008836389924495676300982092025765783512749250624297822169761305238359955010699895018824154119671"
1322 .parse()
1323 .unwrap(),
1324 -198,
1325 );
1326 assert_eq!(v.to_f32().value().to_bits(), 0x00040007);
1327 }
1328
1329 // A positive value below the smallest subnormal must round *up* to that smallest subnormal
1330 // under Up/Away (and to ±0 under toward-zero/opposite/nearest) — the underflow path used to
1331 // return a mode-blind signed zero, which is not an upper bound for a positive value under Up.
1332 #[test]
1333 fn f64_directed_underflow_below_smallest_subnormal() {
1334 use crate::round::mode::Down;
1335 // tiny positive binary value, far below 2^-1074
1336 let up = FBig::<crate::round::mode::Up, 2>::from_parts(IBig::ONE, -20000);
1337 let away = FBig::<crate::round::mode::Away, 2>::from_parts(IBig::ONE, -20000);
1338 let zero = FBig::<Zero, 2>::from_parts(IBig::ONE, -20000);
1339 let down = FBig::<Down, 2>::from_parts(IBig::ONE, -20000);
1340 assert_eq!(up.to_f64().value().to_bits(), 0x0000_0000_0000_0001); // smallest +subnormal
1341 assert_eq!(away.to_f64().value().to_bits(), 0x0000_0000_0000_0001);
1342 assert_eq!(zero.to_f64().value().to_bits(), 0x0); // +0
1343 assert_eq!(down.to_f64().value().to_bits(), 0x0);
1344
1345 // tiny negative: Down/Away -> smallest -subnormal, Zero/Up -> -0
1346 let ndown = FBig::<Down, 2>::from_parts(-IBig::ONE, -20000);
1347 let nup = FBig::<crate::round::mode::Up, 2>::from_parts(-IBig::ONE, -20000);
1348 assert_eq!(ndown.to_f64().value().to_bits(), 0x8000_0000_0000_0001); // smallest -subnormal
1349 assert_eq!(nup.to_f64().value().to_bits(), 0x8000_0000_0000_0000); // -0
1350 }
1351
1352 // A wide-significand *decimal* value far below ½·MIN_SUBNORMAL (exponent −20000). The base
1353 // conversion's internal `exp` underflows for such a catastrophically tiny value, so without the
1354 // source-`log2_bounds` short-circuit the converted magnitude is wrong and `to_f64` returned a
1355 // spurious finite subnormal (≈2^-593) instead of the directed underflow endpoint. The binary
1356 // test above doesn't hit this — it skips base conversion entirely.
1357 #[test]
1358 fn f64_decimal_wide_significand_underflow() {
1359 use crate::round::mode::{Down, Up};
1360 let wide = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234"
1361 .parse::<IBig>()
1362 .unwrap();
1363 for prec in [20usize, 50, 100, 500] {
1364 let he = FBig::<HalfEven, 10>::from_parts(wide.clone(), -20000)
1365 .with_precision(prec)
1366 .value();
1367 let up = FBig::<Up, 10>::from_parts(wide.clone(), -20000)
1368 .with_precision(prec)
1369 .value();
1370 let dn = FBig::<Down, 10>::from_parts(wide.clone(), -20000)
1371 .with_precision(prec)
1372 .value();
1373 // positive value: nearest/Down -> +0, Up -> smallest positive subnormal
1374 assert_eq!(he.to_f64().value().to_bits(), 0x0, "HalfEven @ prec {prec}");
1375 assert_eq!(dn.to_f64().value().to_bits(), 0x0, "Down @ prec {prec}");
1376 assert_eq!(up.to_f64().value().to_bits(), 0x0000_0000_0000_0001, "Up @ prec {prec}");
1377 // f32 mirror: |x| < 2^-150 = ½·MIN_SUBNORMAL -> +0 / smallest +subnormal
1378 assert_eq!(he.to_f32().value().to_bits(), 0x0u32, "f32 HalfEven @ prec {prec}");
1379 assert_eq!(up.to_f32().value().to_bits(), 0x0000_0001u32, "f32 Up @ prec {prec}");
1380
1381 // negative value: nearest/Up -> -0, Down -> smallest negative subnormal
1382 let neg = FBig::<HalfEven, 10>::from_parts(-wide.clone(), -20000)
1383 .with_precision(prec)
1384 .value();
1385 let ndn = FBig::<Down, 10>::from_parts(-wide.clone(), -20000)
1386 .with_precision(prec)
1387 .value();
1388 assert_eq!(
1389 neg.to_f64().value().to_bits(),
1390 0x8000_0000_0000_0000,
1391 "neg HalfEven @ prec {prec}"
1392 );
1393 assert_eq!(
1394 ndn.to_f64().value().to_bits(),
1395 0x8000_0000_0000_0001,
1396 "neg Down @ prec {prec}"
1397 );
1398 }
1399 }
1400
1401 #[test]
1402 fn ibig_try_from_accepts_signed_zero() {
1403 // IEEE-754 signed zero (sign encoded in a -1 exponent sentinel) is plain 0.
1404 let neg_zero = FBig::<HalfAway, 10>::new(Repr::neg_zero(), Context::new(8));
1405 assert_eq!(IBig::try_from(neg_zero), Ok(IBig::from(0)));
1406
1407 // positive zero already worked, and still does
1408 let pos_zero = FBig::<HalfAway, 10>::new(Repr::zero(), Context::new(8));
1409 assert_eq!(IBig::try_from(pos_zero), Ok(IBig::from(0)));
1410
1411 // UBig delegates to the IBig impl, so it accepts signed zero too
1412 let neg_zero = FBig::<HalfAway, 2>::new(Repr::neg_zero(), Context::new(8));
1413 assert_eq!(UBig::try_from(neg_zero), Ok(UBig::from(0u8)));
1414
1415 // a genuine fractional value must still be rejected
1416 let frac = FBig::<HalfAway, 10>::new(Repr::new(IBig::from(1), -1), Context::new(8));
1417 assert_eq!(IBig::try_from(frac), Err(ConversionError::LossOfPrecision));
1418
1419 // a normal integer round-trips exactly
1420 let int_val = FBig::<HalfAway, 10>::new(Repr::new(IBig::from(42), 0), Context::new(8));
1421 assert_eq!(IBig::try_from(int_val), Ok(IBig::from(42)));
1422 }
1423
1424 #[test]
1425 fn with_base_high_precision_no_overflow() {
1426 // Regression for issue #95: converting a high-precision base-2 float to base
1427 // 10 panicked on 32-bit targets ("arithmetic operations with the infinity are
1428 // not allowed!"). The base conversion evaluates exp(r) as `sum^(B^n)` through
1429 // `powi` with a huge exponent (B^n) on a base (sum) very close to 1; `powi`'s
1430 // overflow guard estimated log2(base) with the catastrophically-canceling
1431 // `log2_est`, and the ~1e-4 of f32 noise scaled by the exponent crossed the
1432 // (much smaller on 32-bit) isize threshold, yielding a spurious ±inf that then
1433 // panicked when shifted. See `powi` in exp.rs for the fix.
1434 use crate::round::mode::Zero;
1435 use core::str::FromStr;
1436
1437 // The reporter's input: -1.1111…0011 in binary (578 significant bits), written
1438 // in the hex form dashu accepts for base-2 floats (`0x1.<hex>…`). The value is
1439 // identical to the raw binary literal.
1440 let num = FBig::<Zero, 2>::from_str(
1441 "-0x1.fffdc8d645194a5a95df4be063472d4406dd096339dd7dc2a8527d208b3da7b9e5c36b4f49a7982cb2ad20a4e7e4c016f858fe8cddea011a6d01fe3823189c4ed4f57a7babc331498",
1442 )
1443 .unwrap();
1444
1445 // at the original 578-bit precision the conversion succeeds …
1446 let a = num
1447 .clone()
1448 .with_precision(578)
1449 .value()
1450 .with_base::<10>()
1451 .value();
1452 assert!(a.repr().is_finite());
1453 // … and so does a slightly higher precision (586), which panicked on 32-bit
1454 // (wasm32 / i686). The result matches the value computed on 64-bit. Compared
1455 // by value (FBig equality ignores context) rather than via string formatting,
1456 // so this works under no_std too.
1457 let b = num.with_precision(586).value().with_base::<10>().value();
1458 assert!(b.repr().is_finite());
1459 let expected = FBig::<Zero, 10>::from_str(
1460 "-1.9999661944503703041843468850635057967553124154072485151176192294480158424234268438137612977886891381228704640656094986435381057574477216648567249609280392009533217665484389886",
1461 )
1462 .unwrap();
1463 assert_eq!(b, expected);
1464 }
1465
1466 // Shrinking an *unlimited*-precision value must round its significand down to the
1467 // target precision. Previously the `with_precision` shrink guard compared context
1468 // precisions (`0 > N` is always false), so an unlimited source was never rounded.
1469 #[test]
1470 fn with_precision_from_unlimited_rounds() {
1471 let a =
1472 FBig::<Zero, 2>::from_repr(Repr::<2>::new(IBig::from(0xffffu32), 0), Context::new(0));
1473 let b = a.with_precision(8).value();
1474 assert_eq!(b.precision(), 8);
1475 assert_eq!(b.repr().digits(), 8);
1476 assert_eq!(b.repr().significand(), &IBig::from(0xffu32));
1477 }
1478}