use crate::cast::*;
use crate::parse::{DecimalParseError, parse_decimal_checked};
pub trait DecimalCast: Sized {
fn to_i32(self) -> Option<i32>;
fn to_i64(self) -> Option<i64>;
fn to_i128(self) -> Option<i128>;
fn to_i256(self) -> Option<i256>;
fn from_decimal<T: DecimalCast>(n: T) -> Option<Self>;
fn from_f64(n: f64) -> Option<Self>;
}
impl DecimalCast for i32 {
fn to_i32(self) -> Option<i32> {
Some(self)
}
fn to_i64(self) -> Option<i64> {
Some(self as i64)
}
fn to_i128(self) -> Option<i128> {
Some(self as i128)
}
fn to_i256(self) -> Option<i256> {
Some(i256::from_i128(self as i128))
}
fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
n.to_i32()
}
fn from_f64(n: f64) -> Option<Self> {
n.to_i32()
}
}
impl DecimalCast for i64 {
fn to_i32(self) -> Option<i32> {
i32::try_from(self).ok()
}
fn to_i64(self) -> Option<i64> {
Some(self)
}
fn to_i128(self) -> Option<i128> {
Some(self as i128)
}
fn to_i256(self) -> Option<i256> {
Some(i256::from_i128(self as i128))
}
fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
n.to_i64()
}
fn from_f64(n: f64) -> Option<Self> {
num_traits::ToPrimitive::to_i64(&n)
}
}
impl DecimalCast for i128 {
fn to_i32(self) -> Option<i32> {
i32::try_from(self).ok()
}
fn to_i64(self) -> Option<i64> {
i64::try_from(self).ok()
}
fn to_i128(self) -> Option<i128> {
Some(self)
}
fn to_i256(self) -> Option<i256> {
Some(i256::from_i128(self))
}
fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
n.to_i128()
}
fn from_f64(n: f64) -> Option<Self> {
n.to_i128()
}
}
impl DecimalCast for i256 {
fn to_i32(self) -> Option<i32> {
self.to_i128().map(|x| i32::try_from(x).ok())?
}
fn to_i64(self) -> Option<i64> {
self.to_i128().map(|x| i64::try_from(x).ok())?
}
fn to_i128(self) -> Option<i128> {
self.to_i128()
}
fn to_i256(self) -> Option<i256> {
Some(self)
}
fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
n.to_i256()
}
fn from_f64(n: f64) -> Option<Self> {
i256::from_f64(n)
}
}
#[expect(clippy::type_complexity)]
fn make_upscaler<I: DecimalType, O: DecimalType>(
input_precision: u8,
input_scale: i8,
output_precision: u8,
output_scale: i8,
) -> Option<(
impl Fn(I::Native) -> Option<O::Native>,
Option<impl Fn(I::Native) -> O::Native>,
)>
where
I::Native: DecimalCast + ArrowNativeTypeOp,
O::Native: DecimalCast + ArrowNativeTypeOp,
{
let delta_scale = output_scale as i16 - input_scale as i16;
let max = O::MAX_FOR_EACH_PRECISION.get(delta_scale as usize)?;
let mul = max.add_wrapping(O::Native::ONE);
let f_fallible = move |x| O::Native::from_decimal(x)?.mul_checked(mul).ok();
let is_infallible_cast = (input_precision as i16) + delta_scale <= (output_precision as i16);
let f_infallible = is_infallible_cast
.then_some(move |x| O::Native::from_decimal(x).unwrap().mul_wrapping(mul));
Some((f_fallible, f_infallible))
}
#[expect(clippy::type_complexity)]
fn make_downscaler<I: DecimalType, O: DecimalType>(
input_precision: u8,
input_scale: i8,
output_precision: u8,
output_scale: i8,
) -> Option<(
impl Fn(I::Native) -> Option<O::Native>,
Option<impl Fn(I::Native) -> O::Native>,
)>
where
I::Native: DecimalCast + ArrowNativeTypeOp,
O::Native: DecimalCast + ArrowNativeTypeOp,
{
let delta_scale = input_scale as i16 - output_scale as i16;
let max = I::MAX_FOR_EACH_PRECISION.get(delta_scale as usize)?;
let div = max.add_wrapping(I::Native::ONE);
let half = div.div_wrapping(I::Native::ONE.add_wrapping(I::Native::ONE));
let half_neg = half.neg_wrapping();
let f_fallible = move |x: I::Native| {
let (d, r) = x.div_rem_wrapping(div);
let adjusted = match x >= I::Native::ZERO {
true if r >= half => d.add_wrapping(I::Native::ONE),
false if r <= half_neg => d.sub_wrapping(I::Native::ONE),
_ => d,
};
O::Native::from_decimal(adjusted)
};
let is_infallible_cast = (input_precision as i16) - delta_scale < (output_precision as i16);
let f_infallible = is_infallible_cast.then_some(move |x| f_fallible(x).unwrap());
Some((f_fallible, f_infallible))
}
fn apply_rescaler<I: DecimalType, O: DecimalType>(
value: I::Native,
output_precision: u8,
f: impl Fn(I::Native) -> Option<O::Native>,
f_infallible: Option<impl Fn(I::Native) -> O::Native>,
) -> Option<O::Native>
where
I::Native: DecimalCast,
O::Native: DecimalCast,
{
if let Some(f_infallible) = f_infallible {
Some(f_infallible(value))
} else {
f(value).filter(|v| O::is_valid_decimal_precision(*v, output_precision))
}
}
pub fn rescale_decimal<I: DecimalType, O: DecimalType>(
value: I::Native,
input_precision: u8,
input_scale: i8,
output_precision: u8,
output_scale: i8,
) -> Option<O::Native>
where
I::Native: DecimalCast + ArrowNativeTypeOp,
O::Native: DecimalCast + ArrowNativeTypeOp,
{
validate_decimal_precision_and_scale::<I>(input_precision, input_scale).ok()?;
validate_decimal_precision_and_scale::<O>(output_precision, output_scale).ok()?;
if input_scale <= output_scale {
let (f, f_infallible) =
make_upscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)?;
apply_rescaler::<I, O>(value, output_precision, f, f_infallible)
} else {
let Some((f, f_infallible)) =
make_downscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
else {
return Some(O::Native::ZERO);
};
apply_rescaler::<I, O>(value, output_precision, f, f_infallible)
}
}
fn cast_decimal_to_decimal_error<I, O>(
output_precision: u8,
output_scale: i8,
) -> impl Fn(<I as ArrowPrimitiveType>::Native) -> ArrowError
where
I: DecimalType,
O: DecimalType,
I::Native: DecimalCast + ArrowNativeTypeOp,
O::Native: DecimalCast + ArrowNativeTypeOp,
{
move |x: I::Native| {
ArrowError::CastError(format!(
"Cannot cast to {}({}, {}). Overflowing on {:?}",
O::PREFIX,
output_precision,
output_scale,
x
))
}
}
fn apply_decimal_cast<I: DecimalType, O: DecimalType>(
array: &PrimitiveArray<I>,
output_precision: u8,
output_scale: i8,
f_fallible: impl Fn(I::Native) -> Option<O::Native>,
f_infallible: Option<impl Fn(I::Native) -> O::Native>,
cast_options: &CastOptions,
) -> Result<PrimitiveArray<O>, ArrowError>
where
I::Native: DecimalCast + ArrowNativeTypeOp,
O::Native: DecimalCast + ArrowNativeTypeOp,
{
let array = if let Some(f_infallible) = f_infallible {
array.unary(f_infallible)
} else if cast_options.safe {
array.unary_opt(|x| {
f_fallible(x).filter(|v| O::is_valid_decimal_precision(*v, output_precision))
})
} else {
let error = cast_decimal_to_decimal_error::<I, O>(output_precision, output_scale);
array.try_unary(|x| {
let v = f_fallible(x).ok_or_else(|| error(x))?;
O::validate_decimal_precision(v, output_precision, output_scale).map(|()| v)
})?
};
Ok(array)
}
fn convert_to_smaller_scale_decimal<I, O>(
array: &PrimitiveArray<I>,
input_precision: u8,
input_scale: i8,
output_precision: u8,
output_scale: i8,
cast_options: &CastOptions,
) -> Result<PrimitiveArray<O>, ArrowError>
where
I: DecimalType,
O: DecimalType,
I::Native: DecimalCast + ArrowNativeTypeOp,
O::Native: DecimalCast + ArrowNativeTypeOp,
{
if let Some((f_fallible, f_infallible)) =
make_downscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
{
apply_decimal_cast(
array,
output_precision,
output_scale,
f_fallible,
f_infallible,
cast_options,
)
} else {
let zeros = vec![O::Native::ZERO; array.len()];
Ok(PrimitiveArray::new(zeros.into(), array.nulls().cloned()))
}
}
fn convert_to_bigger_or_equal_scale_decimal<I, O>(
array: &PrimitiveArray<I>,
input_precision: u8,
input_scale: i8,
output_precision: u8,
output_scale: i8,
cast_options: &CastOptions,
) -> Result<PrimitiveArray<O>, ArrowError>
where
I: DecimalType,
O: DecimalType,
I::Native: DecimalCast + ArrowNativeTypeOp,
O::Native: DecimalCast + ArrowNativeTypeOp,
{
if let Some((f, f_infallible)) =
make_upscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
{
apply_decimal_cast(
array,
output_precision,
output_scale,
f,
f_infallible,
cast_options,
)
} else {
Err(ArrowError::CastError(format!(
"Cannot cast to {}({}, {}). Value overflows for output scale",
O::PREFIX,
output_precision,
output_scale
)))
}
}
pub(crate) fn cast_decimal_to_decimal_same_type<T>(
array: &PrimitiveArray<T>,
input_precision: u8,
input_scale: i8,
output_precision: u8,
output_scale: i8,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError>
where
T: DecimalType,
T::Native: DecimalCast + ArrowNativeTypeOp,
{
let array: PrimitiveArray<T> =
if input_scale == output_scale && input_precision <= output_precision {
array.clone()
} else if input_scale <= output_scale {
convert_to_bigger_or_equal_scale_decimal::<T, T>(
array,
input_precision,
input_scale,
output_precision,
output_scale,
cast_options,
)?
} else {
convert_to_smaller_scale_decimal::<T, T>(
array,
input_precision,
input_scale,
output_precision,
output_scale,
cast_options,
)?
};
Ok(Arc::new(array.with_precision_and_scale(
output_precision,
output_scale,
)?))
}
pub(crate) fn cast_decimal_to_decimal<I, O>(
array: &PrimitiveArray<I>,
input_precision: u8,
input_scale: i8,
output_precision: u8,
output_scale: i8,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError>
where
I: DecimalType,
O: DecimalType,
I::Native: DecimalCast + ArrowNativeTypeOp,
O::Native: DecimalCast + ArrowNativeTypeOp,
{
let array: PrimitiveArray<O> = if input_scale > output_scale {
convert_to_smaller_scale_decimal::<I, O>(
array,
input_precision,
input_scale,
output_precision,
output_scale,
cast_options,
)?
} else {
convert_to_bigger_or_equal_scale_decimal::<I, O>(
array,
input_precision,
input_scale,
output_precision,
output_scale,
cast_options,
)?
};
Ok(Arc::new(array.with_precision_and_scale(
output_precision,
output_scale,
)?))
}
#[deprecated(
since = "60.0.0",
note = "Use `arrow_cast::parse::parse_decimal` instead"
)]
pub fn parse_string_to_decimal_native<T: DecimalType>(
value_str: &str,
scale: usize,
) -> Result<T::Native, ArrowError> {
let overflow = || {
ArrowError::InvalidArgumentError(format!(
"Cannot convert {value_str} to {}: Overflow",
T::PREFIX
))
};
let scale = i8::try_from(scale).map_err(|_| overflow())?;
parse_decimal_checked::<T>(value_str, T::MAX_PRECISION, scale).map_err(|e| match e {
DecimalParseError::InvalidFormat => {
ArrowError::InvalidArgumentError(format!("Invalid decimal format: {value_str:?}"))
}
DecimalParseError::Overflow => overflow(),
})
}
pub(crate) fn generic_string_to_decimal_cast<'a, T, S>(
from: &'a S,
precision: u8,
scale: i8,
cast_options: &CastOptions,
) -> Result<PrimitiveArray<T>, ArrowError>
where
T: DecimalType,
&'a S: StringArrayType<'a>,
{
if cast_options.safe {
let iter = from
.iter()
.map(|v| parse_decimal_checked::<T>(v?, precision, scale).ok());
Ok(unsafe {
PrimitiveArray::<T>::from_trusted_len_iter(iter)
.with_precision_and_scale(precision, scale)?
})
} else {
let mut builder = PrimitiveBuilder::<T>::with_capacity(from.len());
for v in from.iter() {
match v {
Some(v) => {
let v = parse_decimal_checked::<T>(v, precision, scale).map_err(|e| {
let reason = match e {
DecimalParseError::InvalidFormat => "invalid decimal format",
DecimalParseError::Overflow => "value does not fit",
};
ArrowError::CastError(format!(
"Cannot cast string '{v}' to value of {}({precision}, {scale}) type: {reason}",
T::PREFIX,
))
})?;
builder.append_value(v);
}
None => builder.append_null(),
}
}
builder.finish().with_precision_and_scale(precision, scale)
}
}
pub(crate) fn string_to_decimal_cast<T: DecimalType, Offset: OffsetSizeTrait>(
from: &GenericStringArray<Offset>,
precision: u8,
scale: i8,
cast_options: &CastOptions,
) -> Result<PrimitiveArray<T>, ArrowError> {
generic_string_to_decimal_cast::<T, GenericStringArray<Offset>>(
from,
precision,
scale,
cast_options,
)
}
pub(crate) fn string_view_to_decimal_cast<T: DecimalType>(
from: &StringViewArray,
precision: u8,
scale: i8,
cast_options: &CastOptions,
) -> Result<PrimitiveArray<T>, ArrowError> {
generic_string_to_decimal_cast::<T, StringViewArray>(from, precision, scale, cast_options)
}
pub(crate) fn cast_string_to_decimal<T: DecimalType, Offset: OffsetSizeTrait>(
from: &dyn Array,
precision: u8,
scale: i8,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError> {
validate_decimal_precision_and_scale::<T>(precision, scale)?;
let result = match from.data_type() {
DataType::Utf8View => string_view_to_decimal_cast::<T>(
from.as_any().downcast_ref::<StringViewArray>().unwrap(),
precision,
scale,
cast_options,
)?,
DataType::Utf8 | DataType::LargeUtf8 => string_to_decimal_cast::<T, Offset>(
from.as_any()
.downcast_ref::<GenericStringArray<Offset>>()
.unwrap(),
precision,
scale,
cast_options,
)?,
other => {
return Err(ArrowError::ComputeError(format!(
"Cannot cast {other:?} to decimal",
)));
}
};
Ok(Arc::new(result))
}
pub(crate) fn cast_floating_point_to_decimal<T: ArrowPrimitiveType, D>(
array: &PrimitiveArray<T>,
precision: u8,
scale: i8,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError>
where
<T as ArrowPrimitiveType>::Native: AsPrimitive<f64>,
D: DecimalType + ArrowPrimitiveType,
<D as ArrowPrimitiveType>::Native: DecimalCast,
{
validate_decimal_precision_and_scale::<D>(precision, scale)?;
let mul = 10_f64.powi(scale as i32);
if cast_options.safe {
array
.unary_opt::<_, D>(|v| float_to_decimal_checked::<D>(v.as_(), mul, precision).ok())
.with_precision_and_scale(precision, scale)
.map(|a| Arc::new(a) as ArrayRef)
} else {
array
.try_unary::<_, D, _>(|v| {
float_to_decimal_checked::<D>(v.as_(), mul, precision)
.map_err(|e| e.into_arrow_error(v, precision, scale))
})?
.with_precision_and_scale(precision, scale)
.map(|a| Arc::new(a) as ArrayRef)
}
}
#[inline]
pub fn float_to_decimal<D>(input: f64, precision: u8, scale: i8) -> Result<D::Native, ArrowError>
where
D: DecimalType,
D::Native: DecimalCast,
{
validate_decimal_precision_and_scale::<D>(precision, scale)?;
float_to_decimal_checked::<D>(input, 10_f64.powi(scale as i32), precision)
.map_err(|e| e.into_arrow_error(input, precision, scale))
}
pub fn float_to_decimal_opt<D>(input: f64, precision: u8, scale: i8) -> Option<D::Native>
where
D: DecimalType,
D::Native: DecimalCast,
{
validate_decimal_precision_and_scale::<D>(precision, scale).ok()?;
float_to_decimal_checked::<D>(input, 10_f64.powi(scale as i32), precision).ok()
}
enum FloatToDecimalError<D: DecimalType> {
NativeOverflow,
PrecisionOverflow(D::Native),
}
impl<D: DecimalType> FloatToDecimalError<D> {
fn into_arrow_error(self, input: impl std::fmt::Debug, precision: u8, scale: i8) -> ArrowError {
match self {
Self::NativeOverflow => ArrowError::CastError(format!(
"Cannot cast to {}({}, {}). Overflowing on {:?}",
D::PREFIX,
precision,
scale,
input
)),
Self::PrecisionOverflow(value) => {
D::validate_decimal_precision(value, precision, scale).unwrap_err()
}
}
}
}
#[inline]
fn float_to_decimal_checked<D>(
input: f64,
mul: f64,
precision: u8,
) -> Result<D::Native, FloatToDecimalError<D>>
where
D: DecimalType,
D::Native: DecimalCast,
{
let value =
D::Native::from_f64((mul * input).round()).ok_or(FloatToDecimalError::NativeOverflow)?;
if D::is_valid_decimal_precision(value, precision) {
Ok(value)
} else {
Err(FloatToDecimalError::PrecisionOverflow(value))
}
}
#[deprecated(since = "60.0.0", note = "Use `float_to_decimal` instead")]
#[inline(always)]
pub fn single_float_to_decimal<D>(input: f64, mul: f64) -> Option<D::Native>
where
D: DecimalType + ArrowPrimitiveType,
<D as ArrowPrimitiveType>::Native: DecimalCast,
{
D::Native::from_f64((mul * input).round())
}
pub(crate) fn cast_decimal_to_integer<D, T>(
array: &dyn Array,
scale: i8,
cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError>
where
T: ArrowPrimitiveType,
<T as ArrowPrimitiveType>::Native: NumCast,
D: DecimalType + ArrowPrimitiveType,
<D as ArrowPrimitiveType>::Native: ToPrimitive,
{
let array = array.as_primitive::<D>();
let div: D::Native = D::Native::usize_as(10)
.pow_checked(scale.unsigned_abs() as u32)
.map_err(|_| {
ArrowError::CastError(format!(
"Cannot cast to {:?}. The scale {} causes overflow.",
D::PREFIX,
scale,
))
})?;
let mut value_builder = PrimitiveBuilder::<T>::with_capacity(array.len());
if scale < 0 {
match cast_options.safe {
true => {
for i in 0..array.len() {
if array.is_null(i) {
value_builder.append_null();
} else {
let v = array
.value(i)
.mul_checked(div)
.ok()
.and_then(<T::Native as NumCast>::from::<D::Native>);
value_builder.append_option(v);
}
}
}
false => {
for i in 0..array.len() {
if array.is_null(i) {
value_builder.append_null();
} else {
let v = array.value(i).mul_checked(div)?;
let value =
<T::Native as NumCast>::from::<D::Native>(v).ok_or_else(|| {
ArrowError::CastError(format!(
"value of {:?} is out of range {}",
v,
T::DATA_TYPE
))
})?;
value_builder.append_value(value);
}
}
}
}
} else {
match cast_options.safe {
true => {
for i in 0..array.len() {
if array.is_null(i) {
value_builder.append_null();
} else {
let v = array.value(i).div_wrapping(div);
let v = <T::Native as NumCast>::from(v);
value_builder.append_option(v);
}
}
}
false => {
for i in 0..array.len() {
if array.is_null(i) {
value_builder.append_null();
} else {
let v = array.value(i).div_wrapping(div);
let value =
<T::Native as NumCast>::from::<D::Native>(v).ok_or_else(|| {
ArrowError::CastError(format!(
"value of {:?} is out of range {}",
v,
T::DATA_TYPE
))
})?;
value_builder.append_value(value);
}
}
}
}
}
Ok(Arc::new(value_builder.finish()))
}
pub(crate) fn cast_decimal_to_float<D: DecimalType, T: ArrowPrimitiveType, F>(
array: &dyn Array,
op: F,
) -> Result<ArrayRef, ArrowError>
where
F: Fn(D::Native) -> T::Native,
{
let array = array.as_primitive::<D>();
let array = array.unary::<_, T>(op);
Ok(Arc::new(array))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_float_to_decimal() {
fn check<D: DecimalType>()
where
D::Native: DecimalCast,
{
for (input, scale, expected) in [
(12.345, 2, 1235),
(-12.345, 2, -1235),
(999.99, 2, 99999),
(-999.99, 2, -99999),
(99999.25, 0, 99999),
(999992.5, -1, 99999),
(0.0, 2, 0),
] {
assert_eq!(
float_to_decimal_opt::<D>(input, 5, scale),
float_to_decimal::<D>(input, 5, scale).ok(),
);
assert_eq!(
float_to_decimal::<D>(input, 5, scale)
.unwrap()
.to_i128()
.unwrap(),
expected,
);
}
for (input, scale) in [
(12345.678, 2),
(-12345.678, 2),
(999.9975, 2),
(-999.9975, 2),
(99999.75, 0),
(-99999.75, 0),
(999997.5, -1),
(-999997.5, -1),
(f64::NAN, 0),
(f64::INFINITY, 0),
(f64::NEG_INFINITY, 0),
(f64::MAX, 0),
] {
assert!(float_to_decimal::<D>(input, 5, scale).is_err());
assert!(float_to_decimal_opt::<D>(input, 5, scale).is_none());
}
for (precision, scale) in [(0, 0), (D::MAX_PRECISION + 1, 0), (5, 6)] {
assert!(float_to_decimal::<D>(0.0, precision, scale).is_err());
assert!(float_to_decimal_opt::<D>(0.0, precision, scale).is_none());
}
}
check::<Decimal32Type>();
check::<Decimal64Type>();
check::<Decimal128Type>();
check::<Decimal256Type>();
}
#[test]
#[expect(deprecated)]
fn test_single_float_to_decimal_preserves_native_range() {
for (input, expected) in [
(1_000_000_000.0, 1_000_000_000),
(-1_000_000_000.0, -1_000_000_000),
(999_999_999.75, 1_000_000_000),
(-999_999_999.75, -1_000_000_000),
] {
assert_eq!(
single_float_to_decimal::<Decimal32Type>(input, 1.0),
Some(expected)
);
assert!(float_to_decimal::<Decimal32Type>(input, 9, 0).is_err());
}
assert_eq!(
single_float_to_decimal::<Decimal32Type>(999_999_999.0, 1.0),
Some(999_999_999),
);
assert_eq!(
single_float_to_decimal::<Decimal32Type>(12.345, 100.0),
Some(1235)
);
assert_eq!(
single_float_to_decimal::<Decimal32Type>(f64::MAX, 1.0),
None
);
}
#[test]
#[expect(deprecated)]
fn test_parse_string_to_decimal_native() {
assert_eq!(
parse_string_to_decimal_native::<Decimal128Type>("123.456", 2).unwrap(),
12346
);
assert_eq!(
parse_string_to_decimal_native::<Decimal128Type>(&"9".repeat(38), 0).unwrap(),
10_i128.pow(38) - 1
);
assert!(
parse_string_to_decimal_native::<Decimal128Type>(&i128::MAX.to_string(), 0).is_err()
);
assert_eq!(
parse_string_to_decimal_native::<Decimal128Type>("abc", 2)
.unwrap_err()
.to_string(),
"Invalid argument error: Invalid decimal format: \"abc\""
);
assert_eq!(
parse_string_to_decimal_native::<Decimal32Type>("1", 10)
.unwrap_err()
.to_string(),
"Invalid argument error: Cannot convert 1 to Decimal32: Overflow"
);
}
#[test]
fn test_rescale_decimal_upscale_within_precision() {
let result = rescale_decimal::<Decimal128Type, Decimal128Type>(
12_345_i128, 5,
2,
8,
5,
);
assert_eq!(result, Some(12_345_000_i128));
}
#[test]
fn test_rescale_decimal_downscale_rounds_half_away_from_zero() {
let positive = rescale_decimal::<Decimal128Type, Decimal128Type>(
1_050_i128, 5, 3, 5, 1,
);
assert_eq!(positive, Some(11_i128));
let negative = rescale_decimal::<Decimal128Type, Decimal128Type>(
-1_050_i128, 5,
3,
5,
1,
);
assert_eq!(negative, Some(-11_i128)); }
#[test]
fn test_rescale_decimal_downscale_large_delta_returns_zero() {
let result = rescale_decimal::<Decimal32Type, Decimal32Type>(12_345_i32, 9, 9, 9, 4);
assert_eq!(result, Some(0_i32));
}
#[test]
fn test_rescale_decimal_upscale_overflow_returns_none() {
let result = rescale_decimal::<Decimal32Type, Decimal32Type>(9_999_i32, 4, 0, 5, 2);
assert_eq!(result, None);
}
#[test]
fn test_rescale_decimal256_extreme_scales() {
assert_eq!(
rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ONE, 76, -76, 76, 0),
None
);
assert_eq!(
rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ZERO, 76, -76, 76, 0),
Some(i256::ZERO)
);
assert_eq!(
rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ONE, 76, i8::MIN, 76, 76),
None
);
assert_eq!(
rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ONE, 76, 76, 76, i8::MIN),
Some(i256::ZERO)
);
}
#[test]
fn test_rescale_decimal_invalid_input_precision_scale_returns_none() {
let result = rescale_decimal::<Decimal128Type, Decimal128Type>(123_i128, 39, 39, 38, 38);
assert_eq!(result, None);
}
#[test]
fn test_rescale_decimal_invalid_output_precision_scale_returns_none() {
let result = rescale_decimal::<Decimal128Type, Decimal128Type>(123_i128, 38, 38, 39, 39);
assert_eq!(result, None);
}
}