Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement LowerExp and UpperExp for integers #66721

Merged
merged 1 commit into from
Feb 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions src/libcore/fmt/num.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use crate::fmt;
use crate::mem::MaybeUninit;
use crate::num::flt2dec;
use crate::ops::{Div, Rem, Sub};
use crate::ptr;
use crate::slice;
Expand Down Expand Up @@ -256,6 +257,161 @@ macro_rules! impl_Display {
};
}

macro_rules! impl_Exp {
($($t:ident),* as $u:ident via $conv_fn:ident named $name:ident) => {
fn $name(
Copy link
Contributor

Choose a reason for hiding this comment

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

Before digging too deeply into the code itself can you offer any background on this implementation? Whether it's based on any common approaches used elsewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is heavily based on impl_Display! from the same file. It also makes use of some stuff from float's implementation (for precision).

mut n: $u,
is_nonnegative: bool,
upper: bool,
f: &mut fmt::Formatter<'_>
) -> fmt::Result {
let (mut n, mut exponent, trailing_zeros, added_precision) = {
let mut exponent = 0;
// count and remove trailing decimal zeroes
while n % 10 == 0 && n >= 10 {
n /= 10;
exponent += 1;
}
let trailing_zeros = exponent;

let (added_precision, subtracted_precision) = match f.precision() {
Some(fmt_prec) => {
// number of decimal digits minus 1
let mut tmp = n;
let mut prec = 0;
while tmp >= 10 {
tmp /= 10;
prec += 1;
}
(fmt_prec.saturating_sub(prec), prec.saturating_sub(fmt_prec))
}
None => (0,0)
};
for _ in 1..subtracted_precision {
n/=10;
exponent += 1;
}
if subtracted_precision != 0 {
let rem = n % 10;
n /= 10;
exponent += 1;
// round up last digit
if rem >= 5 {
n += 1;
}
}
(n, exponent, trailing_zeros, added_precision)
};

// 39 digits (worst case u128) + . = 40
let mut buf = [MaybeUninit::<u8>::uninit(); 40];
let mut curr = buf.len() as isize; //index for buf
let buf_ptr = MaybeUninit::first_ptr_mut(&mut buf);
let lut_ptr = DEC_DIGITS_LUT.as_ptr();

// decode 2 chars at a time
while n >= 100 {
let d1 = ((n % 100) as isize) << 1;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
n /= 100;
exponent += 2;
}
// n is <= 99, so at most 2 chars long
let mut n = n as isize; // possibly reduce 64bit math
// decode second-to-last character
if n >= 10 {
curr -= 1;
unsafe {
*buf_ptr.offset(curr) = (n as u8 % 10_u8) + b'0';
}
n /= 10;
exponent += 1;
}
// add decimal point iff >1 mantissa digit will be printed
if exponent != trailing_zeros || added_precision != 0 {
curr -= 1;
unsafe {
*buf_ptr.offset(curr) = b'.';
}
}

let buf_slice = unsafe {
// decode last character
curr -= 1;
*buf_ptr.offset(curr) = (n as u8) + b'0';

let len = buf.len() - curr as usize;
slice::from_raw_parts(buf_ptr.offset(curr), len)
};

// stores 'e' (or 'E') and the up to 2-digit exponent
let mut exp_buf = [MaybeUninit::<u8>::uninit(); 3];
let exp_ptr = MaybeUninit::first_ptr_mut(&mut exp_buf);
let exp_slice = unsafe {
*exp_ptr.offset(0) = if upper {b'E'} else {b'e'};
let len = if exponent < 10 {
*exp_ptr.offset(1) = (exponent as u8) + b'0';
2
} else {
let off = exponent << 1;
ptr::copy_nonoverlapping(lut_ptr.offset(off), exp_ptr.offset(1), 2);
3
};
slice::from_raw_parts(exp_ptr, len)
};

let parts = &[
flt2dec::Part::Copy(buf_slice),
flt2dec::Part::Zero(added_precision),
flt2dec::Part::Copy(exp_slice)
];
let sign = if !is_nonnegative {
&b"-"[..]
} else if f.sign_plus() {
&b"+"[..]
} else {
&b""[..]
};
let formatted = flt2dec::Formatted{sign, parts};
f.pad_formatted_parts(&formatted)
}

$(
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::LowerExp for $t {
#[allow(unused_comparisons)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let is_nonnegative = *self >= 0;
let n = if is_nonnegative {
self.$conv_fn()
} else {
// convert the negative num to positive by summing 1 to it's 2 complement
(!self.$conv_fn()).wrapping_add(1)
};
$name(n, is_nonnegative, false, f)
}
})*
$(
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::UpperExp for $t {
#[allow(unused_comparisons)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let is_nonnegative = *self >= 0;
let n = if is_nonnegative {
self.$conv_fn()
} else {
// convert the negative num to positive by summing 1 to it's 2 complement
(!self.$conv_fn()).wrapping_add(1)
};
$name(n, is_nonnegative, true, f)
}
})*
};
}

