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

Add exponential formatting to BigInt #214

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 46 additions & 2 deletions src/bigint.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
69 changes: 68 additions & 1 deletion src/biguint.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -143,6 +144,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, 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, which ensures
// that printing won't have rounding errors.
const MAX_FLOAT_DIGITS: usize = 14;
Copy link
Member

Choose a reason for hiding this comment

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

f64::DIGITS is 15, though it does say "approximate". Perhaps we should us that, minus one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I subtracted 1 to ensure the rounding wouldn't turn out weird, but forgot to update the docs. I didn't know f64::DIGITS existed, so I'll update to take that into account.

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, true)
}
} else {
fmt_exp_slow(self, f, true, true)
}
}
}
}

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);
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);
}
Copy link
Member

Choose a reason for hiding this comment

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

Don't we need rounding if there were more digits than the precision required?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point! Added.

// 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(is_nonneg, "", &buffer)
Copy link
Member

Choose a reason for hiding this comment

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

pad_integral feels weird in this case, but I'm not sure it's wrong. The standard library uses its internal pad_formatted_parts for exponential integers, but at a glance I don't see what would be different.

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's definitely weird, but it does seem to do the right thing. I've added a comment to that effect.

}

impl Zero for BigUint {
#[inline]
fn zero() -> BigUint {
Expand Down