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}