Skip to content

Conversation

@YashSuthar983
Copy link
Contributor

@YashSuthar983 YashSuthar983 commented Oct 25, 2025

Implemented IEEE 754-compliant handling for large numbers to avoid overflow and represent results using scientific notation.

Fixes #5471

Summary by CodeRabbit

  • Bug Fixes
    • More accurate floating-point exponent scaling with precise IEEE‑754-aware handling and rounding.
    • Corrected behavior for very large or very small exponents, including improved handling of subnormal values.
    • Enhanced overflow and underflow detection with proper signed-zero returns where applicable.
    • Maintains previous results for normal-range values while fixing edge-case math inaccuracies.

Implemented IEEE 754-compliant handling for large numbers to avoid
overflow and represent results using scientific notation.

Fixes RustPython#5471

Signed-off-by: Yash Suthar <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 25, 2025

Walkthrough

Replaced the previous ldexp path in stdlib/src/math.rs with an IEEE‑754 bit-manipulation implementation: added ToPrimitive import, decomposed floats, adjusted exponents (including subnormal handling), assembled result bits, and added overflow/underflow checks and rounding to correctly produce extreme float64 values.

Changes

Cohort / File(s) Summary
ldexp implementation enhancement
stdlib/src/math.rs
Added ToPrimitive import and replaced the prior simple multiplication/early-return path for ldexp with a comprehensive IEEE‑754 bit-level implementation: decomposes mantissa/exponent/sign, adjusts for subnormal inputs, shifts/assembles bits, performs overflow/underflow checks, reconstructs the float (including signed zero), and applies rounding/normalization.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Caller
    participant ldexp as ldexp(x, exp)
    participant Decompose as decompose_float
    participant BitLogic as bit-level logic
    participant Assemble as assemble_float
    Note over ldexp,BitLogic:#eef3ff: IEEE-754 bit-manipulation flow
    Caller->>ldexp: call(x, exp)
    ldexp->>Decompose: decompose mantissa, exponent, sign
    Decompose-->>ldexp: components
    ldexp->>BitLogic: adjust exponent, handle subnormal, apply shifts
    alt exponent > max
        BitLogic-->>ldexp: return +/-inf
    else exponent < min
        BitLogic->>Assemble: construct denormal or signed zero (with rounding)
        Assemble-->>ldexp: result
    else in-range
        BitLogic->>Assemble: assemble normal float bits
        Assemble-->>ldexp: result
    end
    ldexp-->>Caller: return final float
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus areas:
    • stdlib/src/math.rs — verify bit masks, shifts, exponent bias, subnormal handling, and rounding behavior.
    • Edge-case correctness: max finite float, NaN/Inf passthrough, signed zero, and denormals.
    • Confirm ToPrimitive is required and not an unnecessary import.

Poem

🐰 I nibble bits and hop through night,
I coax the exponent, set it right.
Mantissa tucked, sign snug and small,
No phantom Inf — I catch them all.
ldexp blooms — a precise, soft call. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Fix ldexp to prevent math range errors" is directly related to the main change in the changeset. The title clearly identifies the function being modified (ldexp) and the primary issue being addressed (preventing math range errors). The title is concise, specific, and helps a teammate scanning the commit history understand that this is a fix to the ldexp function's behavior related to overflow/range handling. The title avoids vague terminology and noise, making it effective for its purpose.
Linked Issues Check ✅ Passed The code changes directly address the requirement from issue #5471 to ensure correct representation of the maximum possible float64 by implementing IEEE 754–compliant handling for ldexp. The changes replace the simple multiplication path with a comprehensive bit-level manipulation approach that includes overflow/underflow checks and precise rounding. According to the test results provided in the PR, the fix successfully resolves both the specific example from issue #5471 (returning the correct maximum finite float64 value instead of overflow/infinity) and related edge cases like subnormal number handling, demonstrating that the core objective has been met.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly in scope and related to fixing the ldexp function to address issue #5471. The modifications include adding the ToPrimitive trait import (used by the new implementation), replacing the simple return path with IEEE 754 bit-manipulation logic, and implementing overflow/underflow checks and proper rounding. These changes are all necessary to achieve the stated objective of preventing math range errors and ensuring correct representation of large float64 values, with no unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 728b7b4 and 8acd6c5.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_math.py is excluded by !Lib/**
📒 Files selected for processing (1)
  • stdlib/src/math.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Format Rust code with the default rustfmt style (run cargo fmt)
Run clippy and fix any warnings or lints introduced by your changes
Follow Rust best practices for error handling and memory management