// Include wasm32 in here since it doesn't reflect the native pointer size, and
// often cares strongly about getting a smaller code size.
#[cfg(any(target_pointer_width = "64", target_arch = "wasm32"))]
Expand All @@ -265,13 +421,20 @@ mod imp {
i8, u8, i16, u16, i32, u32, i64, u64, usize, isize
as u64 via to_u64 named fmt_u64
);
impl_Exp!(
i8, u8, i16, u16, i32, u32, i64, u64, usize, isize
as u64 via to_u64 named exp_u64
);
}

#[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))]
mod imp {
use super::*;
impl_Display!(i8, u8, i16, u16, i32, u32, isize, usize as u32 via to_u32 named fmt_u32);
impl_Display!(i64, u64 as u64 via to_u64 named fmt_u64);
impl_Exp!(i8, u8, i16, u16, i32, u32, isize, usize as u32 via to_u32 named exp_u32);
impl_Exp!(i64, u64 as u64 via to_u64 named exp_u64);
}

impl_Display!(i128, u128 as u128 via to_u128 named fmt_u128);
impl_Exp!(i128, u128 as u128 via to_u128 named exp_u128);
80 changes: 80 additions & 0 deletions src/libcore/tests/fmt/num.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ fn test_format_int() {
assert_eq!(format!("{:o}", 1i16), "1");
assert_eq!(format!("{:o}", 1i32), "1");
assert_eq!(format!("{:o}", 1i64), "1");
assert_eq!(format!("{:e}", 1isize), "1e0");
assert_eq!(format!("{:e}", 1i8), "1e0");
assert_eq!(format!("{:e}", 1i16), "1e0");
assert_eq!(format!("{:e}", 1i32), "1e0");
assert_eq!(format!("{:e}", 1i64), "1e0");
assert_eq!(format!("{:E}", 1isize), "1E0");
assert_eq!(format!("{:E}", 1i8), "1E0");
assert_eq!(format!("{:E}", 1i16), "1E0");
assert_eq!(format!("{:E}", 1i32), "1E0");
assert_eq!(format!("{:E}", 1i64), "1E0");
maxbla marked this conversation as resolved.
Show resolved Hide resolved

assert_eq!(format!("{}", 1usize), "1");
assert_eq!(format!("{}", 1u8), "1");
Expand Down Expand Up @@ -69,13 +79,79 @@ fn test_format_int() {
assert_eq!(format!("{:o}", 1u16), "1");
assert_eq!(format!("{:o}", 1u32), "1");
assert_eq!(format!("{:o}", 1u64), "1");
assert_eq!(format!("{:e}", 1u8), "1e0");
assert_eq!(format!("{:e}", 1u16), "1e0");
assert_eq!(format!("{:e}", 1u32), "1e0");
assert_eq!(format!("{:e}", 1u64), "1e0");
assert_eq!(format!("{:E}", 1u8), "1E0");
assert_eq!(format!("{:E}", 1u16), "1E0");
assert_eq!(format!("{:E}", 1u32), "1E0");
assert_eq!(format!("{:E}", 1u64), "1E0");

// Test a larger number
assert_eq!(format!("{:b}", 55), "110111");
assert_eq!(format!("{:o}", 55), "67");
assert_eq!(format!("{}", 55), "55");
assert_eq!(format!("{:x}", 55), "37");
assert_eq!(format!("{:X}", 55), "37");
assert_eq!(format!("{:e}", 55), "5.5e1");
assert_eq!(format!("{:E}", 55), "5.5E1");
assert_eq!(format!("{:e}", 10000000000u64), "1e10");
assert_eq!(format!("{:E}", 10000000000u64), "1E10");
assert_eq!(format!("{:e}", 10000000001u64), "1.0000000001e10");
assert_eq!(format!("{:E}", 10000000001u64), "1.0000000001E10");
}

#[test]
fn test_format_int_exp_limits() {
use core::{i128, i16, i32, i64, i8, u128, u16, u32, u64, u8};
assert_eq!(format!("{:e}", i8::MIN), "-1.28e2");
assert_eq!(format!("{:e}", i8::MAX), "1.27e2");
assert_eq!(format!("{:e}", i16::MIN), "-3.2768e4");
assert_eq!(format!("{:e}", i16::MAX), "3.2767e4");
assert_eq!(format!("{:e}", i32::MIN), "-2.147483648e9");
assert_eq!(format!("{:e}", i32::MAX), "2.147483647e9");
assert_eq!(format!("{:e}", i64::MIN), "-9.223372036854775808e18");
assert_eq!(format!("{:e}", i64::MAX), "9.223372036854775807e18");
assert_eq!(format!("{:e}", i128::MIN), "-1.70141183460469231731687303715884105728e38");
assert_eq!(format!("{:e}", i128::MAX), "1.70141183460469231731687303715884105727e38");

assert_eq!(format!("{:e}", u8::MAX), "2.55e2");
assert_eq!(format!("{:e}", u16::MAX), "6.5535e4");
assert_eq!(format!("{:e}", u32::MAX), "4.294967295e9");
assert_eq!(format!("{:e}", u64::MAX), "1.8446744073709551615e19");
assert_eq!(format!("{:e}", u128::MAX), "3.40282366920938463463374607431768211455e38");
}

#[test]
fn test_format_int_exp_precision() {
use core::{i128, i16, i32, i64, i8};

//test that float and integer match
let big_int: u32 = 314_159_265;
assert_eq!(format!("{:.1e}", big_int), format!("{:.1e}", f64::from(big_int)));

//test adding precision
assert_eq!(format!("{:.10e}", i8::MIN), "-1.2800000000e2");
assert_eq!(format!("{:.10e}", i16::MIN), "-3.2768000000e4");
assert_eq!(format!("{:.10e}", i32::MIN), "-2.1474836480e9");
assert_eq!(format!("{:.20e}", i64::MIN), "-9.22337203685477580800e18");
assert_eq!(format!("{:.40e}", i128::MIN), "-1.7014118346046923173168730371588410572800e38");

//test rounding
assert_eq!(format!("{:.1e}", i8::MIN), "-1.3e2");
assert_eq!(format!("{:.1e}", i16::MIN), "-3.3e4");
assert_eq!(format!("{:.1e}", i32::MIN), "-2.1e9");
assert_eq!(format!("{:.1e}", i64::MIN), "-9.2e18");
assert_eq!(format!("{:.1e}", i128::MIN), "-1.7e38");

//test huge precision
assert_eq!(format!("{:.1000e}", 1), format!("1.{}e0", "0".repeat(1000)));
//test zero precision
assert_eq!(format!("{:.0e}", 1), format!("1e0",));

//test padding with precision (and sign)
assert_eq!(format!("{:+10.3e}", 1), " +1.000e0");
}

#[test]
Expand All @@ -86,13 +162,17 @@ fn test_format_int_zero() {
assert_eq!(format!("{:o}", 0), "0");
assert_eq!(format!("{:x}", 0), "0");
assert_eq!(format!("{:X}", 0), "0");
assert_eq!(format!("{:e}", 0), "0e0");
assert_eq!(format!("{:E}", 0), "0E0");

assert_eq!(format!("{}", 0u32), "0");
assert_eq!(format!("{:?}", 0u32), "0");
assert_eq!(format!("{:b}", 0u32), "0");
assert_eq!(format!("{:o}", 0u32), "0");
assert_eq!(format!("{:x}", 0u32), "0");
assert_eq!(format!("{:X}", 0u32), "0");
assert_eq!(format!("{:e}", 0u32), "0e0");
assert_eq!(format!("{:E}", 0u32), "0E0");
}

#[test]
Expand Down