From cbfb0e8cc881c7243107fb785e8d1275a056bf03 Mon Sep 17 00:00:00 2001 From: Patrick Norton Date: Tue, 10 Aug 2021 06:56:08 -0400 Subject: [PATCH 1/6] Added exponential formatting BigUint --- src/biguint.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/biguint.rs b/src/biguint.rs index 271a8837..c714a9eb 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -4,7 +4,7 @@ use crate::std_alloc::{String, Vec}; use core::cmp; use core::cmp::Ordering; use core::default::Default; -use core::fmt; +use core::fmt::{self, Write}; use core::hash; use core::mem; use core::str; @@ -143,6 +143,72 @@ impl fmt::Octal for BigUint { } } +impl fmt::LowerExp for BigUint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Option::Some(small) = self.to_u64() { + fmt::LowerExp::fmt(&small, f) + } else { + // The max digits that can fit into a f64, which ensures + // that printing won't have rounding errors. + const MAX_FLOAT_DIGITS: usize = 14; + if f.precision().filter(|x| x < &MAX_FLOAT_DIGITS).is_some() { + let as_f64 = self.to_f64().unwrap(); + if as_f64.is_finite() { + fmt::LowerExp::fmt(&as_f64, f) + } else { + fmt_exp_slow(self, f, false) + } + } else { + fmt_exp_slow(self, f, false) + } + } + } +} + +impl fmt::UpperExp for BigUint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Option::Some(small) = self.to_u64() { + fmt::UpperExp::fmt(&small, f) + } else { + // The max digits that can fit into a f64, which ensures + // that printing won't have rounding errors. + const MAX_FLOAT_DIGITS: usize = 14; + if f.precision().filter(|&x| x < MAX_FLOAT_DIGITS).is_some() { + let as_f64 = self.to_f64().unwrap(); + if as_f64.is_finite() { + fmt::UpperExp::fmt(&as_f64, f) + } else { + fmt_exp_slow(self, f, true) + } + } else { + fmt_exp_slow(self, f, true) + } + } + } +} + +fn fmt_exp_slow(x: &BigUint, f: &mut fmt::Formatter<'_>, upper: bool) -> fmt::Result { + let precision = f.precision().unwrap_or(16); + let digits = to_str_radix_reversed(x, 10); + let mut buffer = String::with_capacity(precision + 5); + debug_assert!(digits.len() > 1, "Values with fewer digits should use direct formatting"); + let mut iter = digits.iter().rev(); + buffer.push(*iter.next().unwrap() as char); + buffer.push('.'); + for &ch in iter.take(precision) { + buffer.push(ch as char); + } + // Add trailing zeroes when explicit precision given + if f.precision().is_some() { + for _ in 0..precision.saturating_sub(digits.len() - 1) { + buffer.push('0'); + } + } + buffer.push(if upper { 'E' } else { 'e' }); + write!(buffer, "{}", digits.len() - 1)?; + f.pad_integral(true, "", &buffer) +} + impl Zero for BigUint { #[inline] fn zero() -> BigUint { From bb8095edbc4e6166c4899422c8ecae51e579e295 Mon Sep 17 00:00:00 2001 From: Patrick Norton Date: Thu, 12 Aug 2021 09:05:42 -0400 Subject: [PATCH 2/6] Added exponential formatting to BigInt --- src/bigint.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- src/biguint.rs | 13 +++++++------ 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/bigint.rs b/src/bigint.rs index 891eeb46..7adbb15b 100644 --- a/src/bigint.rs +++ b/src/bigint.rs @@ -12,12 +12,12 @@ use core::{i128, u128}; use core::{i64, u64}; use num_integer::{Integer, Roots}; -use num_traits::{Num, One, Pow, Signed, Zero}; +use num_traits::{Num, One, Pow, Signed, Zero, ToPrimitive}; use self::Sign::{Minus, NoSign, Plus}; use crate::big_digit::BigDigit; -use crate::biguint::to_str_radix_reversed; +use crate::biguint::{to_str_radix_reversed, fmt_exp_slow}; use crate::biguint::{BigUint, IntDigits, U32Digits, U64Digits}; mod addition; @@ -174,6 +174,50 @@ impl fmt::UpperHex for BigInt { } } +impl fmt::LowerExp for BigInt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Option::Some(small) = self.to_u64() { + fmt::LowerExp::fmt(&small, f) + } else { + // The max digits that can fit into a f64, which ensures + // that printing won't have rounding errors. + const MAX_FLOAT_DIGITS: usize = 14; + if f.precision().filter(|x| x < &MAX_FLOAT_DIGITS).is_some() { + let as_f64 = self.to_f64().unwrap(); + if as_f64.is_finite() { + fmt::LowerExp::fmt(&as_f64, f) + } else { + fmt_exp_slow(self.magnitude(), f, false, !self.is_negative()) + } + } else { + fmt_exp_slow(self.magnitude(), f, false, !self.is_negative()) + } + } + } +} + +impl fmt::UpperExp for BigInt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Option::Some(small) = self.to_i64() { + fmt::UpperExp::fmt(&small, f) + } else { + // The max digits that can fit into a f64, which ensures + // that printing won't have rounding errors. + const MAX_FLOAT_DIGITS: usize = 14; + if f.precision().filter(|&x| x < MAX_FLOAT_DIGITS).is_some() { + let as_f64 = self.to_f64().unwrap(); + if as_f64.is_finite() { + fmt::UpperExp::fmt(&as_f64, f) + } else { + fmt_exp_slow(self.magnitude(), f, true, !self.is_negative()) + } + } else { + fmt_exp_slow(self.magnitude(), f, true, !self.is_negative()) + } + } + } +} + // !-2 = !...f fe = ...0 01 = +1 // !-1 = !...f ff = ...0 00 = 0 // ! 0 = !...0 00 = ...f ff = -1 diff --git a/src/biguint.rs b/src/biguint.rs index c714a9eb..9dccd676 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -33,6 +33,7 @@ mod serde; pub(crate) use self::convert::to_str_radix_reversed; pub use self::iter::{U32Digits, U64Digits}; +use crate::Sign; /// A big unsigned integer type. pub struct BigUint { @@ -156,10 +157,10 @@ impl fmt::LowerExp for BigUint { if as_f64.is_finite() { fmt::LowerExp::fmt(&as_f64, f) } else { - fmt_exp_slow(self, f, false) + fmt_exp_slow(self, f, false, true) } } else { - fmt_exp_slow(self, f, false) + fmt_exp_slow(self, f, false, true) } } } @@ -178,16 +179,16 @@ impl fmt::UpperExp for BigUint { if as_f64.is_finite() { fmt::UpperExp::fmt(&as_f64, f) } else { - fmt_exp_slow(self, f, true) + fmt_exp_slow(self, f, true, true) } } else { - fmt_exp_slow(self, f, true) + fmt_exp_slow(self, f, true, true) } } } } -fn fmt_exp_slow(x: &BigUint, f: &mut fmt::Formatter<'_>, upper: bool) -> fmt::Result { +pub(crate) fn fmt_exp_slow(x: &BigUint, f: &mut fmt::Formatter<'_>, upper: bool, is_nonneg: bool) -> fmt::Result { let precision = f.precision().unwrap_or(16); let digits = to_str_radix_reversed(x, 10); let mut buffer = String::with_capacity(precision + 5); @@ -206,7 +207,7 @@ fn fmt_exp_slow(x: &BigUint, f: &mut fmt::Formatter<'_>, upper: bool) -> fmt::Re } buffer.push(if upper { 'E' } else { 'e' }); write!(buffer, "{}", digits.len() - 1)?; - f.pad_integral(true, "", &buffer) + f.pad_integral(is_nonneg, "", &buffer) } impl Zero for BigUint { From 1bfdd2f209e1b1389cfbc6b8da9839aece650c87 Mon Sep 17 00:00:00 2001 From: Patrick Norton Date: Sat, 28 Aug 2021 06:29:29 -0700 Subject: [PATCH 3/6] Fixed rounding issues --- src/biguint.rs | 64 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/src/biguint.rs b/src/biguint.rs index 9dccd676..31d679ec 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -171,10 +171,10 @@ impl fmt::UpperExp for BigUint { if let Option::Some(small) = self.to_u64() { fmt::UpperExp::fmt(&small, f) } else { - // The max digits that can fit into a f64, which ensures - // that printing won't have rounding errors. - const MAX_FLOAT_DIGITS: usize = 14; - if f.precision().filter(|&x| x < MAX_FLOAT_DIGITS).is_some() { + // The max digits that can fit into a f64, minus 1 to ensure there + // aren't any rounding errors. + const MAX_FLOAT_DIGITS: u32 = f64::DIGITS - 1; + if f.precision().filter(|&x| x < (MAX_FLOAT_DIGITS as usize)).is_some() { let as_f64 = self.to_f64().unwrap(); if as_f64.is_finite() { fmt::UpperExp::fmt(&as_f64, f) @@ -190,12 +190,18 @@ impl fmt::UpperExp for BigUint { pub(crate) fn fmt_exp_slow(x: &BigUint, f: &mut fmt::Formatter<'_>, upper: bool, is_nonneg: bool) -> fmt::Result { let precision = f.precision().unwrap_or(16); - let digits = to_str_radix_reversed(x, 10); + let mut digits = to_str_radix_reversed(x, 10); + let digit_count = digits.len(); + let end_carry = round_at(&mut digits, digit_count.saturating_sub(precision)); let mut buffer = String::with_capacity(precision + 5); debug_assert!(digits.len() > 1, "Values with fewer digits should use direct formatting"); let mut iter = digits.iter().rev(); - buffer.push(*iter.next().unwrap() as char); - buffer.push('.'); + if end_carry { + buffer.push_str("1."); + } else { + buffer.push(*iter.next().unwrap() as char); + buffer.push('.'); + } for &ch in iter.take(precision) { buffer.push(ch as char); } @@ -206,10 +212,52 @@ pub(crate) fn fmt_exp_slow(x: &BigUint, f: &mut fmt::Formatter<'_>, upper: bool, } } buffer.push(if upper { 'E' } else { 'e' }); - write!(buffer, "{}", digits.len() - 1)?; + let exponent = if end_carry { digit_count } else { digit_count - 1 }; + write!(buffer, "{}", exponent)?; + // pad_integral seems a little weird, but it does do the right thing, and + // the equivalent that f32 and f64 use is internal-only. f.pad_integral(is_nonneg, "", &buffer) } +fn round_at(chars: &mut [u8], last: usize) -> bool { + if last == 0 { + return false; + } + let mut carry = char_rounds_up(chars[last - 1]); + for digit in &mut chars[last..] { + if !carry { + return false; + } else { + carry = increment_digit(digit); + } + } + carry +} + +fn char_rounds_up(ch: u8) -> bool { + match ch { + b'0'..=b'4' => false, + b'5'..=b'9' => true, + _ => unreachable!("{} is not a decimal digit", ch), + } +} + +fn increment_digit(ch: &mut u8) -> bool { + *ch = match *ch { + b'0' => b'1', + b'1' => b'2', + b'2' => b'3', + b'3' => b'4', + b'5' => b'6', + b'6' => b'7', + b'7' => b'8', + b'8' => b'9', + b'9' => b'0', + _ => unreachable!("{} is not a decimal digit", ch), + }; + *ch == b'0' +} + impl Zero for BigUint { #[inline] fn zero() -> BigUint { From c77839488cdd653ec23a2e2a3b3124903ffdbafc Mon Sep 17 00:00:00 2001 From: Patrick Norton Date: Sat, 28 Aug 2021 06:39:28 -0700 Subject: [PATCH 4/6] Added import of core::f64 --- src/biguint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biguint.rs b/src/biguint.rs index 31d679ec..3d63d1ed 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -8,7 +8,7 @@ use core::fmt::{self, Write}; use core::hash; use core::mem; use core::str; -use core::{u32, u64, u8}; +use core::{u32, u64, u8, f64}; use num_integer::{Integer, Roots}; use num_traits::{Num, One, Pow, ToPrimitive, Unsigned, Zero}; From dd658dbf0dea94c507b6e5446c8d0a80d8050389 Mon Sep 17 00:00:00 2001 From: Patrick Norton Date: Sat, 28 Aug 2021 06:43:56 -0700 Subject: [PATCH 5/6] Fixed formatting issues --- src/bigint.rs | 4 ++-- src/biguint.rs | 26 ++++++++++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/bigint.rs b/src/bigint.rs index 7adbb15b..faaefab4 100644 --- a/src/bigint.rs +++ b/src/bigint.rs @@ -12,12 +12,12 @@ use core::{i128, u128}; use core::{i64, u64}; use num_integer::{Integer, Roots}; -use num_traits::{Num, One, Pow, Signed, Zero, ToPrimitive}; +use num_traits::{Num, One, Pow, Signed, ToPrimitive, Zero}; use self::Sign::{Minus, NoSign, Plus}; use crate::big_digit::BigDigit; -use crate::biguint::{to_str_radix_reversed, fmt_exp_slow}; +use crate::biguint::{fmt_exp_slow, to_str_radix_reversed}; use crate::biguint::{BigUint, IntDigits, U32Digits, U64Digits}; mod addition; diff --git a/src/biguint.rs b/src/biguint.rs index 3d63d1ed..9fe4bece 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -8,7 +8,7 @@ use core::fmt::{self, Write}; use core::hash; use core::mem; use core::str; -use core::{u32, u64, u8, f64}; +use core::{f64, u32, u64, u8}; use num_integer::{Integer, Roots}; use num_traits::{Num, One, Pow, ToPrimitive, Unsigned, Zero}; @@ -33,7 +33,6 @@ mod serde; pub(crate) use self::convert::to_str_radix_reversed; pub use self::iter::{U32Digits, U64Digits}; -use crate::Sign; /// A big unsigned integer type. pub struct BigUint { @@ -174,7 +173,10 @@ impl fmt::UpperExp for BigUint { // The max digits that can fit into a f64, minus 1 to ensure there // aren't any rounding errors. const MAX_FLOAT_DIGITS: u32 = f64::DIGITS - 1; - if f.precision().filter(|&x| x < (MAX_FLOAT_DIGITS as usize)).is_some() { + if f.precision() + .filter(|&x| x < (MAX_FLOAT_DIGITS as usize)) + .is_some() + { let as_f64 = self.to_f64().unwrap(); if as_f64.is_finite() { fmt::UpperExp::fmt(&as_f64, f) @@ -188,13 +190,21 @@ impl fmt::UpperExp for BigUint { } } -pub(crate) fn fmt_exp_slow(x: &BigUint, f: &mut fmt::Formatter<'_>, upper: bool, is_nonneg: bool) -> fmt::Result { +pub(crate) fn fmt_exp_slow( + x: &BigUint, + f: &mut fmt::Formatter<'_>, + upper: bool, + is_nonneg: bool, +) -> fmt::Result { let precision = f.precision().unwrap_or(16); let mut digits = to_str_radix_reversed(x, 10); let digit_count = digits.len(); let end_carry = round_at(&mut digits, digit_count.saturating_sub(precision)); let mut buffer = String::with_capacity(precision + 5); - debug_assert!(digits.len() > 1, "Values with fewer digits should use direct formatting"); + debug_assert!( + digits.len() > 1, + "Values with fewer digits should use direct formatting" + ); let mut iter = digits.iter().rev(); if end_carry { buffer.push_str("1."); @@ -212,7 +222,11 @@ pub(crate) fn fmt_exp_slow(x: &BigUint, f: &mut fmt::Formatter<'_>, upper: bool, } } buffer.push(if upper { 'E' } else { 'e' }); - let exponent = if end_carry { digit_count } else { digit_count - 1 }; + let exponent = if end_carry { + digit_count + } else { + digit_count - 1 + }; write!(buffer, "{}", exponent)?; // pad_integral seems a little weird, but it does do the right thing, and // the equivalent that f32 and f64 use is internal-only. From f182b63a3b8d0c8a2ca6716bb2f889c7e5d4eacc Mon Sep 17 00:00:00 2001 From: Patrick Norton Date: Sat, 28 Aug 2021 07:05:19 -0700 Subject: [PATCH 6/6] Removed fast-path that only works on versions >= 1.42 --- src/bigint.rs | 46 +++++++++++++++++++------------------------- src/biguint.rs | 52 ++++++++++++++++++++++---------------------------- 2 files changed, 43 insertions(+), 55 deletions(-) diff --git a/src/bigint.rs b/src/bigint.rs index faaefab4..eac085f7 100644 --- a/src/bigint.rs +++ b/src/bigint.rs @@ -174,46 +174,40 @@ impl fmt::UpperHex for BigInt { } } +// When we bump the MSRV to >= 1.42, we can add a fast-path where this fits into an i64. + impl fmt::LowerExp for BigInt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Option::Some(small) = self.to_u64() { - fmt::LowerExp::fmt(&small, f) - } else { - // The max digits that can fit into a f64, which ensures - // that printing won't have rounding errors. - const MAX_FLOAT_DIGITS: usize = 14; - if f.precision().filter(|x| x < &MAX_FLOAT_DIGITS).is_some() { - let as_f64 = self.to_f64().unwrap(); - if as_f64.is_finite() { - fmt::LowerExp::fmt(&as_f64, f) - } else { - fmt_exp_slow(self.magnitude(), f, false, !self.is_negative()) - } + // The max digits that can fit into a f64, which ensures + // that printing won't have rounding errors. + const MAX_FLOAT_DIGITS: usize = 14; + if f.precision().filter(|x| x < &MAX_FLOAT_DIGITS).is_some() { + let as_f64 = self.to_f64().unwrap(); + if as_f64.is_finite() { + fmt::LowerExp::fmt(&as_f64, f) } else { fmt_exp_slow(self.magnitude(), f, false, !self.is_negative()) } + } else { + fmt_exp_slow(self.magnitude(), f, false, !self.is_negative()) } } } impl fmt::UpperExp for BigInt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Option::Some(small) = self.to_i64() { - fmt::UpperExp::fmt(&small, f) - } else { - // The max digits that can fit into a f64, which ensures - // that printing won't have rounding errors. - const MAX_FLOAT_DIGITS: usize = 14; - if f.precision().filter(|&x| x < MAX_FLOAT_DIGITS).is_some() { - let as_f64 = self.to_f64().unwrap(); - if as_f64.is_finite() { - fmt::UpperExp::fmt(&as_f64, f) - } else { - fmt_exp_slow(self.magnitude(), f, true, !self.is_negative()) - } + // The max digits that can fit into a f64, which ensures + // that printing won't have rounding errors. + const MAX_FLOAT_DIGITS: usize = 14; + if f.precision().filter(|&x| x < MAX_FLOAT_DIGITS).is_some() { + let as_f64 = self.to_f64().unwrap(); + if as_f64.is_finite() { + fmt::UpperExp::fmt(&as_f64, f) } else { fmt_exp_slow(self.magnitude(), f, true, !self.is_negative()) } + } else { + fmt_exp_slow(self.magnitude(), f, true, !self.is_negative()) } } } diff --git a/src/biguint.rs b/src/biguint.rs index 9fe4bece..26311270 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -143,49 +143,43 @@ impl fmt::Octal for BigUint { } } +// When we bump the MSRV to >= 1.42, we can add a fast-path where this fits into a u64. + impl fmt::LowerExp for BigUint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Option::Some(small) = self.to_u64() { - fmt::LowerExp::fmt(&small, f) - } else { - // The max digits that can fit into a f64, which ensures - // that printing won't have rounding errors. - const MAX_FLOAT_DIGITS: usize = 14; - if f.precision().filter(|x| x < &MAX_FLOAT_DIGITS).is_some() { - let as_f64 = self.to_f64().unwrap(); - if as_f64.is_finite() { - fmt::LowerExp::fmt(&as_f64, f) - } else { - fmt_exp_slow(self, f, false, true) - } + // The max digits that can fit into a f64, which ensures + // that printing won't have rounding errors. + const MAX_FLOAT_DIGITS: usize = 14; + if f.precision().filter(|x| x < &MAX_FLOAT_DIGITS).is_some() { + let as_f64 = self.to_f64().unwrap(); + if as_f64.is_finite() { + fmt::LowerExp::fmt(&as_f64, f) } else { fmt_exp_slow(self, f, false, true) } + } else { + fmt_exp_slow(self, f, false, true) } } } impl fmt::UpperExp for BigUint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Option::Some(small) = self.to_u64() { - fmt::UpperExp::fmt(&small, f) - } else { - // The max digits that can fit into a f64, minus 1 to ensure there - // aren't any rounding errors. - const MAX_FLOAT_DIGITS: u32 = f64::DIGITS - 1; - if f.precision() - .filter(|&x| x < (MAX_FLOAT_DIGITS as usize)) - .is_some() - { - let as_f64 = self.to_f64().unwrap(); - if as_f64.is_finite() { - fmt::UpperExp::fmt(&as_f64, f) - } else { - fmt_exp_slow(self, f, true, true) - } + // The max digits that can fit into a f64, minus 1 to ensure there + // aren't any rounding errors. + const MAX_FLOAT_DIGITS: u32 = f64::DIGITS - 1; + if f.precision() + .filter(|&x| x < (MAX_FLOAT_DIGITS as usize)) + .is_some() + { + let as_f64 = self.to_f64().unwrap(); + if as_f64.is_finite() { + fmt::UpperExp::fmt(&as_f64, f) } else { fmt_exp_slow(self, f, true, true) } + } else { + fmt_exp_slow(self, f, true, true) } } }