Rollup merge of #147499 - josh-kaplan:master, r=Mark-Simulacrum

Implement round-ties-to-even for Duration Debug for consistency with f64

## Summary

This PR proposes a fix for rust-lang/rust#103747 implementing IEEE-754 S4.3 roundTiesToEven for Duration Debug implementation.

## Testing

Added new test in `time.rs` for  validating roundTiesToEven behavior in Duration formatting. Reran all debug formatting tests in `time.rs` with `./x test library/coretests --test-args time::debug_formatting`.
This commit is contained in:
Jonathan Brouwer
2025-12-28 18:16:09 +01:00
committed by GitHub
2 changed files with 54 additions and 31 deletions
+45 -30
View File
@@ -1315,39 +1315,54 @@ fn fmt_decimal(
// need to perform rounding to match the semantics of printing
// normal floating point numbers. However, we only need to do work
// when rounding up. This happens if the first digit of the
// remaining ones is >= 5.
// remaining ones is >= 5. When the first digit is exactly 5, rounding
// follows IEEE-754 round-ties-to-even semantics: we only round up
// if the last written digit is odd.
let integer_part = if fractional_part > 0 && fractional_part >= divisor * 5 {
// Round up the number contained in the buffer. We go through
// the buffer backwards and keep track of the carry.
let mut rev_pos = pos;
let mut carry = true;
while carry && rev_pos > 0 {
rev_pos -= 1;
// If the digit in the buffer is not '9', we just need to
// increment it and can stop then (since we don't have a
// carry anymore). Otherwise, we set it to '0' (overflow)
// and continue.
if buf[rev_pos] < b'9' {
buf[rev_pos] += 1;
carry = false;
} else {
buf[rev_pos] = b'0';
}
}
// If we still have the carry bit set, that means that we set
// the whole buffer to '0's and need to increment the integer
// part.
if carry {
// If `integer_part == u64::MAX` and precision < 9, any
// carry of the overflow during rounding of the
// `fractional_part` into the `integer_part` will cause the
// `integer_part` itself to overflow. Avoid this by using an
// `Option<u64>`, with `None` representing `u64::MAX + 1`.
integer_part.checked_add(1)
// For ties (fractional_part == divisor * 5), only round up if last digit is odd
let is_tie = fractional_part == divisor * 5;
let last_digit_is_odd = if pos > 0 {
(buf[pos - 1] - b'0') % 2 == 1
} else {
// No fractional digits - check the integer part
(integer_part % 2) == 1
};
if is_tie && !last_digit_is_odd {
Some(integer_part)
} else {
// Round up the number contained in the buffer. We go through
// the buffer backwards and keep track of the carry.
let mut rev_pos = pos;
let mut carry = true;
while carry && rev_pos > 0 {
rev_pos -= 1;
// If the digit in the buffer is not '9', we just need to
// increment it and can stop then (since we don't have a
// carry anymore). Otherwise, we set it to '0' (overflow)
// and continue.
if buf[rev_pos] < b'9' {
buf[rev_pos] += 1;
carry = false;
} else {
buf[rev_pos] = b'0';
}
}
// If we still have the carry bit set, that means that we set
// the whole buffer to '0's and need to increment the integer
// part.
if carry {
// If `integer_part == u64::MAX` and precision < 9, any
// carry of the overflow during rounding of the
// `fractional_part` into the `integer_part` will cause the
// `integer_part` itself to overflow. Avoid this by using an
// `Option<u64>`, with `None` representing `u64::MAX + 1`.
integer_part.checked_add(1)
} else {
Some(integer_part)
}
}
} else {
Some(integer_part)
+9 -1
View File
@@ -439,7 +439,6 @@ fn debug_formatting_precision_two() {
assert_eq!(format!("{:.2?}", Duration::new(4, 001_000_000)), "4.00s");
assert_eq!(format!("{:.2?}", Duration::new(2, 100_000_000)), "2.10s");
assert_eq!(format!("{:.2?}", Duration::new(2, 104_990_000)), "2.10s");
assert_eq!(format!("{:.2?}", Duration::new(2, 105_000_000)), "2.11s");
assert_eq!(format!("{:.2?}", Duration::new(8, 999_999_999)), "9.00s");
}
@@ -480,6 +479,15 @@ fn debug_formatting_precision_high() {
assert_eq!(format!("{:.20?}", Duration::new(4, 001_000_000)), "4.00100000000000000000s");
}
#[test]
fn debug_formatting_round_to_even() {
assert_eq!(format!("{:.0?}", Duration::new(1, 500_000_000)), "2s");
assert_eq!(format!("{:.0?}", Duration::new(2, 500_000_000)), "2s");
assert_eq!(format!("{:.0?}", Duration::new(0, 1_500_000)), "2ms");
assert_eq!(format!("{:.0?}", Duration::new(0, 2_500_000)), "2ms");
assert_eq!(format!("{:.2?}", Duration::new(2, 105_000_000)), "2.10s");
}
#[test]
fn duration_const() {
// test that the methods of `Duration` are usable in a const context