about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMax Blachman <blachmanmax@gmail.com>2019-11-24 14:30:56 -0800
committerMax Blachman <blachmanmax@gmail.com>2020-02-14 18:42:51 -0800
commita8fe47d1756df0fb68f6ed2edd2cedfb3cc66d7c (patch)
treef2b530b71e94bc1836f64afef592b259133013a9
parentb92c6ee882853313698f1148512e8e992ba36b2d (diff)
downloadrust-a8fe47d1756df0fb68f6ed2edd2cedfb3cc66d7c.tar.gz
rust-a8fe47d1756df0fb68f6ed2edd2cedfb3cc66d7c.zip
implement LowerExp and UpperExp for integers
-rw-r--r--src/libcore/fmt/num.rs163
-rw-r--r--src/libcore/tests/fmt/num.rs80
2 files changed, 243 insertions, 0 deletions
diff --git a/src/libcore/fmt/num.rs b/src/libcore/fmt/num.rs
index d562639a658..c187471fb5f 100644
--- a/src/libcore/fmt/num.rs
+++ b/src/libcore/fmt/num.rs
@@ -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;
@@ -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(
+            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"))]
@@ -265,6 +421,10 @@ 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")))]
@@ -272,6 +432,9 @@ 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);
diff --git a/src/libcore/tests/fmt/num.rs b/src/libcore/tests/fmt/num.rs
index 10fcf8b76cc..a50c2b46a91 100644
--- a/src/libcore/tests/fmt/num.rs
+++ b/src/libcore/tests/fmt/num.rs
@@ -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");
 
     assert_eq!(format!("{}", 1usize), "1");
     assert_eq!(format!("{}", 1u8), "1");
@@ -69,6 +79,14 @@ 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");
@@ -76,6 +94,64 @@ fn test_format_int() {
     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]
@@ -86,6 +162,8 @@ 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");
@@ -93,6 +171,8 @@ fn test_format_int_zero() {
     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]