Skip to main content

oxinum_float/native/
float_convert.rs

1//! Conversions to and from primitive numeric types.
2//!
3//! Provides:
4//!
5//! - [`BigFloat::from_i64`] — exact-then-rounded integer encoding.
6//! - [`BigFloat::from_f64`] — exact decomposition of an IEEE-754 `f64` value.
7//! - [`BigFloat::to_f64`] — IEEE-754-correct, round-to-nearest-even encoding.
8//! - [`BigFloat::from_bigint`] — convert a [`BigInt`] to `BigFloat` at specified precision.
9//! - [`BigFloat::from_biguint`] — convert a [`BigUint`] to `BigFloat` at specified precision.
10
11use oxinum_core::{OxiNumError, OxiNumResult, Sign};
12use oxinum_int::native::{BigInt, BigUint};
13
14use super::float::{BigFloat, FloatClass, RoundingMode};
15
16// ---------------------------------------------------------------------------
17// BigFloat → BigInt conversions
18// ---------------------------------------------------------------------------
19
20impl BigFloat {
21    /// Returns `true` if this value has a non-zero fractional part
22    /// (i.e., is not an exact integer).
23    ///
24    /// Zero and values with `exponent >= 0` are always exact integers.
25    fn has_fractional_part(&self) -> bool {
26        if self.is_zero() || self.exponent >= 0 {
27            return false;
28        }
29        let shift = (-self.exponent) as u64;
30        // fractional bits live in the low `shift` bits of the mantissa.
31        // Any of those bits being set means there is a fractional part.
32        // Equivalent: trailing_zeros < shift.
33        self.mantissa.trailing_zeros() < shift
34    }
35
36    /// Returns `true` if the fractional part is exactly 1/2
37    /// (the half-bit is set and all lower bits are zero).
38    fn half_exactly(&self) -> bool {
39        if self.is_zero() || self.exponent >= 0 {
40            return false;
41        }
42        let shift = (-self.exponent) as u64;
43        // The half-bit is at position (shift - 1).
44        // Exactly 1/2: bit (shift-1) == 1 AND all bits below it are 0.
45        // i.e. trailing_zeros == shift - 1.
46        self.mantissa.test_bit(shift - 1) && self.mantissa.trailing_zeros() == shift - 1
47    }
48
49    /// Returns `true` if the fractional part is strictly greater than 1/2.
50    fn more_than_half(&self) -> bool {
51        if self.is_zero() || self.exponent >= 0 {
52            return false;
53        }
54        let shift = (-self.exponent) as u64;
55        // The half-bit is at position (shift - 1).
56        // More than 1/2: bit (shift-1) == 1 AND at least one lower bit is 1.
57        // i.e. trailing_zeros < shift - 1.
58        self.mantissa.test_bit(shift - 1) && self.mantissa.trailing_zeros() < shift - 1
59    }
60
61    /// Convert to [`BigInt`] by truncating toward zero (round-toward-zero).
62    ///
63    /// Returns the integer part of `self`, discarding any fractional bits.
64    ///
65    /// Non-finite values (NaN, ±Inf) have no integer representation.
66    /// This method returns `BigInt::zero()` as a documented lossy fallback
67    /// for those inputs; callers that may encounter non-finite values should
68    /// use [`BigFloat::is_finite`] to guard before calling.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use oxinum_float::native::BigFloat;
74    /// use oxinum_int::native::BigInt;
75    ///
76    /// let x = BigFloat::from_f64(3.7, 64).expect("3.7");
77    /// assert_eq!(x.to_bigint_trunc(), BigInt::from(3i64));
78    ///
79    /// let y = BigFloat::from_f64(-3.7, 64).expect("-3.7");
80    /// assert_eq!(y.to_bigint_trunc(), BigInt::from(-3i64));
81    ///
82    /// // Non-finite values return zero (lossy).
83    /// assert_eq!(BigFloat::nan(53).to_bigint_trunc(), BigInt::zero());
84    /// assert_eq!(BigFloat::infinity(53).to_bigint_trunc(), BigInt::zero());
85    /// ```
86    pub fn to_bigint_trunc(&self) -> BigInt {
87        // Non-finite BigFloat has no meaningful integer representation.
88        // Return zero as a documented lossy fallback for NaN and ±Inf.
89        if !self.is_finite() {
90            return BigInt::zero();
91        }
92        if self.is_zero() {
93            return BigInt::zero();
94        }
95        if self.exponent >= 0 {
96            // No fractional bits: value is mantissa * 2^exponent (integer).
97            let mag = self.mantissa.shl_bits(self.exponent as u64);
98            return BigInt::from_parts(self.sign, mag);
99        }
100        // Shift right to drop the fractional part.
101        let shift = (-self.exponent) as u64;
102        let mag = self.mantissa.shr_bits(shift);
103        if mag.is_zero() {
104            BigInt::zero()
105        } else {
106            BigInt::from_parts(self.sign, mag)
107        }
108    }
109
110    /// Convert to [`BigInt`] by rounding toward negative infinity (floor).
111    ///
112    /// For negative values with a non-zero fractional part, the result is
113    /// one less than the truncation (i.e. more negative).
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use oxinum_float::native::BigFloat;
119    /// use oxinum_int::native::BigInt;
120    ///
121    /// let x = BigFloat::from_f64(3.7, 64).expect("3.7");
122    /// assert_eq!(x.to_bigint_floor(), BigInt::from(3i64));
123    ///
124    /// let y = BigFloat::from_f64(-3.7, 64).expect("-3.7");
125    /// assert_eq!(y.to_bigint_floor(), BigInt::from(-4i64));
126    /// ```
127    pub fn to_bigint_floor(&self) -> BigInt {
128        let t = self.to_bigint_trunc();
129        if self.sign == Sign::Negative && self.has_fractional_part() {
130            &t - &BigInt::one()
131        } else {
132            t
133        }
134    }
135
136    /// Convert to [`BigInt`] by rounding toward positive infinity (ceiling).
137    ///
138    /// For positive values with a non-zero fractional part, the result is
139    /// one more than the truncation.
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// use oxinum_float::native::BigFloat;
145    /// use oxinum_int::native::BigInt;
146    ///
147    /// let x = BigFloat::from_f64(3.7, 64).expect("3.7");
148    /// assert_eq!(x.to_bigint_ceil(), BigInt::from(4i64));
149    ///
150    /// let y = BigFloat::from_f64(-3.7, 64).expect("-3.7");
151    /// assert_eq!(y.to_bigint_ceil(), BigInt::from(-3i64));
152    /// ```
153    pub fn to_bigint_ceil(&self) -> BigInt {
154        let t = self.to_bigint_trunc();
155        if self.sign == Sign::Positive && self.has_fractional_part() {
156            &t + &BigInt::one()
157        } else {
158            t
159        }
160    }
161
162    /// Convert to [`BigInt`] by rounding half-away-from-zero.
163    ///
164    /// - Fractional part < 1/2: truncate toward zero.
165    /// - Fractional part == 1/2: round away from zero (ties-away).
166    /// - Fractional part > 1/2: round away from zero.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use oxinum_float::native::BigFloat;
172    /// use oxinum_int::native::BigInt;
173    ///
174    /// let x = BigFloat::from_f64(3.7, 64).expect("3.7");
175    /// assert_eq!(x.to_bigint_round(), BigInt::from(4i64));
176    ///
177    /// let y = BigFloat::from_f64(-3.7, 64).expect("-3.7");
178    /// assert_eq!(y.to_bigint_round(), BigInt::from(-4i64));
179    /// ```
180    pub fn to_bigint_round(&self) -> BigInt {
181        if !self.has_fractional_part() {
182            return self.to_bigint_trunc();
183        }
184        // If fractional part >= 1/2 (half or more), round away from zero.
185        if self.half_exactly() || self.more_than_half() {
186            let t = self.to_bigint_trunc();
187            if self.sign == Sign::Positive {
188                &t + &BigInt::one()
189            } else {
190                &t - &BigInt::one()
191            }
192        } else {
193            // Fractional part < 1/2: truncate toward zero.
194            self.to_bigint_trunc()
195        }
196    }
197}
198
199impl BigFloat {
200    /// Encode the integer `n` as a `BigFloat` at `prec` bits of precision.
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// use oxinum_float::native::{BigFloat, RoundingMode};
206    /// let a = BigFloat::from_i64(-42, 16, RoundingMode::HalfEven);
207    /// assert_eq!(a.to_f64(), -42.0);
208    /// ```
209    pub fn from_i64(n: i64, prec: u32, mode: RoundingMode) -> Self {
210        assert!(prec > 0, "BigFloat precision must be > 0");
211        if n == 0 {
212            return Self::zero(prec);
213        }
214        let (sign, mag_u64) = if n < 0 {
215            // Avoid `-i64::MIN` overflow: take the two's-complement magnitude
216            // via wrapping_neg, which is exact in unsigned space.
217            (Sign::Negative, (n as i128).unsigned_abs() as u64)
218        } else {
219            (Sign::Positive, n as u64)
220        };
221        let mantissa = BigUint::from_u64(mag_u64);
222        Self::from_parts(sign, mantissa, 0, prec, mode)
223    }
224
225    /// Decompose an IEEE-754 `f64` value into a `BigFloat` at `prec` bits.
226    ///
227    /// At `prec >= 53` the decomposition is exact (no rounding occurs). The
228    /// rounding mode used for any narrowing is [`RoundingMode::HalfEven`].
229    ///
230    /// # Errors
231    ///
232    /// Returns [`OxiNumError::Parse`] if `x` is `NaN` or infinite — native
233    /// `BigFloat` does not yet model those special values.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// use oxinum_float::native::BigFloat;
239    /// let half = BigFloat::from_f64(0.5, 1).expect("0.5 fits in 1 bit");
240    /// assert_eq!(half.to_f64(), 0.5);
241    /// ```
242    pub fn from_f64(x: f64, prec: u32) -> OxiNumResult<Self> {
243        assert!(prec > 0, "BigFloat precision must be > 0");
244        if x.is_nan() {
245            return Err(OxiNumError::Parse("cannot encode NaN as BigFloat".into()));
246        }
247        if x.is_infinite() {
248            return Err(OxiNumError::Parse(
249                "cannot encode infinity as BigFloat".into(),
250            ));
251        }
252        if x == 0.0 {
253            // Both +0.0 and -0.0 land at canonical zero.
254            return Ok(Self::zero(prec));
255        }
256        // Decode the IEEE-754 layout.
257        let bits = x.to_bits();
258        let sign_bit = (bits >> 63) & 1;
259        let biased_exp = ((bits >> 52) & 0x7FF) as i64;
260        let fraction = bits & ((1u64 << 52) - 1);
261        let sign = if sign_bit == 1 {
262            Sign::Negative
263        } else {
264            Sign::Positive
265        };
266        let (mantissa_u64, unbiased_exp) = if biased_exp == 0 {
267            // Subnormal: no implicit leading bit, exponent fixed at -1074.
268            (fraction, -1074_i64)
269        } else {
270            // Normal: implicit leading bit at position 52, exponent unbiased.
271            (fraction | (1u64 << 52), biased_exp - 1023 - 52)
272        };
273        let mantissa = BigUint::from_u64(mantissa_u64);
274        Ok(Self::from_parts(
275            sign,
276            mantissa,
277            unbiased_exp,
278            prec,
279            RoundingMode::HalfEven,
280        ))
281    }
282
283    /// Round to the nearest [`f64`] (ties-to-even). Values whose magnitudes
284    /// exceed `f64::MAX` saturate to ±∞, and underflows round to ±0.
285    ///
286    /// # Examples
287    ///
288    /// ```
289    /// use oxinum_float::native::{BigFloat, RoundingMode};
290    /// let one = BigFloat::from_i64(1, 53, RoundingMode::HalfEven);
291    /// assert_eq!(one.to_f64(), 1.0);
292    /// ```
293    pub fn to_f64(&self) -> f64 {
294        // Non-finite values must be handled before the is_zero() check, since
295        // NaN and Inf have mantissa=0 and would otherwise fall through to the
296        // subnormal path, producing incorrect finite results.
297        match self.class {
298            FloatClass::Nan => return f64::NAN,
299            FloatClass::Infinite => {
300                return if self.sign == Sign::Negative {
301                    f64::NEG_INFINITY
302                } else {
303                    f64::INFINITY
304                };
305            }
306            FloatClass::Finite => {}
307        }
308        if self.is_zero() {
309            return 0.0;
310        }
311        // Pull out the magnitude of the mantissa and the exponent into a
312        // representation where the top bit is at position 52 (= number of
313        // fraction bits in an f64 normal).
314        let bits = self.mantissa.bit_length();
315        // Effective binary exponent of the value: exponent + (bit_length - 1)
316        // is the position of the top bit relative to the value's "1.xxx" form.
317        let top_bit_exp = self.exponent.saturating_add(bits as i64 - 1);
318        // f64 normals have unbiased exponent in [-1022, 1023].
319        // f64 subnormals have effective top-bit exponent down to -1074.
320        if top_bit_exp > 1023 {
321            return if self.sign == Sign::Negative {
322                f64::NEG_INFINITY
323            } else {
324                f64::INFINITY
325            };
326        }
327        if top_bit_exp < -1074 {
328            return if self.sign == Sign::Negative {
329                -0.0
330            } else {
331                0.0
332            };
333        }
334        // Strategy: produce a 64-bit representation `m` where the leading 1
335        // is at position 52 (or below for subnormal), then assemble the f64.
336        //
337        // Step 1: Normalize the mantissa into a `desired`-bit integer with
338        // round-to-nearest-even. The target precision for the f64 mantissa
339        // is `desired_mantissa_bits`:
340        //   - normal:    53 bits (top bit at position 52, then we strip the
341        //                implicit leading 1).
342        //   - subnormal: `top_bit_exp + 1075` bits — the integer mantissa
343        //                IS the stored fraction.
344        let desired_mantissa_bits: i64 = if top_bit_exp >= -1022 {
345            53
346        } else {
347            // top_bit_exp in [-1074, -1023] => 1..=52 bits.
348            top_bit_exp + 1075
349        };
350        let desired = desired_mantissa_bits as u64;
351        let mut tmp = self.clone();
352        // Round to `desired` bits (HalfEven).
353        tmp.round_to_precision_in_place(desired as u32, RoundingMode::HalfEven);
354        // After rounding, the carry-over case can promote a subnormal to
355        // the smallest normal. We re-derive `new_top` from the rounded
356        // mantissa and re-encode in the appropriate branch.
357        if tmp.is_zero() {
358            return if self.sign == Sign::Negative {
359                -0.0
360            } else {
361                0.0
362            };
363        }
364        let new_bits = tmp.mantissa.bit_length();
365        let new_top = tmp.exponent.saturating_add(new_bits as i64 - 1);
366        if new_top > 1023 {
367            return if self.sign == Sign::Negative {
368                f64::NEG_INFINITY
369            } else {
370                f64::INFINITY
371            };
372        }
373        let mantissa_full = match tmp.mantissa.to_u64() {
374            Some(m) => m,
375            None => {
376                return if self.sign == Sign::Negative {
377                    f64::NEG_INFINITY
378                } else {
379                    f64::INFINITY
380                };
381            }
382        };
383        let sign_bit: u64 = if self.sign == Sign::Negative { 1 } else { 0 };
384        if new_top >= -1022 {
385            // Normal. mantissa_full has 53 bits; top bit is the implicit 1,
386            // which we strip away.
387            let biased_exp = (new_top + 1023) as u64;
388            let fraction = mantissa_full & ((1u64 << 52) - 1);
389            let bits_out = (sign_bit << 63) | (biased_exp << 52) | fraction;
390            f64::from_bits(bits_out)
391        } else {
392            // Subnormal. The integer mantissa IS the fraction. mantissa_full
393            // has `new_top + 1075` bits, which fits in the 52 fraction bits.
394            let fraction = mantissa_full & ((1u64 << 52) - 1);
395            let bits_out = (sign_bit << 63) | fraction;
396            f64::from_bits(bits_out)
397        }
398    }
399}
400
401impl BigFloat {
402    /// Convert a [`BigInt`] (signed arbitrary-precision integer) to `BigFloat`
403    /// at `prec` bits of precision using the given rounding mode.
404    ///
405    /// The conversion is exact when `prec >= n.magnitude().bit_length()`;
406    /// otherwise the result is rounded according to `mode`.
407    ///
408    /// # Examples
409    ///
410    /// ```
411    /// use oxinum_float::native::{BigFloat, RoundingMode};
412    /// use oxinum_int::native::BigInt;
413    ///
414    /// let n = BigInt::from(-42i64);
415    /// let f = BigFloat::from_bigint(&n, 64, RoundingMode::HalfEven);
416    /// assert_eq!(f.to_f64(), -42.0);
417    /// ```
418    pub fn from_bigint(n: &BigInt, prec: u32, mode: RoundingMode) -> Self {
419        assert!(prec > 0, "BigFloat precision must be > 0");
420        let sign = n.sign();
421        let mag = n.magnitude().clone();
422        let f = Self::from_biguint(&mag, prec, mode);
423        if sign == Sign::Negative && !f.is_zero() {
424            f.neg()
425        } else {
426            f
427        }
428    }
429
430    /// Convert a [`BigUint`] (non-negative arbitrary-precision integer) to
431    /// `BigFloat` at `prec` bits of precision using the given rounding mode.
432    ///
433    /// The conversion is exact when `prec >= n.bit_length()`; otherwise the
434    /// result is rounded according to `mode`.
435    ///
436    /// # Examples
437    ///
438    /// ```
439    /// use oxinum_float::native::{BigFloat, RoundingMode};
440    /// use oxinum_int::native::BigUint;
441    ///
442    /// let n = BigUint::from_u64(1024);
443    /// let f = BigFloat::from_biguint(&n, 64, RoundingMode::HalfEven);
444    /// assert_eq!(f.to_f64(), 1024.0);
445    /// ```
446    pub fn from_biguint(n: &BigUint, prec: u32, mode: RoundingMode) -> Self {
447        assert!(prec > 0, "BigFloat precision must be > 0");
448        if n.is_zero() {
449            return Self::zero(prec);
450        }
451        // `from_parts` calls `canonicalize_normalize` then `round_to_precision_in_place`,
452        // which handles all the bit-length vs prec casework for us.
453        Self::from_parts(Sign::Positive, n.clone(), 0, prec, mode)
454    }
455}
456
457impl From<i64> for BigFloat {
458    /// Encodes `n` at 64 bits of precision with banker's rounding.
459    ///
460    /// Use [`BigFloat::from_i64`] for explicit precision control.
461    fn from(n: i64) -> Self {
462        Self::from_i64(n, 64, RoundingMode::HalfEven)
463    }
464}
465
466impl TryFrom<f64> for BigFloat {
467    type Error = OxiNumError;
468    fn try_from(x: f64) -> Result<Self, Self::Error> {
469        // 53 bits captures every finite double exactly.
470        Self::from_f64(x, 53)
471    }
472}