dashu_float/convert.rs
1use core::{
2 convert::{TryFrom, TryInto},
3 num::FpCategory,
4};
5
6use dashu_base::{
7 Approximation::*, BitTest, ConversionError, DivRemEuclid, EstimatedLog2, FloatEncoding, Sign,
8 Signed,
9};
10use dashu_int::{IBig, UBig, Word};
11
12use crate::{
13 error::{assert_finite, panic_unlimited_precision, FpError},
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 },
21 utils::{factor_base, ilog_exact, shl_digits, shl_digits_in_place, shr_digits},
22};
23
24impl<R: Round> Context<R> {
25 /// Convert an [IBig] instance to a [FBig] instance with precision
26 /// and rounding given by the context.
27 ///
28 /// # Examples
29 ///
30 /// ```
31 /// # use core::str::FromStr;
32 /// # use dashu_base::ParseError;
33 /// # use dashu_float::DBig;
34 /// use dashu_base::Approximation::*;
35 /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
36 ///
37 /// let context = Context::<HalfAway>::new(2);
38 /// assert_eq!(context.convert_int::<10>((-12).into()), Exact(DBig::from_str("-12")?));
39 /// assert_eq!(
40 /// context.convert_int::<10>(5678.into()),
41 /// Inexact(DBig::from_str("5.7e3")?, AddOne)
42 /// );
43 /// # Ok::<(), ParseError>(())
44 /// ```
45 pub fn convert_int<const B: Word>(&self, n: IBig) -> Rounded<FBig<R, B>> {
46 let repr = Repr::<B>::new(n, 0);
47 self.repr_round(repr).map(|v| FBig::new(v, *self))
48 }
49}
50
51macro_rules! impl_from_float_for_fbig {
52 ($t:ty) => {
53 impl TryFrom<$t> for Repr<2> {
54 type Error = ConversionError;
55
56 fn try_from(f: $t) -> Result<Self, Self::Error> {
57 match f.decode() {
58 Ok((man, exp)) => Ok(if man == 0 && f.is_sign_negative() {
59 Self::neg_zero()
60 } else {
61 Repr::new(man.into(), exp as _)
62 }),
63 Err(FpCategory::Infinite) => match f.sign() {
64 Sign::Positive => Ok(Self::infinity()),
65 Sign::Negative => Ok(Self::neg_infinity()),
66 },
67 _ => Err(ConversionError::OutOfBounds), // NaN
68 }
69 }
70 }
71
72 impl<R: Round> TryFrom<$t> for FBig<R, 2> {
73 type Error = ConversionError;
74
75 fn try_from(f: $t) -> Result<Self, Self::Error> {
76 match f.decode() {
77 Ok((man, exp)) => {
78 // preserve the sign of a signed zero (-0.0 -> Repr::neg_zero())
79 let repr = if man == 0 && f.is_sign_negative() {
80 Repr::neg_zero()
81 } else {
82 Repr::new(man.into(), exp as _)
83 };
84
85 // The precision is inferenced from the mantissa, because the mantissa of
86 // normal float is always normalized. This will produce correct precision
87 // for subnormal floats
88 let bits = man.unsigned_abs().bit_len();
89 let context = Context::new(bits);
90 Ok(Self::new(repr, context))
91 }
92 Err(FpCategory::Infinite) => match f.sign() {
93 Sign::Positive => Ok(Self::INFINITY),
94 Sign::Negative => Ok(Self::NEG_INFINITY),
95 },
96 _ => Err(ConversionError::OutOfBounds), // NaN
97 }
98 }
99 }
100 };
101}
102
103impl_from_float_for_fbig!(f32);
104impl_from_float_for_fbig!(f64);
105
106impl<R: Round, const B: Word> FBig<R, B> {
107 /// Convert the float number to base 10 (with decimal exponents) rounding to even
108 /// and tying away from zero.
109 ///
110 /// It's equivalent to `self.with_rounding::<HalfAway>().with_base::<10>()`.
111 /// The output is directly of type [DBig][crate::DBig].
112 ///
113 /// See [with_base()][Self::with_base] for the precision behavior.
114 ///
115 /// # Examples
116 ///
117 /// ```
118 /// # use core::str::FromStr;
119 /// # use dashu_base::ParseError;
120 /// # use dashu_float::{FBig, DBig};
121 /// use dashu_base::Approximation::*;
122 /// use dashu_float::round::Rounding::*;
123 ///
124 /// type Real = FBig;
125 ///
126 /// assert_eq!(
127 /// Real::from_str("0x1234")?.to_decimal(),
128 /// Exact(DBig::from_str("4660")?)
129 /// );
130 /// assert_eq!(
131 /// Real::from_str("0x12.34")?.to_decimal(),
132 /// Inexact(DBig::from_str("18.20")?, NoOp)
133 /// );
134 /// assert_eq!(
135 /// Real::from_str("0x1.234p-4")?.to_decimal(),
136 /// Inexact(DBig::from_str("0.07111")?, AddOne)
137 /// );
138 /// # Ok::<(), ParseError>(())
139 /// ```
140 ///
141 /// # Panics
142 ///
143 /// Panics if the associated context has unlimited precision and the conversion
144 /// cannot be performed losslessly.
145 #[inline]
146 pub fn to_decimal(&self) -> Rounded<FBig<HalfAway, 10>> {
147 self.clone().with_rounding().with_base::<10>()
148 }
149
150 /// Convert the float number to base 2 (with binary exponents) rounding towards zero.
151 ///
152 /// It's equivalent to `self.with_rounding::<Zero>().with_base::<2>()`.
153 ///
154 /// See [with_base()][Self::with_base] for the precision and rounding behavior.
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// # use core::str::FromStr;
160 /// # use dashu_base::ParseError;
161 /// # use dashu_float::{FBig, DBig};
162 /// use dashu_base::Approximation::*;
163 /// use dashu_float::round::{mode::HalfAway, Rounding::*};
164 ///
165 /// type Real = FBig;
166 ///
167 /// assert_eq!(
168 /// DBig::from_str("1234")?.to_binary(),
169 /// Exact(Real::from_str("0x4d2")?)
170 /// );
171 /// assert_eq!(
172 /// DBig::from_str("12.34")?.to_binary(),
173 /// Inexact(Real::from_str("0xc.57")?, NoOp)
174 /// );
175 /// assert_eq!(
176 /// DBig::from_str("1.234e-1")?.to_binary(),
177 /// Inexact(Real::from_str("0x1.f97p-4")?, NoOp)
178 /// );
179 /// # Ok::<(), ParseError>(())
180 /// ```
181 ///
182 /// # Panics
183 ///
184 /// Panics if the associated context has unlimited precision and the conversion
185 /// cannot be performed losslessly.
186 #[inline]
187 pub fn to_binary(&self) -> Rounded<FBig<Zero, 2>> {
188 self.clone().with_rounding().with_base::<2>()
189 }
190
191 /// Explicitly change the precision of the float number.
192 ///
193 /// If the given precision is less than the current value in the context,
194 /// it will be rounded with the rounding mode specified by the generic parameter.
195 ///
196 /// # Examples
197 ///
198 /// ```rust
199 /// # use core::str::FromStr;
200 /// # use dashu_base::ParseError;
201 /// # use dashu_float::{FBig, DBig};
202 /// use dashu_base::Approximation::*;
203 /// use dashu_float::round::{mode::HalfAway, Rounding::*};
204 ///
205 /// let a = DBig::from_str("2.345")?;
206 /// assert_eq!(a.precision(), 4);
207 /// assert_eq!(
208 /// a.clone().with_precision(3),
209 /// Inexact(DBig::from_str("2.35")?, AddOne)
210 /// );
211 /// assert_eq!(
212 /// a.clone().with_precision(5),
213 /// Exact(DBig::from_str("2.345")?)
214 /// );
215 /// # Ok::<(), ParseError>(())
216 /// ```
217 #[inline]
218 pub fn with_precision(self, precision: usize) -> Rounded<Self> {
219 let new_context = Context::new(precision);
220
221 // shrink if necessary
222 let repr = if self.context.precision > precision {
223 // it also handles unlimited precision
224 new_context.repr_round(self.repr)
225 } else {
226 Exact(self.repr)
227 };
228
229 repr.map(|v| Self::new(v, new_context))
230 }
231
232 /// Explicitly change the rounding mode of the number.
233 ///
234 /// This operation doesn't modify the underlying representation, it only changes
235 /// the rounding mode in the context.
236 ///
237 /// # Examples
238 ///
239 /// ```rust
240 /// # use core::str::FromStr;
241 /// # use dashu_base::ParseError;
242 /// # use dashu_float::{FBig, DBig};
243 /// use dashu_base::Approximation::*;
244 /// use dashu_float::round::{mode::{HalfAway, Zero}, Rounding::*};
245 ///
246 /// type DBigHalfAway = DBig;
247 /// type DBigZero = FBig::<Zero, 10>;
248 ///
249 /// let a = DBigHalfAway::from_str("2.345")?;
250 /// let b = DBigZero::from_str("2.345")?;
251 /// assert_eq!(a.with_rounding::<Zero>(), b);
252 /// # Ok::<(), ParseError>(())
253 /// ```
254 #[inline]
255 pub fn with_rounding<NewR: Round>(self) -> FBig<NewR, B> {
256 FBig {
257 repr: self.repr,
258 context: Context::new(self.context.precision),
259 }
260 }
261
262 /// Explicitly change the base of the float number.
263 ///
264 /// This function internally calls [with_base_and_precision][Self::with_base_and_precision].
265 /// The precision of the result number will be calculated in such a way that the new
266 /// limit of the significand is less than or equal to before. That is, the new precision
267 /// will be the max integer such that
268 ///
269 /// `NewB ^ new_precision <= B ^ old_precision`
270 ///
271 /// If any rounding happens during the conversion, it follows the rounding mode specified
272 /// by the generic parameter.
273 ///
274 /// # Examples
275 ///
276 /// ```rust
277 /// # use core::str::FromStr;
278 /// # use dashu_base::ParseError;
279 /// # use dashu_float::{FBig, DBig};
280 /// use dashu_base::Approximation::*;
281 /// use dashu_float::round::{mode::Zero, Rounding::*};
282 ///
283 /// type FBin = FBig;
284 /// type FDec = FBig<Zero, 10>;
285 /// type FHex = FBig<Zero, 16>;
286 ///
287 /// let a = FBin::from_str("0x1.234")?; // 0x1234 * 2^-12
288 /// assert_eq!(
289 /// a.clone().with_base::<10>(),
290 /// // 1.1376953125 rounded towards zero
291 /// Inexact(FDec::from_str("1.137")?, NoOp)
292 /// );
293 /// assert_eq!(
294 /// a.clone().with_base::<16>(),
295 /// // conversion is exact when the new base is a power of the old base
296 /// Exact(FHex::from_str("1.234")?)
297 /// );
298 /// # Ok::<(), ParseError>(())
299 /// ```
300 ///
301 /// # Panics
302 ///
303 /// Panics if the associated context has unlimited precision and the conversion
304 /// cannot be performed losslessly.
305 #[inline]
306 #[allow(non_upper_case_globals)]
307 pub fn with_base<const NewB: Word>(self) -> Rounded<FBig<R, NewB>> {
308 // if self.context.precision is zero, then precision is also zero
309 let precision =
310 Repr::<B>::BASE.pow(self.context.precision).log2_bounds().0 / NewB.log2_bounds().1;
311 self.with_base_and_precision(precision as usize)
312 }
313
314 /// Explicitly change the base of the float number with given precision (under the new base).
315 ///
316 /// Infinities are mapped to infinities inexactly, the error will be [NoOp][Rounding::NoOp].
317 ///
318 /// Conversion for float numbers with unlimited precision is only allowed in following cases:
319 /// - The number is infinite
320 /// - The new base NewB is a power of B
321 /// - B is a power of the new base NewB
322 ///
323 /// # Examples
324 ///
325 /// ```rust
326 /// # use core::str::FromStr;
327 /// # use dashu_base::ParseError;
328 /// # use dashu_float::{FBig, DBig};
329 /// use dashu_base::Approximation::*;
330 /// use dashu_float::round::{mode::Zero, Rounding::*};
331 ///
332 /// type FBin = FBig;
333 /// type FDec = FBig<Zero, 10>;
334 /// type FHex = FBig<Zero, 16>;
335 ///
336 /// let a = FBin::from_str("0x1.234")?; // 0x1234 * 2^-12
337 /// assert_eq!(
338 /// a.clone().with_base_and_precision::<10>(8),
339 /// // 1.1376953125 rounded towards zero
340 /// Inexact(FDec::from_str("1.1376953")?, NoOp)
341 /// );
342 /// assert_eq!(
343 /// a.clone().with_base_and_precision::<16>(8),
344 /// // conversion can be exact when the new base is a power of the old base
345 /// Exact(FHex::from_str("1.234")?)
346 /// );
347 /// assert_eq!(
348 /// a.clone().with_base_and_precision::<16>(2),
349 /// // but the conversion is still inexact if the target precision is smaller
350 /// Inexact(FHex::from_str("1.2")?, NoOp)
351 /// );
352 /// # Ok::<(), ParseError>(())
353 /// ```
354 ///
355 /// # Panics
356 ///
357 /// Panics if the associated context has unlimited precision and the conversion
358 /// cannot be performed losslessly.
359 #[allow(non_upper_case_globals)]
360 #[inline]
361 pub fn with_base_and_precision<const NewB: Word>(
362 self,
363 precision: usize,
364 ) -> Rounded<FBig<R, NewB>> {
365 let context = Context::<R>::new(precision);
366 context
367 .convert_base(self.repr, None)
368 .map(|repr| FBig::new(repr, context))
369 }
370
371 /// Convert the float number to integer with the given rounding mode.
372 ///
373 /// # Warning
374 ///
375 /// If the float number has a very large exponent, it will be evaluated and result
376 /// in allocating an huge integer and it might eat up all your memory.
377 ///
378 /// To get a rough idea of how big the number is, it's recommended to use [EstimatedLog2].
379 ///
380 /// # Examples
381 ///
382 /// ```
383 /// # use core::str::FromStr;
384 /// # use dashu_base::ParseError;
385 /// # use dashu_float::{FBig, DBig};
386 /// use dashu_base::Approximation::*;
387 /// use dashu_float::round::Rounding::*;
388 ///
389 /// assert_eq!(
390 /// DBig::from_str("1234")?.to_int(),
391 /// Exact(1234.into())
392 /// );
393 /// assert_eq!(
394 /// DBig::from_str("1.234e6")?.to_int(),
395 /// Exact(1234000.into())
396 /// );
397 /// assert_eq!(
398 /// DBig::from_str("1.234")?.to_int(),
399 /// Inexact(1.into(), NoOp)
400 /// );
401 /// # Ok::<(), ParseError>(())
402 /// ```
403 ///
404 /// # Panics
405 ///
406 /// Panics if the number is infinte
407 pub fn to_int(&self) -> Rounded<IBig> {
408 assert_finite(&self.repr);
409
410 // shortcut when the number is already an integer
411 if self.repr.exponent >= 0 {
412 return Exact(shl_digits::<B>(&self.repr.significand, self.repr.exponent as usize));
413 }
414
415 let (hi, lo, precision) = self.split_at_point_internal();
416 let adjust = R::round_fract::<B>(&hi, lo, precision);
417 Inexact(hi + adjust, adjust)
418 }
419
420 /// Convert the float number to [f32] with the rounding mode associated with the type.
421 ///
422 /// Note that the conversion is inexact even if the number is infinite.
423 ///
424 /// # Examples
425 ///
426 /// ```
427 /// # use core::str::FromStr;
428 /// # use dashu_base::ParseError;
429 /// # use dashu_float::DBig;
430 /// assert_eq!(DBig::from_str("1.234")?.to_f32().value(), 1.234);
431 /// assert_eq!(DBig::INFINITY.to_f32().value(), f32::INFINITY);
432 /// # Ok::<(), ParseError>(())
433 /// ```
434 #[inline]
435 pub fn to_f32(&self) -> Rounded<f32> {
436 Context::<R>::convert_to_f32(self.repr.clone())
437 }
438
439 /// Convert the float number to [f64] with the rounding mode associated with the type.
440 ///
441 /// Note that the conversion is inexact even if the number is infinite.
442 ///
443 /// # Examples
444 ///
445 /// ```
446 /// # use core::str::FromStr;
447 /// # use dashu_base::ParseError;
448 /// # use dashu_float::DBig;
449 /// assert_eq!(DBig::from_str("1.234")?.to_f64().value(), 1.234);
450 /// assert_eq!(DBig::INFINITY.to_f64().value(), f64::INFINITY);
451 /// # Ok::<(), ParseError>(())
452 /// ```
453 #[inline]
454 pub fn to_f64(&self) -> Rounded<f64> {
455 Context::<R>::convert_to_f64(self.repr.clone())
456 }
457}
458
459/// `isize` exponent arithmetic overflowed during base conversion: the value's magnitude
460/// falls outside the representable exponent range, so the result is ±infinity (`large`) or
461/// ±0 (`!large`). Mirrors the convention `convert_base` already uses in its division path,
462/// keeping the conversion overflow-safe (no panic) at the value level.
463#[allow(non_upper_case_globals)]
464fn converted_overflow_repr<const NewB: Word>(large: bool, sign: Sign) -> Rounded<Repr<NewB>> {
465 Inexact(
466 if large {
467 Repr::<NewB>::infinity_with_sign(sign)
468 } else {
469 Repr::<NewB>::zero_with_sign(sign)
470 },
471 Rounding::NoOp,
472 )
473}
474
475/// Number of significant bits a binary float format keeps for a value whose most-significant bit
476/// sits at position `msb`: `max_bits` across the normal range, but fewer for subnormals, whose
477/// spacing is fixed at `2^subnormal_exp` (e.g. `2^-1074` for f64, `2^-149` for f32). Rounding the
478/// source straight to this width lets the bit-encoding step avoid a second rounding, which would
479/// otherwise double-round subnormals.
480fn significand_bits(v: &Repr<2>, max_bits: usize, subnormal_exp: isize) -> usize {
481 if v.significand.is_zero() {
482 return max_bits;
483 }
484 let msb = v.exponent + v.digits() as isize - 1;
485 (msb - subnormal_exp + 1).clamp(1, max_bits as isize) as usize
486}
487
488/// Convert `repr` to base 2 and truncate to `width` significant bits, forcing the lowest kept bit
489/// to 1 whenever the tail is nonzero (round-to-odd). Rounding this down to any width up to
490/// `width - 2` reproduces the correctly-rounded value for every rounding mode, so the two-step
491/// "convert, then round to the final width" cannot double-round. `width` is fixed and generous, so
492/// the base-conversion logarithm stays accurate even when the final width is tiny (deep subnormals).
493#[allow(non_upper_case_globals)]
494fn convert_base_odd<const B: Word>(repr: Repr<B>, width: usize) -> Repr<2> {
495 match Context::<Zero>::new(width).convert_base::<B, 2>(repr, None) {
496 Exact(v) => v,
497 Inexact(v, _) if v.significand.is_zero() => v,
498 Inexact(v, _) => {
499 let shift = width - v.digits();
500 let (sign, mut mag) = v.significand.into_parts();
501 mag <<= shift;
502 mag.set_bit(0);
503 Repr::new(IBig::from_parts(sign, mag), v.exponent - shift as isize)
504 }
505 }
506}
507
508impl<R: Round> Context<R> {
509 // Convert `repr` (base B) to the nearest f64 under this context's rounding mode. A generous
510 // round-to-odd base conversion is rounded once to the target's precision at its own magnitude
511 // (fewer than 53 bits for subnormals), so `into_f64_internal` re-rounds nothing — which would
512 // otherwise double-round subnormals. Handles a source significand of any size.
513 fn convert_to_f64<const B: Word>(repr: Repr<B>) -> Rounded<f64> {
514 if repr.is_infinite() {
515 return Inexact(repr.sign() * f64::INFINITY, Rounding::NoOp);
516 }
517 let odd = convert_base_odd::<B>(repr, 60);
518 let bits = significand_bits(&odd, 53, -1074);
519 Context::<R>::new(bits)
520 .repr_round(odd)
521 .and_then(|v| v.into_f64_internal())
522 }
523
524 // [convert_to_f64] for f32.
525 fn convert_to_f32<const B: Word>(repr: Repr<B>) -> Rounded<f32> {
526 if repr.is_infinite() {
527 return Inexact(repr.sign() * f32::INFINITY, Rounding::NoOp);
528 }
529 let odd = convert_base_odd::<B>(repr, 32);
530 let bits = significand_bits(&odd, 24, -149);
531 Context::<R>::new(bits)
532 .repr_round(odd)
533 .and_then(|v| v.into_f32_internal())
534 }
535
536 // Convert the [Repr] from base B to base NewB, with the precision under the target base from this context.
537 #[allow(non_upper_case_globals)]
538 fn convert_base<const B: Word, const NewB: Word>(
539 &self,
540 repr: Repr<B>,
541 mut cache: Option<&mut ConstCache>,
542 ) -> Rounded<Repr<NewB>> {
543 // shortcut if NewB is the same as B
544 if NewB == B {
545 return Exact(Repr {
546 significand: repr.significand,
547 exponent: repr.exponent,
548 });
549 }
550
551 // shortcut for infinities, no rounding happens but the result is inexact
552 if repr.is_infinite() {
553 return Inexact(
554 Repr {
555 significand: repr.significand,
556 exponent: repr.exponent,
557 },
558 Rounding::NoOp,
559 );
560 }
561
562 if NewB > B {
563 // shortcut if NewB is a power of B
564 let n = ilog_exact(NewB, B);
565 if n > 1 {
566 let (exp, rem) = repr.exponent.div_rem_euclid(n as isize);
567 let signif = repr.significand * B.pow(rem as u32);
568 let repr = Repr::new(signif, exp);
569 return self.repr_round(repr);
570 }
571 } else {
572 // shortcut if B is a power of NewB
573 let n = ilog_exact(B, NewB);
574 if n > 1 {
575 let exp = match repr.exponent.checked_mul(n as isize) {
576 Some(e) => e,
577 None => return converted_overflow_repr::<NewB>(repr.exponent > 0, repr.sign()),
578 };
579 return Exact(Repr::new(repr.significand, exp));
580 }
581 }
582
583 // Shortcut: when B and NewB share common factors, factor out the common part.
584 // B = NewB^a * r where gcd(r, NewB) = 1, so B^exp = NewB^(a*exp) * r^exp.
585 // For positive exponents the result is always exact (integer multiplication).
586 // For negative exponents, exact only when r^|exp| divides the significand.
587 let (a, r) = factor_base(B, NewB);
588 if a > 0 && r > 1 {
589 if repr.exponent >= 0 {
590 let sign = repr.sign();
591 let r_exp = UBig::from_word(r).pow(repr.exponent as usize);
592 let significand = repr.significand * r_exp;
593 let exp = match (a as isize).checked_mul(repr.exponent) {
594 Some(e) => e,
595 None => return converted_overflow_repr::<NewB>(true, sign),
596 };
597 let new_repr = Repr::<NewB>::new(significand, exp);
598 return self.repr_round(new_repr);
599 } else {
600 let r_exp: IBig = UBig::from_word(r).pow((-repr.exponent) as usize).into();
601 if repr.significand.is_multiple_of(&r_exp) {
602 let exp = match (a as isize).checked_mul(repr.exponent) {
603 Some(e) => e,
604 None => return converted_overflow_repr::<NewB>(false, repr.sign()),
605 };
606 let new_repr = Repr::<NewB>::new(repr.significand / r_exp, exp);
607 return self.repr_round(new_repr);
608 }
609 }
610 }
611
612 // When NewB is a multiple of B: compute significand * B^exp directly
613 // as an integer, then express in base NewB.
614 if NewB % B == 0 && repr.exponent >= 0 {
615 let signif = repr.significand * Repr::<B>::BASE.pow(repr.exponent as usize);
616 let new_repr = Repr::<NewB>::new(signif, 0);
617 return self.repr_round(new_repr);
618 }
619
620 // if the base cannot be converted losslessly, the precision must be set
621 if self.precision == 0 {
622 panic_unlimited_precision();
623 }
624
625 // choose a exponent threshold such that number with exponent smaller than this value
626 // will be converted by directly evaluating the power. The threshold here is chosen such
627 // that the power under base 10 will fit in a double word.
628 const THRESHOLD_SMALL_EXP: isize = (Word::BITS as f32 * 0.60206) as isize; // word bits * 2 / log2(10)
629 if repr.exponent.abs() <= THRESHOLD_SMALL_EXP {
630 // if the exponent is small enough, directly evaluate the exponent
631 if repr.exponent >= 0 {
632 let signif = repr.significand * Repr::<B>::BASE.pow(repr.exponent as usize);
633 Exact(Repr::new(signif, 0))
634 } else {
635 let den: Repr<NewB> =
636 Repr::new(Repr::<B>::BASE.pow(-repr.exponent as usize).into(), 0);
637 // repr_div requires the dividend to be no wider than `precision + divisor`, so
638 // pre-shrink the significand the same way Context::div does — the caller, not
639 // the kernel, is responsible for bounding the dividend. Rounding it to
640 // `den.digits() + precision` preserves enough information for the division to
641 // be correctly rounded at `precision`.
642 let num: Repr<NewB> = Repr::new(repr.significand, 0);
643 let num =
644 if !num.is_pos_zero() && num.digits_ub() > den.digits_lb() + self.precision {
645 Self::new(den.digits() + self.precision)
646 .repr_round_ref(&num)
647 .value()
648 } else {
649 num
650 };
651 match self.repr_div(num, den) {
652 Ok(v) => v.map(|r: Repr<NewB>| Repr {
653 significand: r.significand,
654 exponent: r.exponent,
655 }),
656 Err(FpError::Overflow(sign)) => {
657 Inexact(Repr::<NewB>::infinity_with_sign(sign), Rounding::NoOp)
658 }
659 Err(FpError::Underflow(sign)) => {
660 Inexact(Repr::<NewB>::zero_with_sign(sign), Rounding::NoOp)
661 }
662 Err(_) => unreachable!(),
663 }
664 }
665 } else {
666 // if the exponent is large, then we first estimate the result exponent as floor(exponent * log(B) / log(NewB)),
667 // then the fractional part is multiplied with the original significand
668 let work_context = Context::<R>::new(2 * self.precision); // double the precision to get the precise logarithm
669 let new_exp = repr.exponent
670 * work_context.unwrap_fp(
671 work_context
672 .ln(&Repr::new(Repr::<B>::BASE.into(), 0), reborrow_cache(&mut cache)),
673 );
674 let (exponent, rem) =
675 new_exp.div_rem_euclid(work_context.ln_base::<NewB>(reborrow_cache(&mut cache)));
676 let exponent_sign = exponent.sign();
677 let exponent: isize = match exponent.try_into() {
678 Ok(v) => v,
679 Err(_) => {
680 return converted_overflow_repr::<NewB>(
681 exponent_sign == Sign::Positive,
682 repr.sign(),
683 );
684 }
685 };
686 let exp_rem = rem.exp();
687 let significand = repr.significand * exp_rem.repr.significand;
688 let repr = Repr::new(significand, exponent + exp_rem.repr.exponent);
689 self.repr_round(repr)
690 }
691 }
692}
693
694impl<const B: Word> Repr<B> {
695 // this method requires that the representation is already rounded to 24 binary bits
696 fn into_f32_internal(self) -> Rounded<f32> {
697 assert!(B == 2);
698 debug_assert!(self.is_finite());
699 debug_assert!(self.significand.bit_len() <= 24);
700
701 let sign = self.sign();
702 if self.is_neg_zero() {
703 // encode() would drop the sign of -0; preserve it exactly
704 return Exact(sign * 0f32);
705 }
706 let man24: i32 = self.significand.try_into().unwrap();
707 if self.exponent >= 128 {
708 // max f32 = 2^128 * (1 - 2^-24)
709 match sign {
710 Sign::Positive => Inexact(f32::INFINITY, Rounding::AddOne),
711 Sign::Negative => Inexact(f32::NEG_INFINITY, Rounding::SubOne),
712 }
713 } else if self.exponent < -149 - 24 {
714 // min f32 = 2^-149
715 Inexact(sign * 0f32, Rounding::NoOp)
716 } else {
717 match f32::encode(man24, self.exponent as i16) {
718 Exact(v) => Exact(v),
719 // this branch only happens when the result underflows
720 Inexact(v, _) => Inexact(v, Rounding::NoOp),
721 }
722 }
723 }
724
725 /// Convert the float number representation to a [f32] with the default IEEE 754 rounding mode.
726 ///
727 /// The default IEEE 754 rounding mode is [HalfEven] (rounding to nearest, ties to even). To convert
728 /// the float number with a specific rounding mode, please use [FBig::to_f32].
729 ///
730 /// # Examples
731 ///
732 /// ```
733 /// # use dashu_base::Approximation::*;
734 /// # use dashu_float::{Repr, round::Rounding::*};
735 /// assert_eq!(Repr::<2>::one().to_f32(), Exact(1.0));
736 /// assert_eq!(Repr::<10>::infinity().to_f32(), Inexact(f32::INFINITY, NoOp));
737 /// ```
738 #[inline]
739 pub fn to_f32(&self) -> Rounded<f32> {
740 Context::<HalfEven>::convert_to_f32(self.clone())
741 }
742
743 // this method requires that the representation is already rounded to 53 binary bits
744 fn into_f64_internal(self) -> Rounded<f64> {
745 assert!(B == 2);
746 debug_assert!(self.is_finite());
747 debug_assert!(self.significand.bit_len() <= 53);
748
749 let sign = self.sign();
750 if self.is_neg_zero() {
751 // encode() would drop the sign of -0; preserve it exactly
752 return Exact(sign * 0f64);
753 }
754 let man53: i64 = self.significand.try_into().unwrap();
755 if self.exponent >= 1024 {
756 // max f64 = 2^1024 × (1 − 2^−53)
757 match sign {
758 Sign::Positive => Inexact(f64::INFINITY, Rounding::AddOne),
759 Sign::Negative => Inexact(f64::NEG_INFINITY, Rounding::SubOne),
760 }
761 } else if self.exponent < -1074 - 53 {
762 // min f64 = 2^-1074
763 Inexact(sign * 0f64, Rounding::NoOp)
764 } else {
765 match f64::encode(man53, self.exponent as i16) {
766 Exact(v) => Exact(v),
767 // this branch only happens when the result underflows
768 Inexact(v, _) => Inexact(v, Rounding::NoOp),
769 }
770 }
771 }
772
773 /// Convert the float number representation to a [f64] with the default IEEE 754 rounding mode.
774 ///
775 /// The default IEEE 754 rounding mode is [HalfEven] (rounding to nearest, ties to even). To convert
776 /// the float number with a specific rounding mode, please use [FBig::to_f64].
777 ///
778 /// # Examples
779 ///
780 /// ```
781 /// # use dashu_base::Approximation::*;
782 /// # use dashu_float::{Repr, round::Rounding::*};
783 /// assert_eq!(Repr::<2>::one().to_f64(), Exact(1.0));
784 /// assert_eq!(Repr::<10>::infinity().to_f64(), Inexact(f64::INFINITY, NoOp));
785 /// ```
786 #[inline]
787 pub fn to_f64(&self) -> Rounded<f64> {
788 Context::<HalfEven>::convert_to_f64(self.clone())
789 }
790
791 /// Convert the float number representation to a [IBig].
792 ///
793 /// The fractional part is always rounded to zero. To convert with other rounding modes,
794 /// please use [FBig::to_int()].
795 ///
796 /// # Warning
797 ///
798 /// If the float number has a very large exponent, it will be evaluated and result
799 /// in allocating an huge integer and it might eat up all your memory.
800 ///
801 /// To get a rough idea of how big the number is, it's recommended to use [EstimatedLog2].
802 ///
803 /// # Examples
804 ///
805 /// ```
806 /// # use dashu_base::Approximation::*;
807 /// # use dashu_int::IBig;
808 /// # use dashu_float::{Repr, round::Rounding::*};
809 /// assert_eq!(Repr::<2>::neg_one().to_int(), Exact(IBig::NEG_ONE));
810 /// ```
811 ///
812 /// # Panics
813 ///
814 /// Panics if the number is infinte.
815 pub fn to_int(&self) -> Rounded<IBig> {
816 assert_finite(self);
817
818 if self.exponent >= 0 {
819 // the number is already an integer
820 Exact(shl_digits::<B>(&self.significand, self.exponent as usize))
821 } else if self.smaller_than_one() {
822 // the number is definitely smaller than
823 Inexact(IBig::ZERO, Rounding::NoOp)
824 } else {
825 let int = shr_digits::<B>(&self.significand, (-self.exponent) as usize);
826 Inexact(int, Rounding::NoOp)
827 }
828 }
829}
830
831impl<const B: Word> From<UBig> for Repr<B> {
832 #[inline]
833 fn from(n: UBig) -> Self {
834 Self::new(n.into(), 0)
835 }
836}
837impl<R: Round, const B: Word> From<UBig> for FBig<R, B> {
838 #[inline]
839 fn from(n: UBig) -> Self {
840 Self::from_parts(n.into(), 0)
841 }
842}
843
844impl<const B: Word> From<IBig> for Repr<B> {
845 #[inline]
846 fn from(n: IBig) -> Self {
847 Self::new(n, 0)
848 }
849}
850impl<R: Round, const B: Word> From<IBig> for FBig<R, B> {
851 #[inline]
852 fn from(n: IBig) -> Self {
853 Self::from_parts(n, 0)
854 }
855}
856
857impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for IBig {
858 type Error = ConversionError;
859
860 #[inline]
861 fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
862 if value.repr.is_infinite() {
863 Err(ConversionError::OutOfBounds)
864 } else if value.repr.significand.is_zero() {
865 // A zero significand is integer zero regardless of exponent. This also
866 // accepts IEEE-754 signed zero, whose sign is carried by a -1 exponent
867 // sentinel (not the significand); it is treated as plain 0. The zero
868 // must be handled here rather than in the `else` branch below, which
869 // shifts by `exponent as usize` and would underflow on the -1 sentinel.
870 Ok(value.repr.significand)
871 } else if value.repr.exponent < 0 {
872 Err(ConversionError::LossOfPrecision)
873 } else {
874 let mut int = value.repr.significand;
875 shl_digits_in_place::<B>(&mut int, value.repr.exponent as usize);
876 Ok(int)
877 }
878 }
879}
880
881impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for UBig {
882 type Error = ConversionError;
883
884 #[inline]
885 fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
886 let int: IBig = value.try_into()?;
887 int.try_into()
888 }
889}
890
891macro_rules! fbig_unsigned_conversions {
892 ($($t:ty)*) => {$(
893 impl<const B: Word> From<$t> for Repr<B> {
894 #[inline]
895 fn from(value: $t) -> Repr<B> {
896 UBig::from(value).into()
897 }
898 }
899 impl<R: Round, const B: Word> From<$t> for FBig<R, B> {
900 #[inline]
901 fn from(value: $t) -> FBig<R, B> {
902 UBig::from(value).into()
903 }
904 }
905
906 impl<const B: Word> TryFrom<Repr<B>> for $t {
907 type Error = ConversionError;
908
909 fn try_from(value: Repr<B>) -> Result<Self, Self::Error> {
910 if value.sign() == Sign::Negative || value.is_infinite() {
911 Err(ConversionError::OutOfBounds)
912 } else {
913 let (log2_lb, _) = value.log2_bounds();
914 if log2_lb >= <$t>::BITS as f32 {
915 Err(ConversionError::OutOfBounds)
916 } else if value.exponent < 0 {
917 Err(ConversionError::LossOfPrecision)
918 } else {
919 shl_digits::<B>(&value.significand, value.exponent as usize).try_into()
920 }
921 }
922 }
923 }
924 impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
925 type Error = ConversionError;
926
927 #[inline]
928 fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
929 value.repr.try_into()
930 }
931 }
932 )*};
933}
934fbig_unsigned_conversions!(u8 u16 u32 u64 u128 usize);
935
936macro_rules! fbig_signed_conversions {
937 ($($t:ty)*) => {$(
938 impl<R: Round, const B: Word> From<$t> for FBig<R, B> {
939 #[inline]
940 fn from(value: $t) -> FBig<R, B> {
941 IBig::from(value).into()
942 }
943 }
944
945 impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
946 type Error = ConversionError;
947
948 fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
949 if value.repr.is_infinite() {
950 Err(ConversionError::OutOfBounds)
951 } else {
952 let (log2_lb, _) = value.repr.log2_bounds();
953 if log2_lb >= <$t>::BITS as f32 {
954 Err(ConversionError::OutOfBounds)
955 } else if value.repr.exponent < 0 {
956 Err(ConversionError::LossOfPrecision)
957 } else {
958 shl_digits::<B>(&value.repr.significand, value.repr.exponent as usize).try_into()
959 }
960 }
961 }
962 }
963 )*};
964}
965fbig_signed_conversions!(i8 i16 i32 i64 i128 isize);
966
967macro_rules! impl_from_fbig_for_float {
968 ($t:ty, $method:ident) => {
969 impl TryFrom<Repr<2>> for $t {
970 type Error = ConversionError;
971
972 #[inline]
973 fn try_from(value: Repr<2>) -> Result<Self, Self::Error> {
974 if value.is_infinite() {
975 Err(ConversionError::LossOfPrecision)
976 } else {
977 match value.$method() {
978 Exact(v) => Ok(v),
979 Inexact(v, _) => {
980 if v.is_infinite() {
981 Err(ConversionError::OutOfBounds)
982 } else {
983 Err(ConversionError::LossOfPrecision)
984 }
985 }
986 }
987 }
988 }
989 }
990
991 impl<R: Round> TryFrom<FBig<R, 2>> for $t {
992 type Error = ConversionError;
993
994 #[inline]
995 fn try_from(value: FBig<R, 2>) -> Result<Self, Self::Error> {
996 // this method is the same as the one for Repr, but it has to be re-implemented
997 // because the rounding behavior of to_32/to_64 is different.
998 if value.repr.is_infinite() {
999 Err(ConversionError::LossOfPrecision)
1000 } else {
1001 match value.$method() {
1002 Exact(v) => Ok(v),
1003 Inexact(v, _) => {
1004 if v.is_infinite() {
1005 Err(ConversionError::OutOfBounds)
1006 } else {
1007 Err(ConversionError::LossOfPrecision)
1008 }
1009 }
1010 }
1011 }
1012 }
1013 }
1014 };
1015}
1016impl_from_fbig_for_float!(f32, to_f32);
1017impl_from_fbig_for_float!(f64, to_f64);
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::*;
1022 use crate::repr::Repr;
1023
1024 #[test]
1025 fn ibig_try_from_accepts_signed_zero() {
1026 // IEEE-754 signed zero (sign encoded in a -1 exponent sentinel) is plain 0.
1027 let neg_zero = FBig::<HalfAway, 10>::new(Repr::neg_zero(), Context::new(8));
1028 assert_eq!(IBig::try_from(neg_zero), Ok(IBig::from(0)));
1029
1030 // positive zero already worked, and still does
1031 let pos_zero = FBig::<HalfAway, 10>::new(Repr::zero(), Context::new(8));
1032 assert_eq!(IBig::try_from(pos_zero), Ok(IBig::from(0)));
1033
1034 // UBig delegates to the IBig impl, so it accepts signed zero too
1035 let neg_zero = FBig::<HalfAway, 2>::new(Repr::neg_zero(), Context::new(8));
1036 assert_eq!(UBig::try_from(neg_zero), Ok(UBig::from(0u8)));
1037
1038 // a genuine fractional value must still be rejected
1039 let frac = FBig::<HalfAway, 10>::new(Repr::new(IBig::from(1), -1), Context::new(8));
1040 assert_eq!(IBig::try_from(frac), Err(ConversionError::LossOfPrecision));
1041
1042 // a normal integer round-trips exactly
1043 let int_val = FBig::<HalfAway, 10>::new(Repr::new(IBig::from(42), 0), Context::new(8));
1044 assert_eq!(IBig::try_from(int_val), Ok(IBig::from(42)));
1045 }
1046
1047 #[test]
1048 fn with_base_high_precision_no_overflow() {
1049 // Regression for issue #95: converting a high-precision base-2 float to base
1050 // 10 panicked on 32-bit targets ("arithmetic operations with the infinity are
1051 // not allowed!"). The base conversion evaluates exp(r) as `sum^(B^n)` through
1052 // `powi` with a huge exponent (B^n) on a base (sum) very close to 1; `powi`'s
1053 // overflow guard estimated log2(base) with the catastrophically-canceling
1054 // `log2_est`, and the ~1e-4 of f32 noise scaled by the exponent crossed the
1055 // (much smaller on 32-bit) isize threshold, yielding a spurious ±inf that then
1056 // panicked when shifted. See `powi` in exp.rs for the fix.
1057 use crate::round::mode::Zero;
1058 use core::str::FromStr;
1059
1060 // The reporter's input: -1.1111…0011 in binary (578 significant bits), written
1061 // in the hex form dashu accepts for base-2 floats (`0x1.<hex>…`). The value is
1062 // identical to the raw binary literal.
1063 let num = FBig::<Zero, 2>::from_str(
1064 "-0x1.fffdc8d645194a5a95df4be063472d4406dd096339dd7dc2a8527d208b3da7b9e5c36b4f49a7982cb2ad20a4e7e4c016f858fe8cddea011a6d01fe3823189c4ed4f57a7babc331498",
1065 )
1066 .unwrap();
1067
1068 // at the original 578-bit precision the conversion succeeds …
1069 let a = num
1070 .clone()
1071 .with_precision(578)
1072 .value()
1073 .with_base::<10>()
1074 .value();
1075 assert!(a.repr().is_finite());
1076 // … and so does a slightly higher precision (586), which panicked on 32-bit
1077 // (wasm32 / i686). The result matches the value computed on 64-bit. Compared
1078 // by value (FBig equality ignores context) rather than via string formatting,
1079 // so this works under no_std too.
1080 let b = num.with_precision(586).value().with_base::<10>().value();
1081 assert!(b.repr().is_finite());
1082 let expected = FBig::<Zero, 10>::from_str(
1083 "-1.9999661944503703041843468850635057967553124154072485151176192294480158424234268438137612977886891381228704640656094986435381057574477216648567249609280392009533217665484389886",
1084 )
1085 .unwrap();
1086 assert_eq!(b, expected);
1087 }
1088}