Files
core-decimal-calc/src/calc.rs

189 lines
5.1 KiB
Rust

use core::{convert::Infallible, fmt::Display, marker::PhantomData};
use num_traits::{float::FloatCore, PrimInt, Unsigned};
#[cfg(feature = "ufmt")]
use ufmt::derive::uDebug;
use crate::{Decimal, DecimalError};
#[derive(Debug)]
#[cfg_attr(feature = "ufmt", derive(uDebug))]
pub enum StackCalcError {
StackOverflow,
StackUnderflow,
DecimalError(DecimalError),
}
impl Display for StackCalcError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
StackCalcError::StackOverflow => f.write_str("stack overflow"),
StackCalcError::StackUnderflow => f.write_str("stack underflow"),
StackCalcError::DecimalError(de) => de.fmt(f),
}
}
}
#[derive(Debug, Clone)]
pub struct StackCalc<F: FloatCore, const STACK: usize = 2, const DSIZE: usize = 5, EXP = u16> {
stack: [Decimal<DSIZE, EXP>; STACK],
index: usize,
phantom: PhantomData<F>,
}
#[cfg(feature = "ufmt")]
impl<F: FloatCore + ufmt::uDebug, const STACK: usize, const DSIZE: usize, EXP: ufmt::uDebug>
ufmt::uDebug for StackCalc<F, STACK, DSIZE, EXP>
{
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
f.debug_struct("StackCalc")?
.field("stack", &self.stack.as_slice())?
.field("index", &self.index)?
.finish()
}
}
impl<F: FloatCore, const STACK: usize, const DSIZE: usize, EXP: PrimInt> Default
for StackCalc<F, STACK, DSIZE, EXP>
{
fn default() -> Self {
StackCalc {
stack: [Decimal::default(); STACK],
index: 0,
phantom: PhantomData,
}
}
}
impl From<DecimalError> for StackCalcError {
fn from(val: DecimalError) -> Self {
StackCalcError::DecimalError(val)
}
}
impl From<Infallible> for StackCalcError {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
impl<F: FloatCore, const STACK: usize, const DSIZE: usize, EXP: PrimInt + Unsigned>
StackCalc<F, STACK, DSIZE, EXP>
{
pub fn is_full(&self) -> bool {
self.index == STACK
}
pub fn is_empty(&self) -> bool {
self.index == 0
}
pub fn len(&self) -> usize {
self.index
}
pub fn reset(&mut self) {
self.index = 0;
}
pub fn last(&self) -> Option<Decimal<DSIZE, EXP>> {
if self.is_empty() {
return None;
}
Some(self.stack[self.index - 1])
}
pub fn iter(&self) -> impl Iterator<Item = &Decimal<DSIZE, EXP>> {
self.stack.iter().take(self.index)
}
pub fn push(
&mut self,
val: impl TryInto<Decimal<DSIZE, EXP>, Error: Into<StackCalcError>>,
) -> Result<(), StackCalcError> {
if self.is_full() {
return Err(StackCalcError::StackOverflow);
}
self.stack[self.index] = val.try_into().map_err(Into::into)?;
self.index += 1;
Ok(())
}
pub fn pop(&mut self) -> Result<Decimal<DSIZE, EXP>, StackCalcError> {
if self.is_empty() {
return Err(StackCalcError::StackUnderflow);
}
self.index -= 1;
Ok(self.stack[self.index])
}
fn pop_pair_float(&mut self) -> Result<(F, F), StackCalcError> {
let a = self.pop()?;
let b = self.pop()?;
let a = a.to_float::<F>()?;
let b = b.to_float::<F>()?;
Ok((a, b))
}
fn calc_pair_float(
&mut self,
f: impl FnOnce(F, F) -> Result<F, StackCalcError>,
) -> Result<Decimal<DSIZE, EXP>, StackCalcError> {
let (a, b) = self.pop_pair_float()?;
let res = Decimal::from_float(f(a, b)?)?;
self.push(res)?;
Ok(res)
}
pub fn add(&mut self) -> Result<Decimal<DSIZE, EXP>, StackCalcError> {
self.calc_pair_float(|a, b| Ok(b + a))
}
pub fn sub(&mut self) -> Result<Decimal<DSIZE, EXP>, StackCalcError> {
self.calc_pair_float(|a, b| Ok(b - a))
}
pub fn mul(&mut self) -> Result<Decimal<DSIZE, EXP>, StackCalcError> {
self.calc_pair_float(|a, b| Ok(b * a))
}
pub fn div(&mut self) -> Result<Decimal<DSIZE, EXP>, StackCalcError> {
self.calc_pair_float(|a, b| Ok(b / a))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() -> Result<(), StackCalcError> {
let mut calc = StackCalc::<f64, 3, 7>::default();
calc.push(Decimal::default())?;
calc.push(1337.42)?;
assert_eq!(calc.add()?, Decimal::try_from(1337.42)?);
calc.push(1337.42)?;
assert_eq!(calc.add()?, Decimal::try_from(1337.42 * 2.0)?);
calc.push(2.0)?;
calc.push(3.0)?;
assert!(matches!(
calc.push(3.0).unwrap_err(),
StackCalcError::StackOverflow
));
calc.add()?;
assert_eq!(calc.last().unwrap(), Decimal::try_from(5.0)?);
calc.add()?;
assert_eq!(
calc.last().unwrap(),
Decimal::try_from(1337.42 * 2.0 + 5.0)?
);
assert!(matches!(
calc.add().unwrap_err(),
StackCalcError::StackUnderflow
));
Ok(())
}
}