Skip to main content

parquet_variant/variant/
decimal.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17use arrow_schema::ArrowError;
18use std::fmt;
19
20/// Trait for variant decimal types, enabling generic code across Decimal4/8/16
21///
22/// This trait provides a common interface for the three variant decimal types,
23/// allowing generic functions and data structures to work with any decimal width.
24/// It is modeled after Arrow's `DecimalType` trait but adapted for variant semantics.
25///
26/// # Example
27///
28/// ```
29/// # use parquet_variant::{VariantDecimal4, VariantDecimal8, VariantDecimalType};
30/// #
31/// fn extract_scale<D: VariantDecimalType>(decimal: D) -> u8 {
32///     decimal.scale()
33/// }
34///
35/// let dec4 = VariantDecimal4::try_new(12345, 2).unwrap();
36/// let dec8 = VariantDecimal8::try_new(67890, 3).unwrap();
37///
38/// assert_eq!(extract_scale(dec4), 2);
39/// assert_eq!(extract_scale(dec8), 3);
40/// ```
41pub trait VariantDecimalType: Into<super::Variant<'static, 'static>> {
42    /// The underlying signed integer type (i32, i64, or i128)
43    type Native;
44
45    /// Maximum number of significant digits this decimal type can represent (9, 18, or 38)
46    const MAX_PRECISION: u8;
47    /// The largest positive unscaled value that fits in [`Self::MAX_PRECISION`] digits.
48    const MAX_UNSCALED_VALUE: Self::Native;
49
50    /// True if the given precision and scale are valid for this variant decimal type.
51    ///
52    /// NOTE: By a strict reading of the "decimal table" in the [variant spec], one might conclude that
53    /// each decimal type has both lower and upper bounds on precision (i.e. Decimal16 with precision 5
54    /// is invalid because Decimal4 "covers" it). But the variant shredding integration tests
55    /// specifically expect such cases to succeed, so we only enforce the upper bound here.
56    ///
57    /// [shredding spec]: https://github.com/apache/parquet-format/blob/master/VariantEncoding.md#encoding-types
58    ///
59    /// # Example
60    /// ```
61    /// # use parquet_variant::{VariantDecimal4, VariantDecimalType};
62    /// #
63    /// assert!(VariantDecimal4::is_valid_precision_and_scale(&5, &2));
64    /// assert!(!VariantDecimal4::is_valid_precision_and_scale(&10, &2)); // too wide
65    /// assert!(!VariantDecimal4::is_valid_precision_and_scale(&5, &-1)); // negative scale
66    /// assert!(!VariantDecimal4::is_valid_precision_and_scale(&5, &7)); // scale too big
67    /// ```
68    fn is_valid_precision_and_scale(precision: &u8, scale: &i8) -> bool {
69        (1..=Self::MAX_PRECISION).contains(precision) && (0..=*precision as i8).contains(scale)
70    }
71
72    /// Creates a new decimal value from the given unscaled integer and scale, failing if the
73    /// integer's width, or the requested scale, exceeds `MAX_PRECISION`.
74    ///
75    /// NOTE: For compatibility with arrow decimal types, negative scale is allowed as long
76    /// as the rescaled value fits in the available precision.
77    ///
78    /// # Example
79    ///
80    /// ```
81    /// # use parquet_variant::{VariantDecimal4, VariantDecimalType};
82    /// #
83    /// // Valid: 123.45 (5 digits, scale 2)
84    /// let d = VariantDecimal4::try_new(12345, 2).unwrap();
85    /// assert_eq!(d.integer(), 12345);
86    /// assert_eq!(d.scale(), 2);
87    ///
88    /// VariantDecimal4::try_new(123, 10).expect_err("scale exceeds MAX_PRECISION");
89    /// VariantDecimal4::try_new(1234567890, 10).expect_err("value's width exceeds MAX_PRECISION");
90    /// ```
91    fn try_new(integer: Self::Native, scale: u8) -> Result<Self, ArrowError>;
92
93    /// Attempts to convert an unscaled arrow decimal value to the indicated variant decimal type.
94    ///
95    /// Unlike [`Self::try_new`], this function accepts a signed scale, and attempts to rescale
96    /// negative-scale values to their equivalent (larger) scale-0 values. For example, a decimal
97    /// value of 123 with scale -2 becomes 12300 with scale 0.
98    ///
99    /// Fails if rescaling fails, or for any of the reasons [`Self::try_new`] could fail.
100    fn try_new_with_signed_scale(integer: Self::Native, scale: i8) -> Result<Self, ArrowError>;
101
102    /// Returns the unscaled integer value
103    fn integer(&self) -> Self::Native;
104
105    /// Returns the scale (number of digits after the decimal point)
106    fn scale(&self) -> u8;
107
108    /// Converts the decimal as an integer if possible,
109    ///
110    /// Return `Some(integer value)` if scale is 0 or the unscaled integer is divisible by 10^scale.
111    /// None for other values.
112    fn as_integer(&self) -> Option<Self::Native>;
113}
114
115/// Implements the complete variant decimal type: methods, Display, and VariantDecimalType trait
116macro_rules! impl_variant_decimal {
117    ($struct_name:ident, $native:ty) => {
118        impl $struct_name {
119            /// Attempts to create a new instance of this decimal type, failing if the value is too
120            /// wide or the scale is too large.
121            pub fn try_new(integer: $native, scale: u8) -> Result<Self, ArrowError> {
122                let max_precision = Self::MAX_PRECISION;
123                if scale > max_precision {
124                    return Err(ArrowError::InvalidArgumentError(format!(
125                        "Scale {scale} is larger than max precision {max_precision}",
126                    )));
127                }
128                if !(-Self::MAX_UNSCALED_VALUE..=Self::MAX_UNSCALED_VALUE).contains(&integer) {
129                    return Err(ArrowError::InvalidArgumentError(format!(
130                        "{integer} is wider than max precision {max_precision}",
131                    )));
132                }
133
134                Ok(Self { integer, scale })
135            }
136
137            /// Returns the unscaled integer value of the decimal.
138            ///
139            /// For example, if the decimal is `123.45`, this will return `12345`.
140            pub fn integer(&self) -> $native {
141                self.integer
142            }
143
144            /// Returns the scale of the decimal (how many digits after the decimal point).
145            ///
146            /// For example, if the decimal is `123.45`, this will return `2`.
147            pub fn scale(&self) -> u8 {
148                self.scale
149            }
150
151            #[doc = concat!(
152                        "Returns Some(`",
153                        stringify!($native),
154                        "`) if scale is zero or integer of the decimal is divisible by 10^scale,\n",
155                        "None for other values.\n\n",
156                        "",
157                        "# Examples\n",
158                        "```rust\n",
159                        "use parquet_variant::", stringify!($struct_name), ";\n",
160                        "//Return the integer if scale is 0\n",
161                        "let d1 = ", stringify!($struct_name), "::try_new(123, 0).unwrap();\n",
162                        "assert_eq!(d1.as_integer(), Some(123));\n",
163                        "// or if the integer is divisible by 10^scale\n",
164                        "let d2 = ", stringify!($struct_name), "::try_new(100, 2).unwrap();\n",
165                        "assert_eq!(d2.as_integer(), Some(1));\n",
166                        "// or the integer is negative and divisible by 10^scale\n",
167                        "let d3 = ", stringify!($struct_name), "::try_new(-100, 2).unwrap();\n",
168                        "assert_eq!(d3.as_integer(), Some(-1));\n",
169                        "// or if the integer is 0\n",
170                        "let d4 = ", stringify!($struct_name), "::try_new(0, 4).unwrap();\n",
171                        "assert_eq!(d4.as_integer(), Some(0));\n",
172                        "// but not if the integer is not divisible by 10^scale\n",
173                        "let d5 = ", stringify!($struct_name), "::try_new(123, 2).unwrap();\n",
174                        "assert_eq!(d5.as_integer(), None);\n",
175                        "// or the integer is negative and not divisible by 10^scale\n",
176                        "let d6 = ", stringify!($struct_name), "::try_new(-123, 2).unwrap();\n",
177                        "assert_eq!(d6.as_integer(), None);\n",
178                        "```\n",
179                    )]
180            pub fn as_integer(&self) -> Option<$native> {
181                if self.scale == 0 {
182                    return Some(self.integer);
183                }
184                let divisor = <$native>::pow(10, self.scale as u32);
185                (self.integer % divisor == 0).then(|| self.integer / divisor)
186            }
187        }
188
189        impl VariantDecimalType for $struct_name {
190            type Native = $native;
191            const MAX_PRECISION: u8 = Self::MAX_PRECISION;
192            const MAX_UNSCALED_VALUE: $native = <$native>::pow(10, Self::MAX_PRECISION as u32) - 1;
193
194            fn try_new(integer: $native, scale: u8) -> Result<Self, ArrowError> {
195                Self::try_new(integer, scale)
196            }
197
198            fn try_new_with_signed_scale(integer: $native, scale: i8) -> Result<Self, ArrowError> {
199                let (integer, scale) = if scale < 0 {
200                    let multiplier = <$native>::checked_pow(10, -scale as u32);
201                    let Some(rescaled) = multiplier.and_then(|m| integer.checked_mul(m)) else {
202                        return Err(ArrowError::InvalidArgumentError(format!(
203                            "Overflow when rescaling {integer} with scale {scale}"
204                        )));
205                    };
206                    (rescaled, 0u8)
207                } else {
208                    (integer, scale as u8)
209                };
210                Self::try_new(integer, scale)
211            }
212
213            fn integer(&self) -> $native {
214                self.integer()
215            }
216
217            fn scale(&self) -> u8 {
218                self.scale()
219            }
220
221            fn as_integer(&self) -> Option<$native> {
222                self.as_integer()
223            }
224        }
225
226        impl fmt::Display for $struct_name {
227            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228                let integer = if self.scale == 0 {
229                    self.integer
230                } else {
231                    let divisor = <$native>::pow(10, self.scale as u32);
232                    let remainder = self.integer % divisor;
233                    if remainder != 0 {
234                        // Track the sign explicitly, in case the quotient is zero
235                        let sign = if self.integer < 0 { "-" } else { "" };
236                        // Format an unsigned remainder with leading zeros and strip trailing zeros
237                        let remainder =
238                            format!("{:0width$}", remainder.abs(), width = self.scale as usize);
239                        let remainder = remainder.trim_end_matches('0');
240                        let quotient = (self.integer / divisor).abs();
241                        return write!(f, "{sign}{quotient}.{remainder}");
242                    }
243                    self.integer / divisor
244                };
245                write!(f, "{integer}")
246            }
247        }
248    };
249}
250
251/// Represents a 4-byte decimal value in the Variant format.
252///
253/// This struct stores a decimal number using a 32-bit signed integer for the coefficient
254/// and an 8-bit unsigned integer for the scale (number of decimal places). Its precision is limited to 9 digits.
255///
256/// For valid precision and scale values, see the Variant specification:
257/// <https://github.com/apache/parquet-format/blob/87f2c8bf77eefb4c43d0ebaeea1778bd28ac3609/VariantEncoding.md?plain=1#L418-L420>
258///
259/// # Example: Create a VariantDecimal4
260/// ```
261/// # use parquet_variant::VariantDecimal4;
262/// // Create a value representing the decimal 123.4567
263/// let decimal = VariantDecimal4::try_new(1234567, 4).expect("Failed to create decimal");
264/// ```
265#[derive(Debug, Clone, Copy, PartialEq)]
266pub struct VariantDecimal4 {
267    integer: i32,
268    scale: u8,
269}
270
271impl VariantDecimal4 {
272    /// Maximum number of significant digits (9 for 4-byte decimals)
273    pub const MAX_PRECISION: u8 = arrow_schema::DECIMAL32_MAX_PRECISION;
274}
275
276impl_variant_decimal!(VariantDecimal4, i32);
277
278/// Represents an 8-byte decimal value in the Variant format.
279///
280/// This struct stores a decimal number using a 64-bit signed integer for the coefficient
281/// and an 8-bit unsigned integer for the scale (number of decimal places). Its precision is between 10 and 18 digits.
282///
283/// For valid precision and scale values, see the Variant specification:
284///
285/// <https://github.com/apache/parquet-format/blob/87f2c8bf77eefb4c43d0ebaeea1778bd28ac3609/VariantEncoding.md?plain=1#L418-L420>
286///
287/// # Example: Create a VariantDecimal8
288/// ```
289/// # use parquet_variant::VariantDecimal8;
290/// // Create a value representing the decimal 123456.78
291/// let decimal = VariantDecimal8::try_new(12345678, 2).expect("Failed to create decimal");
292/// ```
293#[derive(Debug, Clone, Copy, PartialEq)]
294pub struct VariantDecimal8 {
295    integer: i64,
296    scale: u8,
297}
298
299impl VariantDecimal8 {
300    /// Maximum number of significant digits (18 for 8-byte decimals)
301    pub const MAX_PRECISION: u8 = arrow_schema::DECIMAL64_MAX_PRECISION;
302}
303
304impl_variant_decimal!(VariantDecimal8, i64);
305
306/// Represents an 16-byte decimal value in the Variant format.
307///
308/// This struct stores a decimal number using a 128-bit signed integer for the coefficient
309/// and an 8-bit unsigned integer for the scale (number of decimal places). Its precision is between 19 and 38 digits.
310///
311/// For valid precision and scale values, see the Variant specification:
312///
313/// <https://github.com/apache/parquet-format/blob/87f2c8bf77eefb4c43d0ebaeea1778bd28ac3609/VariantEncoding.md?plain=1#L418-L420>
314///
315/// # Example: Create a VariantDecimal16
316/// ```
317/// # use parquet_variant::VariantDecimal16;
318/// // Create a value representing the decimal 12345678901234567.890
319/// let decimal = VariantDecimal16::try_new(12345678901234567890, 3).unwrap();
320/// ```
321#[derive(Debug, Clone, Copy, PartialEq)]
322pub struct VariantDecimal16 {
323    integer: i128,
324    scale: u8,
325}
326
327impl VariantDecimal16 {
328    /// Maximum number of significant digits (38 for 16-byte decimals)
329    pub const MAX_PRECISION: u8 = arrow_schema::DECIMAL128_MAX_PRECISION;
330}
331
332impl_variant_decimal!(VariantDecimal16, i128);
333
334// Infallible conversion from a narrower decimal type to a wider one
335macro_rules! impl_from_decimal_for_decimal {
336    ($from_ty:ty, $for_ty:ty) => {
337        impl From<$from_ty> for $for_ty {
338            fn from(decimal: $from_ty) -> Self {
339                Self {
340                    integer: decimal.integer.into(),
341                    scale: decimal.scale,
342                }
343            }
344        }
345    };
346}
347
348impl_from_decimal_for_decimal!(VariantDecimal4, VariantDecimal8);
349impl_from_decimal_for_decimal!(VariantDecimal4, VariantDecimal16);
350impl_from_decimal_for_decimal!(VariantDecimal8, VariantDecimal16);
351
352// Fallible conversion from a wider decimal type to a narrower one
353macro_rules! impl_try_from_decimal_for_decimal {
354    ($from_ty:ty, $for_ty:ty) => {
355        impl TryFrom<$from_ty> for $for_ty {
356            type Error = ArrowError;
357
358            fn try_from(decimal: $from_ty) -> Result<Self, ArrowError> {
359                let Ok(integer) = decimal.integer.try_into() else {
360                    return Err(ArrowError::InvalidArgumentError(format!(
361                        "Value {} is wider than max precision {}",
362                        decimal.integer,
363                        Self::MAX_PRECISION
364                    )));
365                };
366                Self::try_new(integer, decimal.scale)
367            }
368        }
369    };
370}
371
372impl_try_from_decimal_for_decimal!(VariantDecimal8, VariantDecimal4);
373impl_try_from_decimal_for_decimal!(VariantDecimal16, VariantDecimal4);
374impl_try_from_decimal_for_decimal!(VariantDecimal16, VariantDecimal8);
375
376// Fallible conversion from a decimal's underlying integer type
377macro_rules! impl_try_from_int_for_decimal {
378    ($from_ty:ty, $for_ty:ty) => {
379        impl TryFrom<$from_ty> for $for_ty {
380            type Error = ArrowError;
381
382            fn try_from(integer: $from_ty) -> Result<Self, ArrowError> {
383                Self::try_new(integer, 0)
384            }
385        }
386    };
387}
388
389impl_try_from_int_for_decimal!(i32, VariantDecimal4);
390impl_try_from_int_for_decimal!(i64, VariantDecimal8);
391impl_try_from_int_for_decimal!(i128, VariantDecimal16);
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396
397    #[test]
398    fn test_variant_decimal_invalid_precision() {
399        // Test precision validation for Decimal4
400        let decimal4_too_large = VariantDecimal4::try_new(1_000_000_000_i32, 2);
401        assert!(
402            decimal4_too_large.is_err(),
403            "Decimal4 precision overflow should fail"
404        );
405        assert!(
406            decimal4_too_large
407                .unwrap_err()
408                .to_string()
409                .contains("wider than max precision")
410        );
411
412        let decimal4_too_small = VariantDecimal4::try_new(-1_000_000_000_i32, 2);
413        assert!(
414            decimal4_too_small.is_err(),
415            "Decimal4 precision underflow should fail"
416        );
417        assert!(
418            decimal4_too_small
419                .unwrap_err()
420                .to_string()
421                .contains("wider than max precision")
422        );
423
424        // Test valid edge cases for Decimal4
425        let decimal4_max_valid = VariantDecimal4::try_new(999_999_999_i32, 2);
426        assert!(
427            decimal4_max_valid.is_ok(),
428            "Decimal4 max valid value should succeed"
429        );
430
431        let decimal4_min_valid = VariantDecimal4::try_new(-999_999_999_i32, 2);
432        assert!(
433            decimal4_min_valid.is_ok(),
434            "Decimal4 min valid value should succeed"
435        );
436
437        // Test precision validation for Decimal8
438        let decimal8_too_large = VariantDecimal8::try_new(1_000_000_000_000_000_000_i64, 2);
439        assert!(
440            decimal8_too_large.is_err(),
441            "Decimal8 precision overflow should fail"
442        );
443        assert!(
444            decimal8_too_large
445                .unwrap_err()
446                .to_string()
447                .contains("wider than max precision")
448        );
449
450        let decimal8_too_small = VariantDecimal8::try_new(-1_000_000_000_000_000_000_i64, 2);
451        assert!(
452            decimal8_too_small.is_err(),
453            "Decimal8 precision underflow should fail"
454        );
455        assert!(
456            decimal8_too_small
457                .unwrap_err()
458                .to_string()
459                .contains("wider than max precision")
460        );
461
462        // Test valid edge cases for Decimal8
463        let decimal8_max_valid = VariantDecimal8::try_new(999_999_999_999_999_999_i64, 2);
464        assert!(
465            decimal8_max_valid.is_ok(),
466            "Decimal8 max valid value should succeed"
467        );
468
469        let decimal8_min_valid = VariantDecimal8::try_new(-999_999_999_999_999_999_i64, 2);
470        assert!(
471            decimal8_min_valid.is_ok(),
472            "Decimal8 min valid value should succeed"
473        );
474
475        // Test precision validation for Decimal16
476        let decimal16_too_large =
477            VariantDecimal16::try_new(100000000000000000000000000000000000000_i128, 2);
478        assert!(
479            decimal16_too_large.is_err(),
480            "Decimal16 precision overflow should fail"
481        );
482        assert!(
483            decimal16_too_large
484                .unwrap_err()
485                .to_string()
486                .contains("wider than max precision")
487        );
488
489        let decimal16_too_small =
490            VariantDecimal16::try_new(-100000000000000000000000000000000000000_i128, 2);
491        assert!(
492            decimal16_too_small.is_err(),
493            "Decimal16 precision underflow should fail"
494        );
495        assert!(
496            decimal16_too_small
497                .unwrap_err()
498                .to_string()
499                .contains("wider than max precision")
500        );
501
502        // Test valid edge cases for Decimal16
503        let decimal16_max_valid =
504            VariantDecimal16::try_new(99999999999999999999999999999999999999_i128, 2);
505        assert!(
506            decimal16_max_valid.is_ok(),
507            "Decimal16 max valid value should succeed"
508        );
509
510        let decimal16_min_valid =
511            VariantDecimal16::try_new(-99999999999999999999999999999999999999_i128, 2);
512        assert!(
513            decimal16_min_valid.is_ok(),
514            "Decimal16 min valid value should succeed"
515        );
516    }
517
518    #[test]
519    fn test_variant_decimal_invalid_scale() {
520        // Test invalid scale for Decimal4 (scale > 9)
521        let decimal4_invalid_scale = VariantDecimal4::try_new(123_i32, 10);
522        assert!(
523            decimal4_invalid_scale.is_err(),
524            "Decimal4 with scale > 9 should fail"
525        );
526        assert!(
527            decimal4_invalid_scale
528                .unwrap_err()
529                .to_string()
530                .contains("larger than max precision")
531        );
532
533        let decimal4_invalid_scale_large = VariantDecimal4::try_new(123_i32, 20);
534        assert!(
535            decimal4_invalid_scale_large.is_err(),
536            "Decimal4 with scale > 9 should fail"
537        );
538
539        // Test valid scale edge case for Decimal4
540        let decimal4_valid_scale = VariantDecimal4::try_new(123_i32, 9);
541        assert!(
542            decimal4_valid_scale.is_ok(),
543            "Decimal4 with scale = 9 should succeed"
544        );
545
546        // Test invalid scale for Decimal8 (scale > 18)
547        let decimal8_invalid_scale = VariantDecimal8::try_new(123_i64, 19);
548        assert!(
549            decimal8_invalid_scale.is_err(),
550            "Decimal8 with scale > 18 should fail"
551        );
552        assert!(
553            decimal8_invalid_scale
554                .unwrap_err()
555                .to_string()
556                .contains("larger than max precision")
557        );
558
559        let decimal8_invalid_scale_large = VariantDecimal8::try_new(123_i64, 25);
560        assert!(
561            decimal8_invalid_scale_large.is_err(),
562            "Decimal8 with scale > 18 should fail"
563        );
564
565        // Test valid scale edge case for Decimal8
566        let decimal8_valid_scale = VariantDecimal8::try_new(123_i64, 18);
567        assert!(
568            decimal8_valid_scale.is_ok(),
569            "Decimal8 with scale = 18 should succeed"
570        );
571
572        // Test invalid scale for Decimal16 (scale > 38)
573        let decimal16_invalid_scale = VariantDecimal16::try_new(123_i128, 39);
574        assert!(
575            decimal16_invalid_scale.is_err(),
576            "Decimal16 with scale > 38 should fail"
577        );
578        assert!(
579            decimal16_invalid_scale
580                .unwrap_err()
581                .to_string()
582                .contains("larger than max precision")
583        );
584
585        let decimal16_invalid_scale_large = VariantDecimal16::try_new(123_i128, 50);
586        assert!(
587            decimal16_invalid_scale_large.is_err(),
588            "Decimal16 with scale > 38 should fail"
589        );
590
591        // Test valid scale edge case for Decimal16
592        let decimal16_valid_scale = VariantDecimal16::try_new(123_i128, 38);
593        assert!(
594            decimal16_valid_scale.is_ok(),
595            "Decimal16 with scale = 38 should succeed"
596        );
597    }
598
599    #[test]
600    fn test_variant_decimal4_display() {
601        // Test zero scale (integers)
602        let d = VariantDecimal4::try_new(42, 0).unwrap();
603        assert_eq!(d.to_string(), "42");
604
605        let d = VariantDecimal4::try_new(-42, 0).unwrap();
606        assert_eq!(d.to_string(), "-42");
607
608        // Test basic decimal formatting
609        let d = VariantDecimal4::try_new(12345, 2).unwrap();
610        assert_eq!(d.to_string(), "123.45");
611
612        let d = VariantDecimal4::try_new(-12345, 2).unwrap();
613        assert_eq!(d.to_string(), "-123.45");
614
615        // Test trailing zeros are trimmed
616        let d = VariantDecimal4::try_new(12300, 2).unwrap();
617        assert_eq!(d.to_string(), "123");
618
619        let d = VariantDecimal4::try_new(-12300, 2).unwrap();
620        assert_eq!(d.to_string(), "-123");
621
622        // Test leading zeros in decimal part
623        let d = VariantDecimal4::try_new(1005, 3).unwrap();
624        assert_eq!(d.to_string(), "1.005");
625
626        let d = VariantDecimal4::try_new(-1005, 3).unwrap();
627        assert_eq!(d.to_string(), "-1.005");
628
629        // Test number smaller than scale (leading zero before decimal)
630        let d = VariantDecimal4::try_new(123, 4).unwrap();
631        assert_eq!(d.to_string(), "0.0123");
632
633        let d = VariantDecimal4::try_new(-123, 4).unwrap();
634        assert_eq!(d.to_string(), "-0.0123");
635
636        // Test zero
637        let d = VariantDecimal4::try_new(0, 0).unwrap();
638        assert_eq!(d.to_string(), "0");
639
640        let d = VariantDecimal4::try_new(0, 3).unwrap();
641        assert_eq!(d.to_string(), "0");
642
643        // Test max scale
644        let d = VariantDecimal4::try_new(123456789, 9).unwrap();
645        assert_eq!(d.to_string(), "0.123456789");
646
647        let d = VariantDecimal4::try_new(-123456789, 9).unwrap();
648        assert_eq!(d.to_string(), "-0.123456789");
649
650        // Test max precision
651        let d = VariantDecimal4::try_new(999999999, 0).unwrap();
652        assert_eq!(d.to_string(), "999999999");
653
654        let d = VariantDecimal4::try_new(-999999999, 0).unwrap();
655        assert_eq!(d.to_string(), "-999999999");
656
657        // Test trailing zeros with mixed decimal places
658        let d = VariantDecimal4::try_new(120050, 4).unwrap();
659        assert_eq!(d.to_string(), "12.005");
660
661        let d = VariantDecimal4::try_new(-120050, 4).unwrap();
662        assert_eq!(d.to_string(), "-12.005");
663    }
664
665    #[test]
666    fn test_variant_decimal8_display() {
667        // Test zero scale (integers)
668        let d = VariantDecimal8::try_new(42, 0).unwrap();
669        assert_eq!(d.to_string(), "42");
670
671        let d = VariantDecimal8::try_new(-42, 0).unwrap();
672        assert_eq!(d.to_string(), "-42");
673
674        // Test basic decimal formatting
675        let d = VariantDecimal8::try_new(1234567890, 3).unwrap();
676        assert_eq!(d.to_string(), "1234567.89");
677
678        let d = VariantDecimal8::try_new(-1234567890, 3).unwrap();
679        assert_eq!(d.to_string(), "-1234567.89");
680
681        // Test trailing zeros are trimmed
682        let d = VariantDecimal8::try_new(123000000, 6).unwrap();
683        assert_eq!(d.to_string(), "123");
684
685        let d = VariantDecimal8::try_new(-123000000, 6).unwrap();
686        assert_eq!(d.to_string(), "-123");
687
688        // Test leading zeros in decimal part
689        let d = VariantDecimal8::try_new(100005, 6).unwrap();
690        assert_eq!(d.to_string(), "0.100005");
691
692        let d = VariantDecimal8::try_new(-100005, 6).unwrap();
693        assert_eq!(d.to_string(), "-0.100005");
694
695        // Test number smaller than scale
696        let d = VariantDecimal8::try_new(123, 10).unwrap();
697        assert_eq!(d.to_string(), "0.0000000123");
698
699        let d = VariantDecimal8::try_new(-123, 10).unwrap();
700        assert_eq!(d.to_string(), "-0.0000000123");
701
702        // Test zero
703        let d = VariantDecimal8::try_new(0, 0).unwrap();
704        assert_eq!(d.to_string(), "0");
705
706        let d = VariantDecimal8::try_new(0, 10).unwrap();
707        assert_eq!(d.to_string(), "0");
708
709        // Test max scale
710        let d = VariantDecimal8::try_new(123456789012345678, 18).unwrap();
711        assert_eq!(d.to_string(), "0.123456789012345678");
712
713        let d = VariantDecimal8::try_new(-123456789012345678, 18).unwrap();
714        assert_eq!(d.to_string(), "-0.123456789012345678");
715
716        // Test max precision
717        let d = VariantDecimal8::try_new(999999999999999999, 0).unwrap();
718        assert_eq!(d.to_string(), "999999999999999999");
719
720        let d = VariantDecimal8::try_new(-999999999999999999, 0).unwrap();
721        assert_eq!(d.to_string(), "-999999999999999999");
722
723        // Test complex trailing zeros
724        let d = VariantDecimal8::try_new(1200000050000, 10).unwrap();
725        assert_eq!(d.to_string(), "120.000005");
726
727        let d = VariantDecimal8::try_new(-1200000050000, 10).unwrap();
728        assert_eq!(d.to_string(), "-120.000005");
729    }
730
731    #[test]
732    fn test_variant_decimal16_display() {
733        // Test zero scale (integers)
734        let d = VariantDecimal16::try_new(42, 0).unwrap();
735        assert_eq!(d.to_string(), "42");
736
737        let d = VariantDecimal16::try_new(-42, 0).unwrap();
738        assert_eq!(d.to_string(), "-42");
739
740        // Test basic decimal formatting
741        let d = VariantDecimal16::try_new(123456789012345, 4).unwrap();
742        assert_eq!(d.to_string(), "12345678901.2345");
743
744        let d = VariantDecimal16::try_new(-123456789012345, 4).unwrap();
745        assert_eq!(d.to_string(), "-12345678901.2345");
746
747        // Test trailing zeros are trimmed
748        let d = VariantDecimal16::try_new(12300000000, 8).unwrap();
749        assert_eq!(d.to_string(), "123");
750
751        let d = VariantDecimal16::try_new(-12300000000, 8).unwrap();
752        assert_eq!(d.to_string(), "-123");
753
754        // Test leading zeros in decimal part
755        let d = VariantDecimal16::try_new(10000005, 8).unwrap();
756        assert_eq!(d.to_string(), "0.10000005");
757
758        let d = VariantDecimal16::try_new(-10000005, 8).unwrap();
759        assert_eq!(d.to_string(), "-0.10000005");
760
761        // Test number smaller than scale
762        let d = VariantDecimal16::try_new(123, 20).unwrap();
763        assert_eq!(d.to_string(), "0.00000000000000000123");
764
765        let d = VariantDecimal16::try_new(-123, 20).unwrap();
766        assert_eq!(d.to_string(), "-0.00000000000000000123");
767
768        // Test zero
769        let d = VariantDecimal16::try_new(0, 0).unwrap();
770        assert_eq!(d.to_string(), "0");
771
772        let d = VariantDecimal16::try_new(0, 20).unwrap();
773        assert_eq!(d.to_string(), "0");
774
775        // Test max scale
776        let d = VariantDecimal16::try_new(12345678901234567890123456789012345678_i128, 38).unwrap();
777        assert_eq!(d.to_string(), "0.12345678901234567890123456789012345678");
778
779        let d =
780            VariantDecimal16::try_new(-12345678901234567890123456789012345678_i128, 38).unwrap();
781        assert_eq!(d.to_string(), "-0.12345678901234567890123456789012345678");
782
783        // Test max precision integer
784        let d = VariantDecimal16::try_new(99999999999999999999999999999999999999_i128, 0).unwrap();
785        assert_eq!(d.to_string(), "99999999999999999999999999999999999999");
786
787        let d = VariantDecimal16::try_new(-99999999999999999999999999999999999999_i128, 0).unwrap();
788        assert_eq!(d.to_string(), "-99999999999999999999999999999999999999");
789
790        // Test complex trailing zeros
791        let d = VariantDecimal16::try_new(12000000000000050000000000000_i128, 25).unwrap();
792        assert_eq!(d.to_string(), "1200.000000000005");
793
794        let d = VariantDecimal16::try_new(-12000000000000050000000000000_i128, 25).unwrap();
795        assert_eq!(d.to_string(), "-1200.000000000005");
796
797        // Test large integer that would overflow i64 but fits in i128
798        let large_int = 12345678901234567890123456789_i128;
799        let d = VariantDecimal16::try_new(large_int, 0).unwrap();
800        assert_eq!(d.to_string(), "12345678901234567890123456789");
801
802        let d = VariantDecimal16::try_new(-large_int, 0).unwrap();
803        assert_eq!(d.to_string(), "-12345678901234567890123456789");
804    }
805}