Implement round-half-even float.fromhex in common float_ops#8234
Implement round-half-even float.fromhex in common float_ops#8234youknowone wants to merge 5 commits into
Conversation
Add float_ops::from_hex returning Result<f64, HexFloatError>, which parses the coefficient and exponent, rounds round-half-even over the full exponent range, and detects overflow. Route float.fromhex through it: raise OverflowError for values too large to represent and ValueError for invalid input. Remove the expectedFailure on test_from_hex. Assisted-by: Claude
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds a new ChangesHex Float Parsing
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Caller as Python Caller
participant PyFloat as PyFloat.fromhex
participant FloatOps as float_ops::from_hex
participant Finish as finish_hex
Caller->>PyFloat: fromhex(string)
PyFloat->>FloatOps: from_hex(string.as_str())
FloatOps->>FloatOps: parse whitespace, sign, inf/nan, 0x prefix, digits, exponent
FloatOps->>Finish: finish_hex(remaining, sign, value)
Finish-->>FloatOps: Ok(f64) or HexFloatError
FloatOps-->>PyFloat: Result<f64, HexFloatError>
alt Overflow
PyFloat-->>Caller: raise OverflowError
else TooLong / Invalid
PyFloat-->>Caller: raise ValueError
else Ok
PyFloat-->>Caller: return float
end
Related Issues: None referenced in the provided summary. Related PRs: None referenced in the provided summary. Suggested labels: rust, float, parsing Suggested reviewers: None specified. 🐰 A hex string hops through digits and dots, 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/common/src/float_ops.rs (1)
503-515: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
is_ascii_digit()hereClippy will flag this manual digit test;
hex_byte_at(bytes, idx).is_ascii_digit()is clearer and removes the duplicate lookup.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/common/src/float_ops.rs` around lines 503 - 515, The hex float exponent parsing in float_ops::hex_byte_at/exp handling uses a manual byte range check that Clippy flags and repeats the same lookup. Update the digit validation in this block to use the byte’s is_ascii_digit() method instead, and keep the surrounding exp_start/index advancement logic unchanged so the parsing behavior stays the same.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/common/src/float_ops.rs`:
- Around line 503-515: The hex float exponent parsing in
float_ops::hex_byte_at/exp handling uses a manual byte range check that Clippy
flags and repeats the same lookup. Update the digit validation in this block to
use the byte’s is_ascii_digit() method instead, and keep the surrounding
exp_start/index advancement logic unchanged so the parsing behavior stays the
same.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: c4465655-ccab-4da5-bcd7-babda2b29650
⛔ Files ignored due to path filters (1)
Lib/test/test_float.pyis excluded by!Lib/**
📒 Files selected for processing (2)
crates/common/src/float_ops.rscrates/vm/src/builtins/float.rs
📦 Library DependenciesThe following Lib/ modules were modified. Here are their dependencies: [x] test: cpython/Lib/test/test_float.py (TODO: 3) dependencies: dependent tests: (no tests depend on float) Legend:
|
| b'0'..=b'9' => (c - b'0') as i32, | ||
| b'a'..=b'f' => (c - b'a') as i32 + 10, | ||
| b'A'..=b'F' => (c - b'A') as i32 + 10, | ||
| _ => -1, |
There was a problem hiding this comment.
Is there a reason for not returning an Option/Result?
| } | ||
|
|
||
| #[inline] | ||
| fn is_py_space(c: u8) -> bool { |
There was a problem hiding this comment.
| fn is_py_space(c: u8) -> bool { | |
| const fn is_py_space(c: u8) -> bool { |
I thought we already had a function for detecting a py_space
| /// Read byte at `i`, returning a NUL terminator past the end so that | ||
| /// digit/sign/space scans stop at the string boundary. | ||
| #[inline] | ||
| fn hex_byte_at(bytes: &[u8], i: usize) -> u8 { |
There was a problem hiding this comment.
Same thing, should it return an Option instead?
| } | ||
| } | ||
|
|
||
| /// `t` must be an ASCII-lowercase literal. Returns true iff every byte of `t` |
There was a problem hiding this comment.
| /// `t` must be an ASCII-lowercase literal. Returns true iff every byte of `t` | |
| /// `t` must be an ASCII-lowercase literal. Returns true if every byte of `t` |
|
|
||
| /// Returns `(retval, endptr)`. When the text at `p` is not inf/nan, `retval` is | ||
| /// `-1.0` and `endptr == p`. | ||
| fn parse_inf_or_nan(bytes: &[u8], p: usize) -> (f64, usize) { |
|
|
||
| /// Correctly-rounded scalbn. Every call site scales an already-representable | ||
| /// value, so the result is exact. | ||
| fn ldexp(x: f64, mut n: i32) -> f64 { |
| } | ||
|
|
||
| // [p <exponent>] | ||
| let exp = if hex_byte_at(bytes, idx) == b'p' || hex_byte_at(bytes, idx) == b'P' { |
There was a problem hiding this comment.
| let exp = if hex_byte_at(bytes, idx) == b'p' || hex_byte_at(bytes, idx) == b'P' { | |
| let exp = if matches!(hex_byte_at(bytes, idx), b'p' | b'P') { |
| let exp = if hex_byte_at(bytes, idx) == b'p' || hex_byte_at(bytes, idx) == b'P' { | ||
| idx += 1; | ||
| let exp_start = idx; | ||
| if hex_byte_at(bytes, idx) == b'-' || hex_byte_at(bytes, idx) == b'+' { |
| if hex_byte_at(bytes, idx) == b'-' || hex_byte_at(bytes, idx) == b'+' { | ||
| idx += 1; | ||
| } | ||
| if !(b'0' <= hex_byte_at(bytes, idx) && hex_byte_at(bytes, idx) <= b'9') { |
| return Err(HexFloatError::Invalid); | ||
| } | ||
| idx += 1; | ||
| while b'0' <= hex_byte_at(bytes, idx) && hex_byte_at(bytes, idx) <= b'9' { |
…prefix Assisted-by: Claude
|
Thanks for the review — addressed in 51b3c1a:
— commented by Claude |
Assisted-by: Claude
… sre_engine Assisted-by: Claude
|
Update on the whitespace helper (after some more thought on where it belongs): instead of For the record, on two related questions that came up: the hex-float parser can't be borrowed from — commented by Claude |
Remove the private copy of is_py_ascii_whitespace in bytes_inner.rs and import the shared rustpython_common::wtf8::is_py_ascii_whitespace. Assisted-by: Claude
Summary
float.fromhexdelegated toliteral::float::from_hex, which is backed byhexf_parse. That reader is meant for exact literals: it rejects any inexact hexadecimal input and cannot tell overflow apart from a parse error, sofloat.fromhexcould not round and never raisedOverflowError.test_from_hexwas consequently marked@unittest.expectedFailure.This adds a dedicated hex-float reader in
common::float_opsthat reproduces the coefficient/exponent parsing, round-half-even rounding, and overflow detection thatfloat.fromhexrequires, and routes the classmethod through it.Changes
common::float_ops::from_hex(&str) -> Result<f64, HexFloatError>: round-half-even over the full exponent range (subnormals down to0x1p-1074), theinf/nanspellings, surrounding ASCII whitespace, a saturating exponent parse, andInvalid/TooLong/Overflowerror kinds.float.fromhexnow mapsOverflow → OverflowErrorandInvalid/TooLong → ValueError, with the messages Python uses.@unittest.expectedFailurefromtest_from_hex.inf/nansign bits, whitespace, and the error kinds.Verification
from_hexoutput was compared bit-for-bit against CPython'sfloat.fromhexover 125,000 inputs (curated + fuzzed, including near-halfway rounding and theDBL_MAXboundary): 0 mismatches.cargo test -p rustpython-commonandpython -m test test_floatpass, withtest_from_hexnow green.— commented by Claude
Summary by CodeRabbit
float.fromhex()now supports a broader set of Python-style hexadecimal floating-point inputs, includinginf,nan, signs, whitespace, and full exponent parsing.