diff --git a/src/pretty/to_fixed/mod.rs b/src/pretty/to_fixed/mod.rs index d371b68..0bf3f0a 100644 --- a/src/pretty/to_fixed/mod.rs +++ b/src/pretty/to_fixed/mod.rs @@ -621,8 +621,20 @@ pub unsafe fn format64_to_fixed(f: f64, fraction_digits: u8, result: *mut u8) -> loop { round_index -= 1; + // Check the boundary before reading to avoid out-of-bounds access + // when rounding carries through all integer digits (e.g. 9 -> 10). + if round_index == -1 { + result.set(0, b'1'); + if dot_index > 0 { + result.set(dot_index, b'0'); + result.set(dot_index + 1, b'.'); + } + result.append_byte(b'0'); + break; + } + let c = result.get(round_index); - if round_index == -1 || c == b'-' { + if c == b'-' { result.set(round_index + 1, b'1'); if dot_index > 0 { result.set(dot_index, b'0'); diff --git a/tests/to_fixed.rs b/tests/to_fixed.rs index c66367d..a8867b0 100644 --- a/tests/to_fixed.rs +++ b/tests/to_fixed.rs @@ -379,3 +379,21 @@ fn test_min_exponent_boundry_full_mantissa() { expected ); } + +// Regression test for https://github.com/boa-dev/ryu-js/issues/55 +// Rounding carry that propagates through all integer digits must not +// read out-of-bounds before the buffer start. +#[test] +fn rounding_carry_past_all_digits() { + assert_eq!(pretty_to_fixed(9.5, 0), "10"); + assert_eq!(pretty_to_fixed(99.5, 0), "100"); + assert_eq!(pretty_to_fixed(999.5, 0), "1000"); + assert_eq!(pretty_to_fixed(9999.5, 0), "10000"); +} + +#[test] +fn rounding_carry_negative() { + assert_eq!(pretty_to_fixed(-9.5, 0), "-10"); + assert_eq!(pretty_to_fixed(-99.5, 0), "-100"); + assert_eq!(pretty_to_fixed(-999.5, 0), "-1000"); +}