Skip to content
Open
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
70 changes: 51 additions & 19 deletions core/src/num/biguint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use crate::error::{FendError, Interrupt};
use crate::format::Format;
use crate::interrupt::test_int;
use crate::num::bigrat::sign::Sign;
use crate::num::{Base, Exact, Range, RangeBound, out_of_range};
use crate::num::{out_of_range, Base, Exact, Range, RangeBound};
use crate::result::FResult;
use crate::serialize::CborValue;
use std::cmp::{Ordering, max};
use std::cmp::{max, Ordering};
use std::{fmt, hash};

#[derive(Clone)]
Expand Down Expand Up @@ -1379,16 +1379,11 @@ impl Format for BigUint {
type Out = FormattedBigUint;

fn format<I: Interrupt>(&self, params: &Self::Params, int: &I) -> FResult<Exact<Self::Out>> {
let base_prefix = if params.write_base_prefix {
Some(params.base)
} else {
None
};

if self.is_zero() {
return Ok(Exact::new(
FormattedBigUint {
base: base_prefix,
base: params.base,
write_base_prefix: params.write_base_prefix,
ty: FormattedBigUintType::Zero,
},
true,
Expand All @@ -1400,7 +1395,8 @@ impl Format for BigUint {
if num.value_len() == 1 && params.base.base_as_u8() == 10 && params.sf_limit.is_none() {
Exact::new(
FormattedBigUint {
base: base_prefix,
base: params.base,
write_base_prefix: params.write_base_prefix,
ty: FormattedBigUintType::Simple(num.get(0)),
},
true,
Expand Down Expand Up @@ -1454,7 +1450,8 @@ impl Format for BigUint {
.is_none_or(|sf| sf >= output.len() - num_leading_zeroes);
Exact::new(
FormattedBigUint {
base: base_prefix,
base: params.base,
write_base_prefix: params.write_base_prefix,
ty: FormattedBigUintType::Complex(output, params.sf_limit),
},
exact,
Expand All @@ -1474,25 +1471,60 @@ enum FormattedBigUintType {
#[must_use]
#[derive(Debug)]
pub(crate) struct FormattedBigUint {
base: Option<Base>,
base: Base,
write_base_prefix: bool,
ty: FormattedBigUintType,
}

impl fmt::Display for FormattedBigUint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
if let Some(base) = self.base {
base.write_prefix(f)?;
if self.write_base_prefix {
self.base.write_prefix(f)?;
}
match &self.ty {
FormattedBigUintType::Zero => write!(f, "0")?,
FormattedBigUintType::Simple(i) => write!(f, "{i}")?,
FormattedBigUintType::Complex(s, sf_limit) => {
for (i, ch) in s.chars().rev().enumerate() {
if sf_limit.is_some() && &Some(i) >= sf_limit {
write!(f, "0")?;
} else {
write!(f, "{ch}")?;
let base_as_u32: u32 = u32::from(self.base.base_as_u8());
let mut rev_chars: Vec<char> = s.chars().rev().collect();

if let Some(sf) = sf_limit
&& *sf > 0 && *sf < rev_chars.len() {
let num_truncated = rev_chars.len() - sf;
let dropped_char = rev_chars[*sf];
let val = dropped_char.to_digit(base_as_u32).unwrap_or(0);

// Keep only the significant figures we want
rev_chars.truncate(*sf);

// Check if we should round up (equivalent to fraction >= 1/2)
if val * 2 >= base_as_u32 {
let mut carry = 1u32;
for i in (0..*sf).rev() {
let d = rev_chars[i].to_digit(base_as_u32).unwrap_or(0) + carry;
if d >= base_as_u32 {
rev_chars[i] = '0';
carry = 1;
} else {
rev_chars[i] = char::from_digit(d, base_as_u32).unwrap();
carry = 0;
break;
}
}
if carry > 0 {
rev_chars.insert(0, '1');
}
}

// Pad with trailing zeros to maintain the original length
let target_len = rev_chars.len() + num_truncated;
while rev_chars.len() < target_len {
rev_chars.push('0');
}
}

for ch in rev_chars {
write!(f, "{ch}")?;
}
}
}
Expand Down
50 changes: 34 additions & 16 deletions core/tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use fend_core::{Context, evaluate};
use fend_core::{evaluate, Context};

#[track_caller]
fn test_serialization_roundtrip(context: &mut Context) {
Expand Down Expand Up @@ -125,12 +125,10 @@ fn two_pi() {
#[test]
fn pi_to_fraction() {
let mut ctx = Context::new();
assert!(
evaluate("pi to fraction", &mut ctx)
.unwrap()
.get_main_result()
.starts_with("approx.")
);
assert!(evaluate("pi to fraction", &mut ctx)
.unwrap()
.get_main_result()
.starts_with("approx."));
}

const DIVISION_BY_ZERO_ERROR: &str = "division by zero";
Expand Down Expand Up @@ -4240,17 +4238,17 @@ fn sf_3() {

#[test]
fn sf_4() {
test_eval("1234567.55645 to 4 sf", "approx. 1234000");
test_eval("1234567.55645 to 4 sf", "approx. 1235000");
}

#[test]
fn sf_5() {
test_eval("1234567.55645 to 5 sf", "approx. 1234500");
test_eval("1234567.55645 to 5 sf", "approx. 1234600");
}

#[test]
fn sf_6() {
test_eval("1234567.55645 to 6 sf", "approx. 1234560");
test_eval("1234567.55645 to 6 sf", "approx. 1234570");
}

#[test]
Expand Down Expand Up @@ -4298,6 +4296,26 @@ fn sf_small_2() {
test_eval("pi / 1000000 to 2 sf", "approx. 0.0000031");
}

#[test]
fn sf_hex_17f_1() {
test_eval("0x17f to 1 sf", "approx. 0x100");
}

#[test]
fn sf_hex_17f_2() {
test_eval("0x17f to 2 sf", "approx. 0x180");
}

#[test]
fn sf_hex_ff_1() {
test_eval("0xff to 1 sf", "approx. 0x100");
}

#[test]
fn sf_hex_ff_2() {
test_eval("0xff to 2 sf", "0xff");
}

#[test]
fn sf_rounding_integer_carry() {
test_eval("123.9 to 3 sf", "approx. 124");
Expand Down Expand Up @@ -4380,12 +4398,12 @@ fn million_pi_3_sf() {

#[test]
fn million_pi_4_sf() {
test_eval("1e6 pi to 4 sf", "approx. 3141000");
test_eval("1e6 pi to 4 sf", "approx. 3142000");
}

#[test]
fn million_pi_5_sf() {
test_eval("1e6 pi to 5 sf", "approx. 3141500");
test_eval("1e6 pi to 5 sf", "approx. 3141600");
}

#[test]
Expand Down Expand Up @@ -4430,17 +4448,17 @@ fn large_integer_to_3_sf() {

#[test]
fn large_integer_to_4_sf() {
test_eval("1234567 to 4 sf", "approx. 1234000");
test_eval("1234567 to 4 sf", "approx. 1235000");
}

#[test]
fn large_integer_to_5_sf() {
test_eval("1234567 to 5 sf", "approx. 1234500");
test_eval("1234567 to 5 sf", "approx. 1234600");
}

#[test]
fn large_integer_to_6_sf() {
test_eval("1234567 to 6 sf", "approx. 1234560");
test_eval("1234567 to 6 sf", "approx. 1234570");
}

#[test]
Expand All @@ -4465,7 +4483,7 @@ fn large_integer_to_10_sf() {

#[test]
fn trailing_zeroes_sf_1() {
test_eval("1234560 to 5sf", "approx. 1234500");
test_eval("1234560 to 5sf", "approx. 1234600");
}

#[test]
Expand Down
Loading