Skip to content

Commit 4c88621

Browse files
committed
Fix ldexp to prevent math range errors
Implemented IEEE 754-compliant handling for large numbers to avoid overflow and represent results using scientific notation. Fixes #5471 Signed-off-by: Yash Suthar <[email protected]>
1 parent 9825d8a commit 4c88621

File tree

1 file changed

+58
-4
lines changed

1 file changed

+58
-4
lines changed

stdlib/src/math.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ mod math {
1212
};
1313
use itertools::Itertools;
1414
use malachite_bigint::BigInt;
15-
use num_traits::{One, Signed, Zero};
15+
use num_traits::{One, Signed, ToPrimitive, Zero};
1616
use rustpython_common::{float_ops, int::true_div};
1717
use std::cmp::Ordering;
1818

@@ -563,11 +563,65 @@ mod math {
563563

564564
if value == 0_f64 || !value.is_finite() {
565565
// NaNs, zeros and infinities are returned unchanged
566-
Ok(value)
566+
return Ok(value);
567+
}
568+
569+
// Using IEEE 754 bit manipulation to handle large exponents correctly.
570+
// Direct multiplication would overflow for large i values, especially when computing
571+
// the largest finite float (i=1024, x<1.0). By directly modifying the exponent bits,
572+
// we avoid intermediate overflow to infinity.
573+
let (mant, exp0) = float_ops::decompose_float(value);
574+
575+
let i_big = i.as_bigint();
576+
let overflow_bound = BigInt::from(1024_i32 - exp0); // i > 1024 - exp0 => overflow
577+
if i_big > &overflow_bound {
578+
return Err(vm.new_overflow_error("math range error"));
579+
}
580+
if i_big == &overflow_bound && mant == 1.0 {
581+
return Err(vm.new_overflow_error("math range error"));
582+
}
583+
let underflow_bound = BigInt::from(-1074_i32 - exp0); // i < -1074 - exp0 => 0.0 with sign
584+
if i_big < &underflow_bound {
585+
return Ok(0.0f64.copysign(value));
586+
}
587+
588+
let i_small: i32 = i_big
589+
.to_i32()
590+
.expect("exponent within [-1074-exp0, 1024-exp0] must fit in i32");
591+
let exp = exp0 + i_small;
592+
593+
const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
594+
const FRAC_MASK: u64 = 0x000F_FFFF_FFFF_FFFF;
595+
let sign_bit: u64 = if value.is_sign_negative() {
596+
SIGN_MASK
567597
} else {
568-
let result = value * (2_f64).powf(try_bigint_to_f64(i.as_bigint(), vm)?);
569-
result_or_overflow(value, result, vm)
598+
0
599+
};
600+
let mant_bits = mant.to_bits() & FRAC_MASK;
601+
if exp >= -1021 {
602+
let e_bits = (1022_i32 + exp) as u64;
603+
let result_bits = sign_bit | (e_bits << 52) | mant_bits;
604+
return Ok(f64::from_bits(result_bits));
570605
}
606+
607+
let full_mant: u64 = (1u64 << 52) | mant_bits;
608+
let shift: u32 = (-exp - 1021) as u32;
609+
let frac_shifted = full_mant >> shift;
610+
let lost_bits = full_mant & ((1u64 << shift) - 1);
611+
612+
let half = 1u64 << (shift - 1);
613+
let frac = if (lost_bits > half) || (lost_bits == half && (frac_shifted & 1) == 1) {
614+
frac_shifted + 1
615+
} else {
616+
frac_shifted
617+
};
618+
619+
let result_bits = if frac >= (1u64 << 52) {
620+
sign_bit | (1u64 << 52)
621+
} else {
622+
sign_bit | frac
623+
};
624+
Ok(f64::from_bits(result_bits))
571625
}
572626

573627
fn math_perf_arb_len_int_op<F>(args: PosArgs<ArgIndex>, op: F, default: BigInt) -> BigInt

0 commit comments

Comments
 (0)