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
109/// Implements the complete variant decimal type: methods, Display, and VariantDecimalType trait
110macro_rules! impl_variant_decimal {
111    ($struct_name:ident, $native:ty) => {
112        impl $struct_name {
113            /// Attempts to create a new instance of this decimal type, failing if the value is too
114            /// wide or the scale is too large.
115            pub fn try_new(integer: $native, scale: u8) -> Result<Self, ArrowError> {
116                let max_precision = Self::MAX_PRECISION;
117                if scale > max_precision {
118                    return Err(ArrowError::InvalidArgumentError(format!(
119                        "Scale {scale} is larger than max precision {max_precision}",
120                    )));
121                }
122                if !(-Self::MAX_UNSCALED_VALUE..=Self::MAX_UNSCALED_VALUE).contains(&integer) {
123                    return Err(ArrowError::InvalidArgumentError(format!(
124                        "{integer} is wider than max precision {max_precision}",
125                    )));
126                }
127
128                Ok(Self { integer, scale })
129            }
130
131            /// Returns the unscaled integer value of the decimal.
132            ///
133            /// For example, if the decimal is `123.45`, this will return `12345`.
134            pub fn integer(&self) -> $native {
135                self.integer
136            }
137
138            /// Returns the scale of the decimal (how many digits after the decimal point).
139            ///
140            /// For example, if the decimal is `123.45`, this will return `2`.
141            pub fn scale(&self) -> u8 {
142                self.scale
143            }
144        }
145
146        impl VariantDecimalType for $struct_name {
147            type Native = $native;
148            const MAX_PRECISION: u8 = Self::MAX_PRECISION;
149            const MAX_UNSCALED_VALUE: $native = <$native>::pow(10, Self::MAX_PRECISION as u32) - 1;
150
151            fn try_new(integer: $native, scale: u8) -> Result<Self, ArrowError> {
152                Self::try_new(integer, scale)
153            }
154
155            fn try_new_with_signed_scale(integer: $native, scale: i8) -> Result<Self, ArrowError> {
156                let (integer, scale) = if scale < 0 {
157                    let multiplier = <$native>::checked_pow(10, -scale as u32);
158                    let Some(rescaled) = multiplier.and_then(|m| integer.checked_mul(m)) else {
159                        return Err(ArrowError::InvalidArgumentError(format!(
160                            "Overflow when rescaling {integer} with scale {scale}"
161                        )));
162                    };
163                    (rescaled, 0u8)
164                } else {
165                    (integer, scale as u8)
166                };
167                Self::try_new(integer, scale)
168            }
169
170            fn integer(&self) -> $native {
171                self.integer()
172            }
173
174            fn scale(&self) -> u8 {
175                self.scale()
176            }
177        }
178
179        impl fmt::Display for $struct_name {
180            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181                let integer = if self.scale == 0 {
182                    self.integer
183                } else {
184                    let divisor = <$native>::pow(10, self.scale as u32);
185                    let remainder = self.integer % divisor;
186                    if remainder != 0 {
187                        // Track the sign explicitly, in case the quotient is zero
188                        let sign = if self.integer < 0 { "-" } else { "" };
189                        // Format an unsigned remainder with leading zeros and strip trailing zeros
190                        let remainder =
191                            format!("{:0width$}", remainder.abs(), width = self.scale as usize);
192                        let remainder = remainder.trim_end_matches('0');
193                        let quotient = (self.integer / divisor).abs();
194                        return write!(f, "{sign}{quotient}.{remainder}");
195                    }
196                    self.integer / divisor
197                };
198                write!(f, "{integer}")
199            }
200        }
201    };
202}
203
204/// Represents a 4-byte decimal value in the Variant format.
205///
206/// This struct stores a decimal number using a 32-bit signed integer for the coefficient
207/// and an 8-bit unsigned integer for the scale (number of decimal places). Its precision is limited to 9 digits.
208///
209/// For valid precision and scale values, see the Variant specification:
210/// <https://github.com/apache/parquet-format/blob/87f2c8bf77eefb4c43d0ebaeea1778bd28ac3609/VariantEncoding.md?plain=1#L418-L420>
211///
212/// # Example: Create a VariantDecimal4
213/// ```
214/// # use parquet_variant::VariantDecimal4;
215/// // Create a value representing the decimal 123.4567
216/// let decimal = VariantDecimal4::try_new(1234567, 4).expect("Failed to create decimal");
217/// ```
218#[derive(Debug, Clone, Copy, PartialEq)]
219pub struct VariantDecimal4 {
220    integer: i32,
221    scale: u8,
222}
223
224impl VariantDecimal4 {
225    /// Maximum number of significant digits (9 for 4-byte decimals)
226    pub const MAX_PRECISION: u8 = arrow_schema::DECIMAL32_MAX_PRECISION;
227}
228
229impl_variant_decimal!(VariantDecimal4, i32);
230
231/// Represents an 8-byte decimal value in the Variant format.
232///
233/// This struct stores a decimal number using a 64-bit signed integer for the coefficient
234/// and an 8-bit unsigned integer for the scale (number of decimal places). Its precision is between 10 and 18 digits.
235///
236/// For valid precision and scale values, see the Variant specification:
237///
238/// <https://github.com/apache/parquet-format/blob/87f2c8bf77eefb4c43d0ebaeea1778bd28ac3609/VariantEncoding.md?plain=1#L418-L420>
239///
240/// # Example: Create a VariantDecimal8
241/// ```
242/// # use parquet_variant::VariantDecimal8;
243/// // Create a value representing the decimal 123456.78
244/// let decimal = VariantDecimal8::try_new(12345678, 2).expect("Failed to create decimal");
245/// ```
246#[derive(Debug, Clone, Copy, PartialEq)]
247pub struct VariantDecimal8 {
248    integer: i64,
249    scale: u8,
250}
251
252impl VariantDecimal8 {
253    /// Maximum number of significant digits (18 for 8-byte decimals)
254    pub const MAX_PRECISION: u8 = arrow_schema::DECIMAL64_MAX_PRECISION;
255}
256
257impl_variant_decimal!(VariantDecimal8, i64);
258
259/// Represents an 16-byte decimal value in the Variant format.
260///
261/// This struct stores a decimal number using a 128-bit signed integer for the coefficient
262/// and an 8-bit unsigned integer for the scale (number of decimal places). Its precision is between 19 and 38 digits.
263///
264/// For valid precision and scale values, see the Variant specification:
265///
266/// <https://github.com/apache/parquet-format/blob/87f2c8bf77eefb4c43d0ebaeea1778bd28ac3609/VariantEncoding.md?plain=1#L418-L420>
267///
268/// # Example: Create a VariantDecimal16
269/// ```
270/// # use parquet_variant::VariantDecimal16;
271/// // Create a value representing the decimal 12345678901234567.890
272/// let decimal = VariantDecimal16::try_new(12345678901234567890, 3).unwrap();
273/// ```
274#[derive(Debug, Clone, Copy, PartialEq)]
275pub struct VariantDecimal16 {
276    integer: i128,
277    scale: u8,
278}
279
280impl VariantDecimal16 {
281    /// Maximum number of significant digits (38 for 16-byte decimals)
282    pub const MAX_PRECISION: u8 = arrow_schema::DECIMAL128_MAX_PRECISION;
283}
284
285impl_variant_decimal!(VariantDecimal16, i128);
286
287// Infallible conversion from a narrower decimal type to a wider one
288macro_rules! impl_from_decimal_for_decimal {
289    ($from_ty:ty, $for_ty:ty) => {
290        impl From<$from_ty> for $for_ty {
291            fn from(decimal: $from_ty) -> Self {
292                Self {
293                    integer: decimal.integer.into(),
294                    scale: decimal.scale,
295                }
296            }
297        }
298    };
299}
300
301impl_from_decimal_for_decimal!(VariantDecimal4, VariantDecimal8);
302impl_from_decimal_for_decimal!(VariantDecimal4, VariantDecimal16);
303impl_from_decimal_for_decimal!(VariantDecimal8, VariantDecimal16);
304
305// Fallible conversion from a wider decimal type to a narrower one
306macro_rules! impl_try_from_decimal_for_decimal {
307    ($from_ty:ty, $for_ty:ty) => {
308        impl TryFrom<$from_ty> for $for_ty {
309            type Error = ArrowError;
310
311            fn try_from(decimal: $from_ty) -> Result<Self, ArrowError> {
312                let Ok(integer) = decimal.integer.try_into() else {
313                    return Err(ArrowError::InvalidArgumentError(format!(
314                        "Value {} is wider than max precision {}",
315                        decimal.integer,
316                        Self::MAX_PRECISION
317                    )));
318                };
319                Self::try_new(integer, decimal.scale)
320            }
321        }
322    };
323}
324
325impl_try_from_decimal_for_decimal!(VariantDecimal8, VariantDecimal4);
326impl_try_from_decimal_for_decimal!(VariantDecimal16, VariantDecimal4);
327impl_try_from_decimal_for_decimal!(VariantDecimal16, VariantDecimal8);
328
329// Fallible conversion from a decimal's underlying integer type
330macro_rules! impl_try_from_int_for_decimal {
331    ($from_ty:ty, $for_ty:ty) => {
332        impl TryFrom<$from_ty> for $for_ty {
333            type Error = ArrowError;
334
335            fn try_from(integer: $from_ty) -> Result<Self, ArrowError> {
336                Self::try_new(integer, 0)
337            }
338        }
339    };
340}
341
342impl_try_from_int_for_decimal!(i32, VariantDecimal4);
343impl_try_from_int_for_decimal!(i64, VariantDecimal8);
344impl_try_from_int_for_decimal!(i128, VariantDecimal16);
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn test_variant_decimal_invalid_precision() {
352        // Test precision validation for Decimal4
353        let decimal4_too_large = VariantDecimal4::try_new(1_000_000_000_i32, 2);
354        assert!(
355            decimal4_too_large.is_err(),
356            "Decimal4 precision overflow should fail"
357        );
358        assert!(
359            decimal4_too_large
360                .unwrap_err()
361                .to_string()
362                .contains("wider than max precision")
363        );
364
365        let decimal4_too_small = VariantDecimal4::try_new(-1_000_000_000_i32, 2);
366        assert!(
367            decimal4_too_small.is_err(),
368            "Decimal4 precision underflow should fail"
369        );
370        assert!(
371            decimal4_too_small
372                .unwrap_err()
373                .to_string()
374                .contains("wider than max precision")
375        );
376
377        // Test valid edge cases for Decimal4
378        let decimal4_max_valid = VariantDecimal4::try_new(999_999_999_i32, 2);
379        assert!(
380            decimal4_max_valid.is_ok(),
381            "Decimal4 max valid value should succeed"
382        );
383
384        let decimal4_min_valid = VariantDecimal4::try_new(-999_999_999_i32, 2);
385        assert!(
386            decimal4_min_valid.is_ok(),
387            "Decimal4 min valid value should succeed"
388        );
389
390        // Test precision validation for Decimal8
391        let decimal8_too_large = VariantDecimal8::try_new(1_000_000_000_000_000_000_i64, 2);
392        assert!(
393            decimal8_too_large.is_err(),
394            "Decimal8 precision overflow should fail"
395        );
396        assert!(
397            decimal8_too_large
398                .unwrap_err()
399                .to_string()
400                .contains("wider than max precision")
401        );
402
403        let decimal8_too_small = VariantDecimal8::try_new(-1_000_000_000_000_000_000_i64, 2);
404        assert!(
405            decimal8_too_small.is_err(),
406            "Decimal8 precision underflow should fail"
407        );
408        assert!(
409            decimal8_too_small
410                .unwrap_err()
411                .to_string()
412                .contains("wider than max precision")
413        );
414
415        // Test valid edge cases for Decimal8
416        let decimal8_max_valid = VariantDecimal8::try_new(999_999_999_999_999_999_i64, 2);
417        assert!(
418            decimal8_max_valid.is_ok(),
419            "Decimal8 max valid value should succeed"
420        );
421
422        let decimal8_min_valid = VariantDecimal8::try_new(-999_999_999_999_999_999_i64, 2);
423        assert!(
424            decimal8_min_valid.is_ok(),
425            "Decimal8 min valid value should succeed"
426        );
427
428        // Test precision validation for Decimal16
429        let decimal16_too_large =
430            VariantDecimal16::try_new(100000000000000000000000000000000000000_i128, 2);
431        assert!(
432            decimal16_too_large.is_err(),
433            "Decimal16 precision overflow should fail"
434        );
435        assert!(
436            decimal16_too_large
437                .unwrap_err()
438                .to_string()
439                .contains("wider than max precision")
440        );
441
442        let decimal16_too_small =
443            VariantDecimal16::try_new(-100000000000000000000000000000000000000_i128, 2);
444        assert!(
445            decimal16_too_small.is_err(),
446            "Decimal16 precision underflow should fail"
447        );
448        assert!(
449            decimal16_too_small
450                .unwrap_err()
451                .to_string()
452                .contains("wider than max precision")
453        );
454
455        // Test valid edge cases for Decimal16
456        let decimal16_max_valid =
457            VariantDecimal16::try_new(99999999999999999999999999999999999999_i128, 2);
458        assert!(
459            decimal16_max_valid.is_ok(),
460            "Decimal16 max valid value should succeed"
461        );
462
463        let decimal16_min_valid =
464            VariantDecimal16::try_new(-99999999999999999999999999999999999999_i128, 2);
465        assert!(
466            decimal16_min_valid.is_ok(),
467            "Decimal16 min valid value should succeed"
468        );
469    }
470
471    #[test]
472    fn test_variant_decimal_invalid_scale() {
473        // Test invalid scale for Decimal4 (scale > 9)
474        let decimal4_invalid_scale = VariantDecimal4::try_new(123_i32, 10);
475        assert!(
476            decimal4_invalid_scale.is_err(),
477            "Decimal4 with scale > 9 should fail"
478        );
479        assert!(
480            decimal4_invalid_scale
481                .unwrap_err()
482                .to_string()
483                .contains("larger than max precision")
484        );
485
486        let decimal4_invalid_scale_large = VariantDecimal4::try_new(123_i32, 20);
487        assert!(
488            decimal4_invalid_scale_large.is_err(),
489            "Decimal4 with scale > 9 should fail"
490        );
491
492        // Test valid scale edge case for Decimal4
493        let decimal4_valid_scale = VariantDecimal4::try_new(123_i32, 9);
494        assert!(
495            decimal4_valid_scale.is_ok(),
496            "Decimal4 with scale = 9 should succeed"
497        );
498
499        // Test invalid scale for Decimal8 (scale > 18)
500        let decimal8_invalid_scale = VariantDecimal8::try_new(123_i64, 19);
501        assert!(
502            decimal8_invalid_scale.is_err(),
503            "Decimal8 with scale > 18 should fail"
504        );
505        assert!(
506            decimal8_invalid_scale
507                .unwrap_err()
508                .to_string()
509                .contains("larger than max precision")
510        );
511
512        let decimal8_invalid_scale_large = VariantDecimal8::try_new(123_i64, 25);
513        assert!(
514            decimal8_invalid_scale_large.is_err(),
515            "Decimal8 with scale > 18 should fail"
516        );
517
518        // Test valid scale edge case for Decimal8
519        let decimal8_valid_scale = VariantDecimal8::try_new(123_i64, 18);
520        assert!(
521            decimal8_valid_scale.is_ok(),
522            "Decimal8 with scale = 18 should succeed"
523        );
524
525        // Test invalid scale for Decimal16 (scale > 38)
526        let decimal16_invalid_scale = VariantDecimal16::try_new(123_i128, 39);
527        assert!(
528            decimal16_invalid_scale.is_err(),
529            "Decimal16 with scale > 38 should fail"
530        );
531        assert!(
532            decimal16_invalid_scale
533                .unwrap_err()
534                .to_string()
535                .contains("larger than max precision")
536        );
537
538        let decimal16_invalid_scale_large = VariantDecimal16::try_new(123_i128, 50);
539        assert!(
540            decimal16_invalid_scale_large.is_err(),
541            "Decimal16 with scale > 38 should fail"
542        );
543
544        // Test valid scale edge case for Decimal16
545        let decimal16_valid_scale = VariantDecimal16::try_new(123_i128, 38);
546        assert!(
547            decimal16_valid_scale.is_ok(),
548            "Decimal16 with scale = 38 should succeed"
549        );
550    }
551
552    #[test]
553    fn test_variant_decimal4_display() {
554        // Test zero scale (integers)
555        let d = VariantDecimal4::try_new(42, 0).unwrap();
556        assert_eq!(d.to_string(), "42");
557
558        let d = VariantDecimal4::try_new(-42, 0).unwrap();
559        assert_eq!(d.to_string(), "-42");
560
561        // Test basic decimal formatting
562        let d = VariantDecimal4::try_new(12345, 2).unwrap();
563        assert_eq!(d.to_string(), "123.45");
564
565        let d = VariantDecimal4::try_new(-12345, 2).unwrap();
566        assert_eq!(d.to_string(), "-123.45");
567
568        // Test trailing zeros are trimmed
569        let d = VariantDecimal4::try_new(12300, 2).unwrap();
570        assert_eq!(d.to_string(), "123");
571
572        let d = VariantDecimal4::try_new(-12300, 2).unwrap();
573        assert_eq!(d.to_string(), "-123");
574
575        // Test leading zeros in decimal part
576        let d = VariantDecimal4::try_new(1005, 3).unwrap();
577        assert_eq!(d.to_string(), "1.005");
578
579        let d = VariantDecimal4::try_new(-1005, 3).unwrap();
580        assert_eq!(d.to_string(), "-1.005");
581
582        // Test number smaller than scale (leading zero before decimal)
583        let d = VariantDecimal4::try_new(123, 4).unwrap();
584        assert_eq!(d.to_string(), "0.0123");
585
586        let d = VariantDecimal4::try_new(-123, 4).unwrap();
587        assert_eq!(d.to_string(), "-0.0123");
588
589        // Test zero
590        let d = VariantDecimal4::try_new(0, 0).unwrap();
591        assert_eq!(d.to_string(), "0");
592
593        let d = VariantDecimal4::try_new(0, 3).unwrap();
594        assert_eq!(d.to_string(), "0");
595
596        // Test max scale
597        let d = VariantDecimal4::try_new(123456789, 9).unwrap();
598        assert_eq!(d.to_string(), "0.123456789");
599
600        let d = VariantDecimal4::try_new(-123456789, 9).unwrap();
601        assert_eq!(d.to_string(), "-0.123456789");
602
603        // Test max precision
604        let d = VariantDecimal4::try_new(999999999, 0).unwrap();
605        assert_eq!(d.to_string(), "999999999");
606
607        let d = VariantDecimal4::try_new(-999999999, 0).unwrap();
608        assert_eq!(d.to_string(), "-999999999");
609
610        // Test trailing zeros with mixed decimal places
611        let d = VariantDecimal4::try_new(120050, 4).unwrap();
612        assert_eq!(d.to_string(), "12.005");
613
614        let d = VariantDecimal4::try_new(-120050, 4).unwrap();
615        assert_eq!(d.to_string(), "-12.005");
616    }
617
618    #[test]
619    fn test_variant_decimal8_display() {
620        // Test zero scale (integers)
621        let d = VariantDecimal8::try_new(42, 0).unwrap();
622        assert_eq!(d.to_string(), "42");
623
624        let d = VariantDecimal8::try_new(-42, 0).unwrap();
625        assert_eq!(d.to_string(), "-42");
626
627        // Test basic decimal formatting
628        let d = VariantDecimal8::try_new(1234567890, 3).unwrap();
629        assert_eq!(d.to_string(), "1234567.89");
630
631        let d = VariantDecimal8::try_new(-1234567890, 3).unwrap();
632        assert_eq!(d.to_string(), "-1234567.89");
633
634        // Test trailing zeros are trimmed
635        let d = VariantDecimal8::try_new(123000000, 6).unwrap();
636        assert_eq!(d.to_string(), "123");
637
638        let d = VariantDecimal8::try_new(-123000000, 6).unwrap();
639        assert_eq!(d.to_string(), "-123");
640
641        // Test leading zeros in decimal part
642        let d = VariantDecimal8::try_new(100005, 6).unwrap();
643        assert_eq!(d.to_string(), "0.100005");
644
645        let d = VariantDecimal8::try_new(-100005, 6).unwrap();
646        assert_eq!(d.to_string(), "-0.100005");
647
648        // Test number smaller than scale
649        let d = VariantDecimal8::try_new(123, 10).unwrap();
650        assert_eq!(d.to_string(), "0.0000000123");
651
652        let d = VariantDecimal8::try_new(-123, 10).unwrap();
653        assert_eq!(d.to_string(), "-0.0000000123");
654
655        // Test zero
656        let d = VariantDecimal8::try_new(0, 0).unwrap();
657        assert_eq!(d.to_string(), "0");
658
659        let d = VariantDecimal8::try_new(0, 10).unwrap();
660        assert_eq!(d.to_string(), "0");
661
662        // Test max scale
663        let d = VariantDecimal8::try_new(123456789012345678, 18).unwrap();
664        assert_eq!(d.to_string(), "0.123456789012345678");
665
666        let d = VariantDecimal8::try_new(-123456789012345678, 18).unwrap();
667        assert_eq!(d.to_string(), "-0.123456789012345678");
668
669        // Test max precision
670        let d = VariantDecimal8::try_new(999999999999999999, 0).unwrap();
671        assert_eq!(d.to_string(), "999999999999999999");
672
673        let d = VariantDecimal8::try_new(-999999999999999999, 0).unwrap();
674        assert_eq!(d.to_string(), "-999999999999999999");
675
676        // Test complex trailing zeros
677        let d = VariantDecimal8::try_new(1200000050000, 10).unwrap();
678        assert_eq!(d.to_string(), "120.000005");
679
680        let d = VariantDecimal8::try_new(-1200000050000, 10).unwrap();
681        assert_eq!(d.to_string(), "-120.000005");
682    }
683
684    #[test]
685    fn test_variant_decimal16_display() {
686        // Test zero scale (integers)
687        let d = VariantDecimal16::try_new(42, 0).unwrap();
688        assert_eq!(d.to_string(), "42");
689
690        let d = VariantDecimal16::try_new(-42, 0).unwrap();
691        assert_eq!(d.to_string(), "-42");
692
693        // Test basic decimal formatting
694        let d = VariantDecimal16::try_new(123456789012345, 4).unwrap();
695        assert_eq!(d.to_string(), "12345678901.2345");
696
697        let d = VariantDecimal16::try_new(-123456789012345, 4).unwrap();
698        assert_eq!(d.to_string(), "-12345678901.2345");
699
700        // Test trailing zeros are trimmed
701        let d = VariantDecimal16::try_new(12300000000, 8).unwrap();
702        assert_eq!(d.to_string(), "123");
703
704        let d = VariantDecimal16::try_new(-12300000000, 8).unwrap();
705        assert_eq!(d.to_string(), "-123");
706
707        // Test leading zeros in decimal part
708        let d = VariantDecimal16::try_new(10000005, 8).unwrap();
709        assert_eq!(d.to_string(), "0.10000005");
710
711        let d = VariantDecimal16::try_new(-10000005, 8).unwrap();
712        assert_eq!(d.to_string(), "-0.10000005");
713
714        // Test number smaller than scale
715        let d = VariantDecimal16::try_new(123, 20).unwrap();
716        assert_eq!(d.to_string(), "0.00000000000000000123");
717
718        let d = VariantDecimal16::try_new(-123, 20).unwrap();
719        assert_eq!(d.to_string(), "-0.00000000000000000123");
720
721        // Test zero
722        let d = VariantDecimal16::try_new(0, 0).unwrap();
723        assert_eq!(d.to_string(), "0");
724
725        let d = VariantDecimal16::try_new(0, 20).unwrap();
726        assert_eq!(d.to_string(), "0");
727
728        // Test max scale
729        let d = VariantDecimal16::try_new(12345678901234567890123456789012345678_i128, 38).unwrap();
730        assert_eq!(d.to_string(), "0.12345678901234567890123456789012345678");
731
732        let d =
733            VariantDecimal16::try_new(-12345678901234567890123456789012345678_i128, 38).unwrap();
734        assert_eq!(d.to_string(), "-0.12345678901234567890123456789012345678");
735
736        // Test max precision integer
737        let d = VariantDecimal16::try_new(99999999999999999999999999999999999999_i128, 0).unwrap();
738        assert_eq!(d.to_string(), "99999999999999999999999999999999999999");
739
740        let d = VariantDecimal16::try_new(-99999999999999999999999999999999999999_i128, 0).unwrap();
741        assert_eq!(d.to_string(), "-99999999999999999999999999999999999999");
742
743        // Test complex trailing zeros
744        let d = VariantDecimal16::try_new(12000000000000050000000000000_i128, 25).unwrap();
745        assert_eq!(d.to_string(), "1200.000000000005");
746
747        let d = VariantDecimal16::try_new(-12000000000000050000000000000_i128, 25).unwrap();
748        assert_eq!(d.to_string(), "-1200.000000000005");
749
750        // Test large integer that would overflow i64 but fits in i128
751        let large_int = 12345678901234567890123456789_i128;
752        let d = VariantDecimal16::try_new(large_int, 0).unwrap();
753        assert_eq!(d.to_string(), "12345678901234567890123456789");
754
755        let d = VariantDecimal16::try_new(-large_int, 0).unwrap();
756        assert_eq!(d.to_string(), "-12345678901234567890123456789");
757    }
758}