diff --git a/core/src/num/biguint.rs b/core/src/num/biguint.rs index f64cbb28..fca1dae2 100644 --- a/core/src/num/biguint.rs +++ b/core/src/num/biguint.rs @@ -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)] @@ -1379,16 +1379,11 @@ impl Format for BigUint { type Out = FormattedBigUint; fn format(&self, params: &Self::Params, int: &I) -> FResult> { - 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, @@ -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, @@ -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, @@ -1474,25 +1471,60 @@ enum FormattedBigUintType { #[must_use] #[derive(Debug)] pub(crate) struct FormattedBigUint { - base: Option, + 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 = 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}")?; } } } diff --git a/core/tests/integration_tests.rs b/core/tests/integration_tests.rs index 1fc46546..778bb727 100644 --- a/core/tests/integration_tests.rs +++ b/core/tests/integration_tests.rs @@ -1,4 +1,4 @@ -use fend_core::{Context, evaluate}; +use fend_core::{evaluate, Context}; #[track_caller] fn test_serialization_roundtrip(context: &mut Context) { @@ -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"; @@ -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] @@ -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"); @@ -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] @@ -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] @@ -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]