Skip to content

Commit

Permalink
core: use banker's rounding for the exact mode in flt2dec.
Browse files Browse the repository at this point in the history
For the shortest mode the IEEE 754 decoder already provides
an exact rounding range accounting for banker's rounding,
but it was not the case for the exact mode. This commit alters
the exact mode algorithm for Dragon so that any number ending at
`...x5000...` with even `x` and infinite zeroes will round to
`...x` instead of `...(x+1)` as it was. Grisu is not affected
by this change because this halfway case always results in
the failure for Grisu.
  • Loading branch information
lifthrasiir committed May 6, 2015
1 parent a641b05 commit 3d34e17
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 6 deletions.
6 changes: 5 additions & 1 deletion src/libcore/num/flt2dec/strategy/dragon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,11 @@ pub fn format_exact(d: &Decoded, buf: &mut [u8], limit: i16) -> (/*#digits*/ usi
}

// rounding up if we stop in the middle of digits
if mant >= *scale.mul_small(5) {
// if the following digits are exactly 5000..., check the prior digit and try to
// round to even (i.e. avoid rounding up when the prior digit is even).
let order = mant.cmp(scale.mul_small(5));
if order == Ordering::Greater || (order == Ordering::Equal &&
(len == 0 || buf[len-1] & 1 == 1)) {
// if rounding up changes the length, the exponent should also change.
// but we've been requested a fixed number of digits, so do not alter the buffer...
if let Some(c) = round_up(buf, len) {
Expand Down
23 changes: 18 additions & 5 deletions src/libcoretest/num/flt2dec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,17 @@ fn check_exact<F, T>(mut f: F, v: T, vstr: &str, expected: &[u8], expectedk: i16
bytes::copy_memory(&expected[..i], &mut expected_);
let mut expectedk_ = expectedk;
if expected[i] >= b'5' {
// if this returns true, expected_[..i] is all `9`s and being rounded up.
// we should always return `100..00` (`i` digits) instead, since that's
// what we can came up with `i` digits anyway. `round_up` assumes that
// the adjustment to the length is done by caller, which we simply ignore.
if let Some(_) = round_up(&mut expected_, i) { expectedk_ += 1; }
// check if this is a rounding-to-even case.
// we avoid rounding ...x5000... (with infinite zeroes) to ...(x+1) when x is even.
if !(i+1 < expected.len() && expected[i-1] & 1 == 0 &&
expected[i] == b'5' &&
expected[i+1] == b' ') {
// if this returns true, expected_[..i] is all `9`s and being rounded up.
// we should always return `100..00` (`i` digits) instead, since that's
// what we can came up with `i` digits anyway. `round_up` assumes that
// the adjustment to the length is done by caller, which we simply ignore.
if let Some(_) = round_up(&mut expected_, i) { expectedk_ += 1; }
}
}

try_exact!(f(&decoded) => &mut buf, &expected_[..i], expectedk_;
Expand Down Expand Up @@ -243,6 +249,7 @@ pub fn f32_exact_sanity_test<F>(mut f: F)
let minf32 = f32::ldexp(1.0, -149);

check_exact!(f(0.1f32) => b"100000001490116119384765625 ", 0);
check_exact!(f(0.5f32) => b"5 ", 0);
check_exact!(f(1.0f32/3.0) => b"3333333432674407958984375 ", 0);
check_exact!(f(3.141592f32) => b"31415920257568359375 ", 1);
check_exact!(f(3.141592e17f32) => b"314159196796878848 ", 18);
Expand Down Expand Up @@ -348,6 +355,7 @@ pub fn f64_exact_sanity_test<F>(mut f: F)

check_exact!(f(0.1f64) => b"1000000000000000055511151231257827021181", 0);
check_exact!(f(0.45f64) => b"4500000000000000111022302462515654042363", 0);
check_exact!(f(0.5f64) => b"5 ", 0);
check_exact!(f(0.95f64) => b"9499999999999999555910790149937383830547", 0);
check_exact!(f(100.0f64) => b"1 ", 3);
check_exact!(f(999.5f64) => b"9995000000000000000000000000000000000000", 3);
Expand Down Expand Up @@ -1032,6 +1040,11 @@ pub fn to_exact_fixed_str_test<F>(mut f_: F)
assert_eq!(to_string(f, 999.5, Minus, 3, false), "999.500");
assert_eq!(to_string(f, 999.5, Minus, 30, false), "999.500000000000000000000000000000");

assert_eq!(to_string(f, 0.5, Minus, 0, false), "1");
assert_eq!(to_string(f, 0.5, Minus, 1, false), "0.5");
assert_eq!(to_string(f, 0.5, Minus, 2, false), "0.50");
assert_eq!(to_string(f, 0.5, Minus, 3, false), "0.500");

assert_eq!(to_string(f, 0.95, Minus, 0, false), "1");
assert_eq!(to_string(f, 0.95, Minus, 1, false), "0.9"); // because it really is less than 0.95
assert_eq!(to_string(f, 0.95, Minus, 2, false), "0.95");
Expand Down

0 comments on commit 3d34e17

Please sign in to comment.