use super::cell_type::CellType;
use super::field_type::{FieldType, IntoValue};
use super::value::{valid_decimal, Decimal, DecimalError, RawValue};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Numeric<const P: u8, const S: u8> {
value: Decimal,
}
impl<const P: u8, const S: u8> Numeric<P, S> {
const LIMITS: () = assert!(
valid_decimal(P, S),
"Numeric needs 1 <= P <= 38, S <= 37 and S <= P"
);
pub fn new(unscaled: i128) -> Result<Self, DecimalError> {
let () = Self::LIMITS;
let fits = 10_u128
.checked_pow(u32::from(P))
.is_some_and(|limit| unscaled.unsigned_abs() < limit);
if fits {
Decimal::new(unscaled, S).map(|value| Self { value })
} else {
Err(DecimalError::TooManyDigits { precision: P })
}
}
#[must_use]
pub const fn unscaled(&self) -> i128 {
self.value.unscaled()
}
}
impl<const P: u8, const S: u8> FieldType for Numeric<P, S> {
fn cell_type() -> CellType {
let () = Self::LIMITS;
CellType::Decimal {
precision: P,
scale: S,
}
}
}
impl<const P: u8, const S: u8> IntoValue for Numeric<P, S> {
fn into_value(self) -> RawValue {
RawValue::Decimal(self.value)
}
}
#[cfg(test)]
mod tests {
use super::Numeric;
use crate::cell::cell_type::CellType;
use crate::cell::field_type::{FieldType, IntoValue};
use crate::cell::value::{Decimal, DecimalError, RawValue};
#[test]
fn numeric_is_a_decimal_column_of_its_precision_and_scale() {
assert_eq!(
Numeric::<12, 2>::cell_type(),
CellType::Decimal {
precision: 12,
scale: 2
}
);
assert_eq!(
Numeric::<12, 2>::new(12_345).map(IntoValue::into_value),
Decimal::new(12_345, 2).map(RawValue::Decimal)
);
}
#[test]
fn numeric_rejects_values_over_its_precision() {
assert_eq!(
Numeric::<4, 2>::new(-9_999).map(|n| n.unscaled()),
Ok(-9_999)
);
assert_eq!(
Numeric::<4, 2>::new(10_000),
Err(DecimalError::TooManyDigits { precision: 4 })
);
assert_eq!(
Numeric::<38, 0>::new(i128::MIN),
Err(DecimalError::TooManyDigits { precision: 38 })
);
}
}