use crate::{ParseError, Rounding};
use core::{
fmt,
mem::MaybeUninit,
num::{IntErrorKind, ParseIntError},
ops::{AddAssign, SubAssign},
};
use num_traits::{
identities::{ConstOne, ConstZero, Zero},
int::PrimInt,
ops::wrapping::WrappingAdd,
Num, SaturatingAdd,
};
pub trait FpdecInner:
PrimInt + ConstOne + ConstZero + AddAssign + SubAssign + WrappingAdd + SaturatingAdd + Zero
{
const MAX: Self;
const MIN: Self;
const TEN: Self;
const MAX_POWERS: Self;
const DIGITS: u32;
const NEG_MIN_STR: &'static str;
type Unsigned: FpdecInner + fmt::Display;
fn unsigned_abs(self) -> Self::Unsigned;
fn get_exp(i: usize) -> Option<Self>;
fn calc_mul_div(self, b: Self, c: Self, rounding: Rounding) -> Option<Self>;
fn checked_mul_ext(self, rhs: Self, diff_scale: i32, rounding: Rounding) -> Option<Self> {
if diff_scale > 0 {
let exp = Self::get_exp(diff_scale as usize)?;
self.calc_mul_div(rhs, exp, rounding)
} else if diff_scale < 0 {
let exp = Self::get_exp(-diff_scale as usize)?;
self.checked_mul(&rhs)?.checked_mul(&exp)
} else {
self.checked_mul(&rhs)
}
}
fn checked_div_ext(self, rhs: Self, diff_scale: i32, rounding: Rounding) -> Option<Self> {
if diff_scale > 0 {
let exp = Self::get_exp(diff_scale as usize)?;
let q = self.rounding_div(rhs, rounding)?;
q.rounding_div(exp, rounding)
} else if diff_scale < 0 {
let exp = Self::get_exp(-diff_scale as usize)?;
self.calc_mul_div(exp, rhs, rounding)
} else {
self.rounding_div(rhs, rounding)
}
}
fn round_diff_with_rounding(self, diff_scale: i32, rounding: Rounding) -> Self {
if diff_scale <= 0 {
return self;
}
match Self::get_exp(diff_scale as usize) {
None => Self::ZERO,
Some(exp) => {
self.rounding_div(exp, rounding).unwrap() * exp
}
}
}
fn rounding_div(self, b: Self, rounding: Rounding) -> Option<Self> {
let q = self.checked_div(&b)?;
let remain = self % b;
if remain == Self::ZERO {
return Some(q);
}
let ret = if (self ^ b) > Self::ZERO {
match rounding {
Rounding::Floor | Rounding::TowardsZero => q,
Rounding::Ceiling | Rounding::AwayFromZero => q + Self::ONE,
Rounding::Round => {
if remain.saturating_add(remain) >= b {
q + Self::ONE
} else {
q
}
}
}
} else {
match rounding {
Rounding::Floor | Rounding::AwayFromZero => q - Self::ONE,
Rounding::Ceiling | Rounding::TowardsZero => q,
Rounding::Round => {
let r = remain.unsigned_abs();
if r.saturating_add(&r) >= b.unsigned_abs() {
q - Self::ONE
} else {
q
}
}
}
};
Some(ret)
}
fn parse_int_as_negative(s: &str) -> Result<Self, ParseIntError>
where
Self: Num<FromStrRadixErr = ParseIntError>,
{
match Self::from_str_radix(s, 10) {
Ok(num) => {
Ok((!num).wrapping_add(&Self::ONE))
}
Err(err) => {
if err.kind() == &IntErrorKind::PosOverflow
&& s.trim_start_matches('0') == Self::NEG_MIN_STR
{
Ok(Self::MIN)
} else {
Err(err)
}
}
}
}
fn try_from_str(s: &str, scale: i32) -> Result<Self, ParseError>
where
Self: Num<FromStrRadixErr = ParseIntError>,
{
let (num, raw_scale) = Self::try_from_str_only(s)?;
if num.is_zero() {
Ok(num)
} else if raw_scale == scale {
Ok(num)
} else if raw_scale > scale {
Err(ParseError::Precision)
} else {
Self::get_exp((scale - raw_scale) as usize)
.ok_or(ParseError::Precision)?
.checked_mul(&num)
.ok_or(ParseError::Overflow)
}
}
fn try_from_str_only(s: &str) -> Result<(Self, i32), ParseError>
where
Self: Num<FromStrRadixErr = ParseIntError>,
{
if s.is_empty() {
return Err(ParseError::Empty);
}
if let Some((int_str, frac_str)) = s.split_once('.') {
let int_num = Self::from_str_radix(int_str, 10)?;
let frac_num = if s.as_bytes()[0] == b'-' {
Self::parse_int_as_negative(frac_str)?
} else {
Self::from_str_radix(frac_str, 10)?
};
let inner = if int_num.is_zero() {
frac_num
} else {
Self::get_exp(frac_str.len())
.ok_or(ParseError::Precision)?
.checked_mul(&int_num)
.ok_or(ParseError::Overflow)?
.checked_add(&frac_num)
.ok_or(ParseError::Overflow)?
};
Ok((inner, frac_str.len() as i32))
} else {
if s == "0" || s == "-0" || s == "+0" {
return Ok((Self::ZERO, 0));
}
let new_int_str = s.trim_end_matches('0');
let diff = s.len() - new_int_str.len();
Ok((Self::from_str_radix(new_int_str, 10)?, -(diff as i32)))
}
}
fn display_fmt(self, scale: i32, f: &mut fmt::Formatter) -> fmt::Result {
if f.width().is_some() {
let mut buf = ArrayWriter::<1050>::new();
display_num(self.unsigned_abs(), scale, f.precision(), &mut buf)?;
f.pad_integral(self >= Self::ZERO, "", buf.ref_str())
} else {
if self < Self::ZERO {
write!(f, "-")?;
} else if f.sign_plus() {
write!(f, "+")?;
}
display_num(self.unsigned_abs(), scale, f.precision(), f)
}
}
fn checked_from_int(self, scale: i32) -> Result<Self, ParseError> {
if scale > 0 {
let exp = Self::get_exp(scale as usize).ok_or(ParseError::Overflow)?;
self.checked_mul(&exp).ok_or(ParseError::Overflow)
} else if scale < 0 {
let exp = Self::get_exp(-scale as usize).ok_or(ParseError::Precision)?;
if !(self % exp).is_zero() {
return Err(ParseError::Precision);
}
Ok(self / exp)
} else {
Ok(self)
}
}
}
fn display_num<I, W>(uns: I, scale: i32, precision: Option<usize>, w: &mut W) -> fmt::Result
where
I: FpdecInner + fmt::Display,
W: fmt::Write,
{
if scale <= 0 {
write!(w, "{}", uns)?;
if scale < 0 && !uns.is_zero() {
let width = (-scale) as usize;
write!(w, "{:0>width$}", 0)?;
}
let precision = precision.unwrap_or(0);
if precision != 0 {
write!(w, ".{:0>precision$}", 0)?;
}
return Ok(());
}
let scale = scale as usize;
let (int, frac, exp) = match I::get_exp(scale) {
Some(exp) => (uns / exp, uns % exp, Some(exp)),
None => (I::ZERO, uns, None),
};
match precision {
None => {
if frac.is_zero() {
return write!(w, "{}", int);
}
let (frac, zeros) = {
let mut zeros = 0;
let mut n = frac;
while (n % I::TEN).is_zero() {
n = n / I::TEN;
zeros += 1;
}
(n, zeros)
};
write!(w, "{}.{:0>width$}", int, frac, width = scale - zeros)
}
Some(precision) if precision == 0 => match exp {
Some(exp) => {
if frac.saturating_add(frac) >= exp {
write!(w, "{}", int + I::ONE)
} else {
write!(w, "{}", int)
}
}
None => write!(w, "0"),
},
Some(precision) => {
if precision == scale {
write!(w, "{}.{:0>scale$}", int, frac)
} else if precision > scale {
let pad = precision - scale;
write!(w, "{}.{:0>scale$}{:0>pad$}", int, frac, 0)
} else {
let frac = match I::get_exp(scale - precision) {
Some(exp) => frac.rounding_div(exp, Rounding::Round).unwrap(),
None => I::ZERO,
};
write!(w, "{}.{:0>precision$}", int, frac)
}
}
}
}
struct ArrayWriter<const CAP: usize> {
pos: usize,
buf: MaybeUninit<[u8; CAP]>,
}
impl<const CAP: usize> ArrayWriter<CAP> {
fn new() -> Self {
Self {
pos: 0,
buf: MaybeUninit::uninit(),
}
}
fn ref_str(&self) -> &str {
unsafe {
let buf = &self.buf.assume_init_ref()[..self.pos];
str::from_utf8_unchecked(buf)
}
}
}
impl<const CAP: usize> fmt::Write for ArrayWriter<CAP> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let len = s.len();
if self.pos + len > CAP {
return Err(fmt::Error);
}
let buf = unsafe { &mut self.buf.assume_init_mut()[self.pos..self.pos + len] };
buf.copy_from_slice(s.as_bytes());
self.pos += len;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
extern crate std;
use std::fmt;
struct TestFmt<I> {
n: I,
scale: i32,
}
impl<I: FpdecInner + fmt::Display> fmt::Display for TestFmt<I> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
self.n.display_fmt(self.scale, f)
}
}
fn do_test_format<I>(s: &str, scale: i32, n: I)
where
I: FpdecInner + fmt::Display + fmt::Debug + Num<FromStrRadixErr = ParseIntError>,
{
assert_eq!(I::try_from_str(s, scale), Ok(n));
let (n1, scale1) = I::try_from_str_only(s).unwrap();
let n2 = n1 * I::TEN.pow((scale - scale1) as u32);
assert_eq!(n2, n);
let ts = TestFmt { n, scale };
assert_eq!(std::format!("{}", &ts), s);
}
fn do_test_format_num_only<I>(n: I)
where
I: FpdecInner + fmt::Display + fmt::Debug + Num<FromStrRadixErr = ParseIntError>,
{
for scale in -100..100 {
let ts = TestFmt { n, scale };
let out = std::format!("{}", ts);
assert_eq!(I::try_from_str(&out, scale), Ok(n));
}
}
#[test]
fn test_format() {
assert_eq!(i8::try_from_str("", 2), Err(ParseError::Empty));
assert_eq!(i8::try_from_str("0", 2), Ok(0));
assert_eq!(i8::try_from_str("0.0", 2), Ok(0));
assert_eq!(i8::try_from_str("-0", 2), Ok(0));
assert_eq!(i8::try_from_str("-0.0", 2), Ok(0));
assert_eq!(i8::try_from_str("+0", 2), Ok(0));
assert_eq!(i8::try_from_str("+0.0", 2), Ok(0));
do_test_format("12300", -2, 123_i8);
do_test_format("1230", -1, 123_i8);
do_test_format("123", 0, 123_i8);
do_test_format("12.3", 1, 123_i8);
do_test_format("1.23", 2, 123_i8);
do_test_format("0.123", 3, 123_i8);
do_test_format("0.0123", 4, 123_i8);
do_test_format("0.00123", 5, 123_i8);
do_test_format("0.000123", 6, 123_i8);
do_test_format("12000", -2, 120_i8);
do_test_format("1200", -1, 120_i8);
do_test_format("120", 0, 120_i8);
do_test_format("12", 1, 120_i8);
do_test_format("1.2", 2, 120_i8);
do_test_format("0.12", 3, 120_i8);
do_test_format("0.012", 4, 120_i8);
do_test_format("0.0012", 5, 120_i8);
do_test_format("0.00012", 6, 120_i8);
do_test_format("-12800", -2, -128_i8);
do_test_format("-1280", -1, -128_i8);
do_test_format("-128", 0, -128_i8);
do_test_format("-12.8", 1, -128_i8);
do_test_format("-1.28", 2, -128_i8);
do_test_format("-0.128", 3, -128_i8);
do_test_format("-0.0128", 4, -128_i8);
do_test_format("-0.00128", 5, -128_i8);
do_test_format("-0.000128", 6, -128_i8);
do_test_format("12300", -2, 123_u8);
do_test_format("1230", -1, 123_u8);
do_test_format("123", 0, 123_u8);
do_test_format("12.3", 1, 123_u8);
do_test_format("1.23", 2, 123_u8);
do_test_format("0.123", 3, 123_u8);
do_test_format("0.0123", 4, 123_u8);
do_test_format("0.00123", 5, 123_u8);
do_test_format("0.000123", 6, 123_u8);
do_test_format("12000", -2, 120_u8);
do_test_format("1200", -1, 120_u8);
do_test_format("120", 0, 120_u8);
do_test_format("12", 1, 120_u8);
do_test_format("1.2", 2, 120_u8);
do_test_format("0.12", 3, 120_u8);
do_test_format("0.012", 4, 120_u8);
do_test_format("0.0012", 5, 120_u8);
do_test_format("0.00012", 6, 120_u8);
do_test_format("25500", -2, 255_u8);
do_test_format("2550", -1, 255_u8);
do_test_format("255", 0, 255_u8);
do_test_format("25.5", 1, 255_u8);
do_test_format("2.55", 2, 255_u8);
do_test_format("0.255", 3, 255_u8);
do_test_format("0.0255", 4, 255_u8);
do_test_format("0.00255", 5, 255_u8);
do_test_format("0.000255", 6, 255_u8);
}
#[test]
fn test_format_num_only() {
do_test_format_num_only(0);
do_test_format_num_only(1_u8);
do_test_format_num_only(12_u8);
do_test_format_num_only(123_u8);
do_test_format_num_only(255_u8);
do_test_format_num_only(1_i8);
do_test_format_num_only(12_i8);
do_test_format_num_only(123_i8);
do_test_format_num_only(-1_i8);
do_test_format_num_only(-12_i8);
do_test_format_num_only(-123_i8);
do_test_format_num_only(-128_i8);
do_test_format_num_only(1_i128);
do_test_format_num_only(12_i128);
do_test_format_num_only(123_i128);
do_test_format_num_only(-1_i128);
do_test_format_num_only(-12_i128);
do_test_format_num_only(-123_i128);
do_test_format_num_only(i32::MAX);
do_test_format_num_only(i32::MIN);
do_test_format_num_only(i64::MAX);
do_test_format_num_only(i64::MIN);
do_test_format_num_only(i128::MAX);
do_test_format_num_only(i128::MIN);
do_test_format_num_only(i32::MAX / 2);
do_test_format_num_only(i32::MIN / 2);
do_test_format_num_only(i64::MAX / 2);
do_test_format_num_only(i64::MIN / 2);
do_test_format_num_only(i128::MAX / 2);
do_test_format_num_only(i128::MIN / 2);
do_test_format_num_only(1_u128);
do_test_format_num_only(12_u128);
do_test_format_num_only(123_u128);
do_test_format_num_only(u32::MAX);
do_test_format_num_only(u64::MAX);
do_test_format_num_only(u128::MAX);
do_test_format_num_only(u32::MAX / 2);
do_test_format_num_only(u64::MAX / 2);
do_test_format_num_only(u128::MAX / 2);
}
#[test]
fn test_options() {
let d = TestFmt {
n: 12_3470,
scale: 4,
};
let d2 = TestFmt {
n: 12_5470,
scale: 4,
};
let n = TestFmt {
n: -12_3470,
scale: 4,
};
let z = TestFmt { n: 0, scale: 4 };
assert_eq!(std::format!("{}", &d), "12.347");
assert_eq!(std::format!("{}", &n), "-12.347");
assert_eq!(std::format!("{}", &z), "0");
assert_eq!(std::format!("{:x>10}", &d), "xxxx12.347");
assert_eq!(std::format!("{:0>10}", &d), "000012.347");
assert_eq!(std::format!("{:x>10}", &n), "xxx-12.347");
assert_eq!(std::format!("{:010}", &n), "-00012.347");
assert_eq!(std::format!("{:.0}", &d), "12");
assert_eq!(std::format!("{:.0}", &d2), "13");
assert_eq!(std::format!("{:.2}", &d), "12.35");
assert_eq!(std::format!("{:.4}", &d), "12.3470");
assert_eq!(std::format!("{:.6}", &d), "12.347000");
assert_eq!(std::format!("{:.0}", &n), "-12");
assert_eq!(std::format!("{:.2}", &n), "-12.35");
assert_eq!(std::format!("{:.4}", &n), "-12.3470");
assert_eq!(std::format!("{:.6}", &n), "-12.347000");
assert_eq!(std::format!("{:.0}", &z), "0");
assert_eq!(std::format!("{:.2}", &z), "0.00");
assert_eq!(std::format!("{:.4}", &z), "0.0000");
assert_eq!(std::format!("{:.6}", &z), "0.000000");
assert_eq!(std::format!("{:+}", &d), "+12.347");
assert_eq!(std::format!("{:+}", &n), "-12.347");
assert_eq!(std::format!("{:+}", &z), "+0");
assert_eq!(std::format!("{:x>+10.2}", &d), "xxxx+12.35");
assert_eq!(std::format!("{:+010.2}", &d), "+000012.35");
let d = TestFmt { n: 12, scale: -4 };
assert_eq!(std::format!("{}", &d), "120000");
assert_eq!(std::format!("{:.0}", &d), "120000");
assert_eq!(std::format!("{:.2}", &d), "120000.00");
}
}