Files:

  • stdlib/src/math.rs
{vm,stdlib}/**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use RustPython macros (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • stdlib/src/math.rs
🧬 Code graph analysis (1)
stdlib/src/math.rs (1)
common/src/float_ops.rs (1)
  • decompose_float (5-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
🔇 Additional comments (8)
stdlib/src/math.rs (8)

15-15: LGTM! Correct import addition.

The ToPrimitive trait is properly imported and used on line 597 for converting BigInt to i32.


564-567: LGTM! Correct early return for special values.

The early return for zeros, NaNs, and infinities is correct per IEEE 754 semantics—these values should pass through ldexp unchanged.


575-581: LGTM! Subnormal input handling is correct.

The scaling approach (multiply by 2^54, decompose, then adjust exponent by -54) correctly maintains the invariant value = mantissa × 2^exponent for subnormal inputs, as verified in previous reviews and testing.

Based on past review comments


583-594: LGTM! Overflow and underflow bounds are correct.

The bounds checking correctly identifies:

  • Overflow when exp + i > 1024 (result ≥ 2^1024)
  • Underflow when exp + i < -1074 (result rounds to zero)

The edge case check on lines 588-590 (mant == 1.0) is overly conservative (mantissa from decompose_float is in [0.5, 1.0) and can never equal exactly 1.0), but it's a safe defensive check.


596-599: LGTM! Safe conversion with justified expect.

The conversion from BigInt to i32 is safe because the bounds checking (lines 583-594) ensures the value is in the range [-2097, 2098], which fits comfortably in i32.


609-613: LGTM! Correct IEEE 754 bit construction for normalized results.

For exp >= -1021, the result is a normalized float. The exponent bits e_bits = 1022 + exp correctly encode the IEEE 754 biased exponent, and the bit assembly produces the correct normalized float.


615-632: LGTM! Correct subnormal handling with proper rounding.

The subnormal path correctly:

  • Computes the right shift amount for the mantissa (lines 616-617)
  • Implements round-to-nearest-even (lines 620-625), the IEEE 754 default rounding mode
  • Handles fraction overflow from rounding (lines 627-631), promoting to the smallest normalized number when appropriate

The bit manipulation accurately represents subnormal values.


554-633: I cannot execute cargo fmt in this sandbox environment due to missing system dependencies (/proc filesystem not available).

Manually verify formatting by running cargo fmt --all locally.

Since the sandbox prevents running the formatter, you'll need to verify the formatting locally:

  1. Run cargo fmt --all in your repository
  2. Review any changes to stdlib/src/math.rs lines 554-633
  3. Commit the formatting changes if any were made

Visual inspection of the provided code snippet shows consistent indentation and structure, but a definitive verification requires running the actual rustfmt tool.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
stdlib/src/math.rs (1)

576-582: Simplify overflow check by removing redundant condition.

The condition mant == 1.0 on line 580 never evaluates to true because decompose_float returns a mantissa in the range [0.5, 1.0) (never exactly 1.0). The overflow check on line 577 is sufficient.

Apply this diff to simplify:

 let overflow_bound = BigInt::from(1024_i32 - exp0); // i > 1024 - exp0 => overflow
 if i_big > &overflow_bound {
     return Err(vm.new_overflow_error("math range error"));
 }
-if i_big == &overflow_bound && mant == 1.0 {
-    return Err(vm.new_overflow_error("math range error"));
-}
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d23f6e and 4c88621.

📒 Files selected for processing (1)
  • stdlib/src/math.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Format Rust code with the default rustfmt style (run cargo fmt)
Run clippy and fix any warnings or lints introduced by your changes
Follow Rust best practices for error handling and memory management

Files:

  • stdlib/src/math.rs
{vm,stdlib}/**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use RustPython macros (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • stdlib/src/math.rs
🧬 Code graph analysis (1)
stdlib/src/math.rs (1)
common/src/float_ops.rs (1)
  • decompose_float (5-14)
🔇 Additional comments (4)
stdlib/src/math.rs (4)

15-15: LGTM: Import is required for the new implementation.

The ToPrimitive trait import is used for the .to_i32() conversion on line 589.


564-573: LGTM: Early return and IEEE 754 approach are sound.

The early return for special values is correct, and the IEEE 754 bit-manipulation approach properly avoids intermediate overflow that would occur with simple multiplication.


593-605: LGTM: Normal float construction is correct.

The IEEE 754 bit construction for normal floats is implemented correctly, including proper sign handling, exponent calculation (exp + 1022), and fraction bits.


607-624: LGTM: Denormalized float construction with rounding is correct.

The implementation correctly handles denormalized results using proper bit shifting, round-to-nearest-even rounding, and the edge case where rounding produces the smallest normal float.

Detect denormalized input values below f64::MIN_POSITIVE
Scale subnormal inputs by 2^54 to bring them into normalized range

Signed-off-by: Yash Suthar <[email protected]>
@YashSuthar983
Copy link
Contributor Author

@arihant2math @youknowone Could you please take a look and review the changes when you get a chance?

@arihant2math
Copy link
Collaborator

Do no new tests pass because of this?

@YashSuthar983
Copy link
Contributor Author

YashSuthar983 commented Oct 25, 2025

@arihant2math heres the test.py which are failing .

import math

# Test 1
x = 6993274598585239
result = math.ldexp(x, -1126)
expected = 1e-323
print("Test 1:", result, expected, result == expected)

# Test 2  (same as #5471 issue)
exponent = 1023
significand = 4503599627370495
mantissa = 1 + significand / 2**52
positive = True

x2 = (mantissa / 2.0) * (1 if positive else -1)  # input value
result2 = math.ldexp(x2, exponent + 1)
expected2 = 1.7976931348623157e+308  # maximum finite float64
print("Test 2:", result2, expected2, result2 == expected2)

Cpython

Python 3.12.3 (main, Aug 14 2025, 17:47:21) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import math
>>> 
>>> # Test 1
>>> x = 6993274598585239
>>> result = math.ldexp(x, -1126)
>>> expected = 1e-323
>>> print("Test 1:", result, expected, result == expected)
Test 1: 1e-323 1e-323 True
>>> 
>>> # Test 2 (rewritten like Test 1)
>>> exponent = 1023
>>> significand = 4503599627370495
>>> mantissa = 1 + significand / 2**52
>>> positive = True
>>> 
>>> x2 = (mantissa / 2.0) * (1 if positive else -1)  # input value
>>> result2 = math.ldexp(x2, exponent + 1)
>>> expected2 = 1.7976931348623157e+308  # maximum finite float64
>>> print("Test 2:", result2, expected2, result2 == expected2)
Test 2: 1.7976931348623157e+308 1.7976931348623157e+308 True
>>> 
>>> 

Rustpython (before fix)

   Compiling rustpython-stdlib v0.4.0 (/home/yash/contri/RustPython/stdlib)
   Compiling rustpython v0.4.0 (/home/yash/contri/RustPython)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.60s
     Running `target/debug/rustpython`
Welcome to the magnificent Rust Python 0.4.0 interpreter 😱 🖖
RustPython 3.13.0
Type "help", "copyright", "credits" or "license" for more information.
>>>>> import math

# Test 1
x = 6993274598585239
result = math.ldexp(x, -1126)
expected = 1e-323
print("Test 1:", result, expected, result == expected)

# Test 2 (rewritten like Test 1)
exponent = 1023
significand = 4503599627370495
mantissa = 1 + significand / 2**52
positive = True

x2 = (mantissa / 2.0) * (1 if positive else -1)  # input value
result2 = math.ldexp(x2, exponent + 1)
expected2 = 1.7976931348623157e+308  # maximum finite float64
print("Test 2:", result2, expected2, result2 == expected2)

Test 1: 0.0 1e-323 False
Traceback (most recent call last):
  File "<stdin>", line 16, in <module>
OverflowError: math range error
>>>>> 

Rustpython (after fix)

   Compiling rustpython-stdlib v0.4.0 (/home/yash/contri/RustPython/stdlib)
   Compiling rustpython v0.4.0 (/home/yash/contri/RustPython)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.87s
     Running `target/debug/rustpython`
Welcome to the magnificent Rust Python 0.4.0 interpreter 😱 🖖
RustPython 3.13.0
Type "help", "copyright", "credits" or "license" for more information.
>>>>> import math

# Test 1
x = 6993274598585239
result = math.ldexp(x, -1126)
expected = 1e-323
print("Test 1:", result, expected, result == expected)

# Test 2  (same as #5471 issue)
exponent = 1023
significand = 4503599627370495
mantissa = 1 + significand / 2**52
positive = True

x2 = (mantissa / 2.0) * (1 if positive else -1)  # input value
result2 = math.ldexp(x2, exponent + 1)
expected2 = 1.7976931348623157e+308  # maximum finite float64
print("Test 2:", result2, expected2, result2 == expected2)

Test 1: 1e-323 1e-323 True
Test 2: 1.7976931348623157e+308 1.7976931348623157e+308 True
>>>>> 

As now we have implemented IEEE 754 format , so this will pass.

Signed-off-by: Yash Suthar <[email protected]>
Copy link
Member

@youknowone youknowone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for contributing!

Because I have no ability to decide it is mathematically correct, it is good as long as the more tests pass.
Please check about the lint failure and the change suggestion on the test file to keep the code style.


Additionally, if you are interested in implementing more precise python math algorithms, could you also check if this function can be implemented in pymath? It is a subproject holding bit-perfect python math library compatible functions. (This suggestion is not related to approve of this PR.)

Co-authored-by: Jeong, YunWon <[email protected]>
@youknowone
Copy link
Member

Running cargo fmt --all will fix the CI failure

Signed-off-by: Yash Suthar <[email protected]>
@YashSuthar983
Copy link
Contributor Author

YashSuthar983 commented Oct 26, 2025

@youknowone don't know why this test_httplib failing.

Copy link
Member

@youknowone youknowone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea, probably flaky because it was unexpected success. It doesn't look related to this patch.

Thank you!

@youknowone youknowone merged commit c6c931a into RustPython:main Oct 26, 2025
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

correct representation of the maximum possible float64

3 participants