This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new f8bcc58f09 [VARIANT] impl Display for VariantDecimalXX (#7785)
f8bcc58f09 is described below

commit f8bcc58f096771ccd2bc08c67151cc855a5aebb7
Author: Ryan Johnson <[email protected]>
AuthorDate: Thu Jun 26 14:23:40 2025 -0700

    [VARIANT] impl Display for VariantDecimalXX (#7785)
    
    # Which issue does this PR close?
    
    * Part of https://github.com/apache/arrow-rs/issues/6736
    
    # Rationale for this change
    
    Follow-up to https://github.com/apache/arrow-rs/pull/7670, which
    accidentally introduced a lossy to-string conversion for variant
    decimals.
    
    # What changes are included in this PR?
    
    Use integer + string operations to convert decimal values to string,
    instead of floating point that could lose precision.
    
    Also, the `VariantDecimalXX` structs now `impl Display`, which greatly
    simplifies the to-json path. A new (private) macro encapsulates the
    to-string logic, since it's syntactically identical for all three
    decimal sizes.
    
    # Are these changes tested?
    
    Yes, new unit tests added.
    
    # Are there any user-facing changes?
    
    The `VariantDecimalXX` structs now `impl Display`
    
    ---------
    
    Co-authored-by: Andrew Lamb <[email protected]>
---
 arrow-schema/src/datatype.rs            |   2 +-
 arrow-schema/src/datatype_parse.rs      |  14 +-
 arrow-schema/src/fields.rs              |   2 +-
 parquet-variant/src/builder.rs          |   2 +-
 parquet-variant/src/decoder.rs          |   3 +-
 parquet-variant/src/to_json.rs          | 131 ++++-------------
 parquet-variant/src/variant/decimal.rs  | 249 ++++++++++++++++++++++++++++++++
 parquet-variant/src/variant/metadata.rs |   3 +-
 8 files changed, 288 insertions(+), 118 deletions(-)

diff --git a/arrow-schema/src/datatype.rs b/arrow-schema/src/datatype.rs
index f22b6c52ba..f742d99cda 100644
--- a/arrow-schema/src/datatype.rs
+++ b/arrow-schema/src/datatype.rs
@@ -467,7 +467,7 @@ impl fmt::Display for DataType {
                         .map(|f| format!("{} {}", f.name(), f.data_type()))
                         .collect::<Vec<_>>()
                         .join(", ");
-                    write!(f, "{}", fields_str)?;
+                    write!(f, "{fields_str}")?;
                 }
                 write!(f, ")")?;
                 Ok(())
diff --git a/arrow-schema/src/datatype_parse.rs 
b/arrow-schema/src/datatype_parse.rs
index 70e4b351ff..d0fc962fb1 100644
--- a/arrow-schema/src/datatype_parse.rs
+++ b/arrow-schema/src/datatype_parse.rs
@@ -79,10 +79,9 @@ impl<'a> Parser<'a> {
             Token::LargeList => self.parse_large_list(),
             Token::FixedSizeList => self.parse_fixed_size_list(),
             Token::Struct => self.parse_struct(),
-            Token::FieldName(word) => Err(make_error(
-                self.val,
-                &format!("unrecognized word: {}", word),
-            )),
+            Token::FieldName(word) => {
+                Err(make_error(self.val, &format!("unrecognized word: 
{word}")))
+            }
             tok => Err(make_error(
                 self.val,
                 &format!("finding next type, got unexpected '{tok}'"),
@@ -155,10 +154,9 @@ impl<'a> Parser<'a> {
     fn parse_double_quoted_string(&mut self, context: &str) -> 
ArrowResult<String> {
         match self.next_token()? {
             Token::DoubleQuotedString(s) => Ok(s),
-            Token::FieldName(word) => Err(make_error(
-                self.val,
-                &format!("unrecognized word: {}", word),
-            )),
+            Token::FieldName(word) => {
+                Err(make_error(self.val, &format!("unrecognized word: 
{word}")))
+            }
             tok => Err(make_error(
                 self.val,
                 &format!("finding double quoted string for {context}, got 
'{tok}'"),
diff --git a/arrow-schema/src/fields.rs b/arrow-schema/src/fields.rs
index 904b933cd2..65b79660e6 100644
--- a/arrow-schema/src/fields.rs
+++ b/arrow-schema/src/fields.rs
@@ -365,7 +365,7 @@ impl UnionFields {
             .inspect(|&idx| {
                 let mask = 1_u128 << idx;
                 if (set & mask) != 0 {
-                    panic!("duplicate type id: {}", idx);
+                    panic!("duplicate type id: {idx}");
                 } else {
                     set |= mask;
                 }
diff --git a/parquet-variant/src/builder.rs b/parquet-variant/src/builder.rs
index 73197e6124..d67ab9c001 100644
--- a/parquet-variant/src/builder.rs
+++ b/parquet-variant/src/builder.rs
@@ -855,7 +855,7 @@ mod tests {
                 let val2 = list.get(2).unwrap();
                 assert_eq!(val2, Variant::ShortString(ShortString("test")));
             }
-            _ => panic!("Expected an array variant, got: {:?}", variant),
+            _ => panic!("Expected an array variant, got: {variant:?}"),
         }
     }
 
diff --git a/parquet-variant/src/decoder.rs b/parquet-variant/src/decoder.rs
index cb8336b5b8..e73911aa29 100644
--- a/parquet-variant/src/decoder.rs
+++ b/parquet-variant/src/decoder.rs
@@ -95,8 +95,7 @@ impl TryFrom<u8> for VariantPrimitiveType {
             15 => Ok(VariantPrimitiveType::Binary),
             16 => Ok(VariantPrimitiveType::String),
             _ => Err(ArrowError::InvalidArgumentError(format!(
-                "unknown primitive type: {}",
-                value
+                "unknown primitive type: {value}",
             ))),
         }
     }
diff --git a/parquet-variant/src/to_json.rs b/parquet-variant/src/to_json.rs
index 9e5cbdccef..09efe20a7a 100644
--- a/parquet-variant/src/to_json.rs
+++ b/parquet-variant/src/to_json.rs
@@ -41,51 +41,7 @@ fn format_binary_base64(bytes: &[u8]) -> String {
     general_purpose::STANDARD.encode(bytes)
 }
 
-/// Write decimal using scovich's hybrid approach for i32
-fn write_decimal_i32(
-    json_buffer: &mut impl Write,
-    integer: i32,
-    scale: u8,
-) -> Result<(), ArrowError> {
-    let integer = if scale == 0 {
-        integer
-    } else {
-        let divisor = 10_i32.pow(scale as u32);
-        if integer % divisor != 0 {
-            // fall back to floating point
-            let result = integer as f64 / divisor as f64;
-            write!(json_buffer, "{}", result)?;
-            return Ok(());
-        }
-        integer / divisor
-    };
-    write!(json_buffer, "{}", integer)?;
-    Ok(())
-}
-
-/// Write decimal using scovich's hybrid approach for i64
-fn write_decimal_i64(
-    json_buffer: &mut impl Write,
-    integer: i64,
-    scale: u8,
-) -> Result<(), ArrowError> {
-    let integer = if scale == 0 {
-        integer
-    } else {
-        let divisor = 10_i64.pow(scale as u32);
-        if integer % divisor != 0 {
-            // fall back to floating point
-            let result = integer as f64 / divisor as f64;
-            write!(json_buffer, "{}", result)?;
-            return Ok(());
-        }
-        integer / divisor
-    };
-    write!(json_buffer, "{}", integer)?;
-    Ok(())
-}
-
-/// Converts a Variant to JSON and writes it to the provided [`Write`]
+/// Converts a Variant to JSON and writes it to the provided `Write`
 ///
 /// This function writes JSON directly to any type that implements [`Write`],
 /// making it efficient for streaming or when you want to control the output 
destination.
@@ -140,40 +96,15 @@ pub fn variant_to_json(json_buffer: &mut impl Write, 
variant: &Variant) -> Resul
         Variant::Null => write!(json_buffer, "null")?,
         Variant::BooleanTrue => write!(json_buffer, "true")?,
         Variant::BooleanFalse => write!(json_buffer, "false")?,
-        Variant::Int8(i) => write!(json_buffer, "{}", i)?,
-        Variant::Int16(i) => write!(json_buffer, "{}", i)?,
-        Variant::Int32(i) => write!(json_buffer, "{}", i)?,
-        Variant::Int64(i) => write!(json_buffer, "{}", i)?,
-        Variant::Float(f) => write!(json_buffer, "{}", f)?,
-        Variant::Double(f) => write!(json_buffer, "{}", f)?,
-        Variant::Decimal4(VariantDecimal4 { integer, scale }) => {
-            write_decimal_i32(json_buffer, *integer, *scale)?;
-        }
-        Variant::Decimal8(VariantDecimal8 { integer, scale }) => {
-            write_decimal_i64(json_buffer, *integer, *scale)?;
-        }
-        Variant::Decimal16(VariantDecimal16 { integer, scale }) => {
-            let integer = if *scale == 0 {
-                *integer
-            } else {
-                let divisor = 10_i128.pow(*scale as u32);
-                if integer % divisor != 0 {
-                    // fall back to floating point
-                    let result = *integer as f64 / divisor as f64;
-                    write!(json_buffer, "{}", result)?;
-                    return Ok(());
-                }
-                integer / divisor
-            };
-            // Prefer to emit as i64, but fall back to u64 or even f64 (lossy) 
if necessary
-            if let Ok(i64_val) = i64::try_from(integer) {
-                write!(json_buffer, "{}", i64_val)?;
-            } else if let Ok(u64_val) = u64::try_from(integer) {
-                write!(json_buffer, "{}", u64_val)?;
-            } else {
-                write!(json_buffer, "{}", integer as f64)?;
-            }
-        }
+        Variant::Int8(i) => write!(json_buffer, "{i}")?,
+        Variant::Int16(i) => write!(json_buffer, "{i}")?,
+        Variant::Int32(i) => write!(json_buffer, "{i}")?,
+        Variant::Int64(i) => write!(json_buffer, "{i}")?,
+        Variant::Float(f) => write!(json_buffer, "{f}")?,
+        Variant::Double(f) => write!(json_buffer, "{f}")?,
+        Variant::Decimal4(decimal) => write!(json_buffer, "{decimal}")?,
+        Variant::Decimal8(decimal) => write!(json_buffer, "{decimal}")?,
+        Variant::Decimal16(decimal) => write!(json_buffer, "{decimal}")?,
         Variant::Date(date) => write!(json_buffer, "\"{}\"", 
format_date_string(date))?,
         Variant::TimestampMicros(ts) => write!(json_buffer, "\"{}\"", 
ts.to_rfc3339())?,
         Variant::TimestampNtzMicros(ts) => {
@@ -183,23 +114,23 @@ pub fn variant_to_json(json_buffer: &mut impl Write, 
variant: &Variant) -> Resul
             // Encode binary as base64 string
             let base64_str = format_binary_base64(bytes);
             let json_str = serde_json::to_string(&base64_str).map_err(|e| {
-                ArrowError::InvalidArgumentError(format!("JSON encoding error: 
{}", e))
+                ArrowError::InvalidArgumentError(format!("JSON encoding error: 
{e}"))
             })?;
-            write!(json_buffer, "{}", json_str)?
+            write!(json_buffer, "{json_str}")?
         }
         Variant::String(s) => {
             // Use serde_json to properly escape the string
             let json_str = serde_json::to_string(s).map_err(|e| {
-                ArrowError::InvalidArgumentError(format!("JSON encoding error: 
{}", e))
+                ArrowError::InvalidArgumentError(format!("JSON encoding error: 
{e}"))
             })?;
-            write!(json_buffer, "{}", json_str)?
+            write!(json_buffer, "{json_str}")?
         }
         Variant::ShortString(s) => {
             // Use serde_json to properly escape the string
             let json_str = serde_json::to_string(s.as_str()).map_err(|e| {
-                ArrowError::InvalidArgumentError(format!("JSON encoding error: 
{}", e))
+                ArrowError::InvalidArgumentError(format!("JSON encoding error: 
{e}"))
             })?;
-            write!(json_buffer, "{}", json_str)?
+            write!(json_buffer, "{json_str}")?
         }
         Variant::Object(obj) => {
             convert_object_to_json(json_buffer, obj)?;
@@ -226,9 +157,9 @@ fn convert_object_to_json(buffer: &mut impl Write, obj: 
&VariantObject) -> Resul
 
         // Write the key (properly escaped)
         let json_key = serde_json::to_string(key).map_err(|e| {
-            ArrowError::InvalidArgumentError(format!("JSON key encoding error: 
{}", e))
+            ArrowError::InvalidArgumentError(format!("JSON key encoding error: 
{e}"))
         })?;
-        write!(buffer, "{}:", json_key)?;
+        write!(buffer, "{json_key}:")?;
 
         // Recursively convert the value
         variant_to_json(buffer, &value)?;
@@ -313,7 +244,7 @@ pub fn variant_to_json_string(variant: &Variant) -> 
Result<String, ArrowError> {
     let mut buffer = Vec::new();
     variant_to_json(&mut buffer, variant)?;
     String::from_utf8(buffer)
-        .map_err(|e| ArrowError::InvalidArgumentError(format!("UTF-8 
conversion error: {}", e)))
+        .map_err(|e| ArrowError::InvalidArgumentError(format!("UTF-8 
conversion error: {e}")))
 }
 
 /// Convert [`Variant`] to [`serde_json::Value`]
@@ -394,7 +325,8 @@ pub fn variant_to_json_value(variant: &Variant) -> 
Result<Value, ArrowError> {
                 }
                 integer / divisor
             };
-            // Prefer to emit as i64, but fall back to u64 or even f64 (lossy) 
if necessary
+            // i128 has higher precision than any 64-bit type. Try a lossless 
narrowing cast to
+            // i64 or u64 first, falling back to a lossy narrowing cast to f64 
if necessary.
             let value = i64::try_from(integer)
                 .map(Value::from)
                 .or_else(|_| u64::try_from(integer).map(Value::from))
@@ -928,8 +860,7 @@ mod tests {
         let json = variant_to_json_string(&variant)?;
 
         // Parse the JSON to verify structure - handle JSON parsing errors 
manually
-        let parsed: Value = serde_json::from_str(&json)
-            .map_err(|e| ArrowError::ParseError(format!("JSON parse error: 
{}", e)))?;
+        let parsed: Value = serde_json::from_str(&json).unwrap();
         let obj = parsed.as_object().expect("expected JSON object");
         assert_eq!(obj.get("name"), Some(&Value::String("Alice".to_string())));
         assert_eq!(obj.get("age"), Some(&Value::Number(30.into())));
@@ -990,8 +921,7 @@ mod tests {
         assert!(json.contains("😀 Smiley"));
 
         // Verify that the JSON can be parsed back
-        let parsed: Value = serde_json::from_str(&json)
-            .map_err(|e| ArrowError::ParseError(format!("JSON parse error: 
{}", e)))?;
+        let parsed: Value = serde_json::from_str(&json).unwrap();
         assert!(matches!(parsed, Value::Object(_)));
 
         Ok(())
@@ -1069,8 +999,7 @@ mod tests {
         let variant = Variant::try_new(&metadata, &value)?;
         let json = variant_to_json_string(&variant)?;
 
-        let parsed: Value = serde_json::from_str(&json)
-            .map_err(|e| ArrowError::ParseError(format!("JSON parse error: 
{}", e)))?;
+        let parsed: Value = serde_json::from_str(&json).unwrap();
         let arr = parsed.as_array().expect("expected JSON array");
         assert_eq!(arr.len(), 5);
         assert_eq!(arr[0], Value::String("hello".to_string()));
@@ -1102,8 +1031,7 @@ mod tests {
         let json = variant_to_json_string(&variant)?;
 
         // Parse and verify all fields are present
-        let parsed: Value = serde_json::from_str(&json)
-            .map_err(|e| ArrowError::ParseError(format!("JSON parse error: 
{}", e)))?;
+        let parsed: Value = serde_json::from_str(&json).unwrap();
         let obj = parsed.as_object().expect("expected JSON object");
         assert_eq!(obj.len(), 3);
         assert_eq!(obj.get("alpha"), 
Some(&Value::String("first".to_string())));
@@ -1135,8 +1063,7 @@ mod tests {
         let variant = Variant::try_new(&metadata, &value)?;
         let json = variant_to_json_string(&variant)?;
 
-        let parsed: Value = serde_json::from_str(&json)
-            .map_err(|e| ArrowError::ParseError(format!("JSON parse error: 
{}", e)))?;
+        let parsed: Value = serde_json::from_str(&json).unwrap();
         let arr = parsed.as_array().expect("expected JSON array");
         assert_eq!(arr.len(), 7);
         assert_eq!(arr[0], Value::String("string_value".to_string()));
@@ -1171,8 +1098,7 @@ mod tests {
         let variant = Variant::try_new(&metadata, &value)?;
         let json = variant_to_json_string(&variant)?;
 
-        let parsed: Value = serde_json::from_str(&json)
-            .map_err(|e| ArrowError::ParseError(format!("JSON parse error: 
{}", e)))?;
+        let parsed: Value = serde_json::from_str(&json).unwrap();
         let obj = parsed.as_object().expect("expected JSON object");
         assert_eq!(obj.len(), 6);
         assert_eq!(
@@ -1202,8 +1128,7 @@ mod tests {
 
         // Due to f64 precision limits, we expect precision loss for values > 
2^53
         // Both functions should produce consistent results (even if not exact)
-        let parsed: Value = serde_json::from_str(&json_string)
-            .map_err(|e| ArrowError::ParseError(format!("JSON parse error: 
{}", e)))?;
+        let parsed: Value = serde_json::from_str(&json_string).unwrap();
         assert_eq!(parsed, json_value);
 
         // Test a case that can be exactly represented (integer result)
diff --git a/parquet-variant/src/variant/decimal.rs 
b/parquet-variant/src/variant/decimal.rs
index f03b1e1e38..c92fd1df82 100644
--- a/parquet-variant/src/variant/decimal.rs
+++ b/parquet-variant/src/variant/decimal.rs
@@ -15,6 +15,30 @@
 // specific language governing permissions and limitations
 // under the License.
 use arrow_schema::ArrowError;
+use std::fmt;
+
+// Macro to format decimal values, using only integer arithmetic to avoid 
floating point precision loss
+macro_rules! format_decimal {
+    ($f:expr, $integer:expr, $scale:expr, $int_type:ty) => {{
+        let integer = if $scale == 0 {
+            $integer
+        } else {
+            let divisor = (10 as $int_type).pow($scale as u32);
+            let remainder = $integer % divisor;
+            if remainder != 0 {
+                // Track the sign explicitly, in case the quotient is zero
+                let sign = if $integer < 0 { "-" } else { "" };
+                // Format an unsigned remainder with leading zeros and strip 
(unnecessary) trailing zeros.
+                let remainder = format!("{:0width$}", remainder.abs(), width = 
$scale as usize);
+                let remainder = remainder.trim_end_matches('0');
+                let quotient = $integer / divisor;
+                return write!($f, "{}{}.{}", sign, quotient.abs(), remainder);
+            }
+            $integer / divisor
+        };
+        write!($f, "{}", integer)
+    }};
+}
 
 /// Represents a 4-byte decimal value in the Variant format.
 ///
@@ -57,6 +81,12 @@ impl VariantDecimal4 {
     }
 }
 
+impl fmt::Display for VariantDecimal4 {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        format_decimal!(f, self.integer, self.scale, i32)
+    }
+}
+
 /// Represents an 8-byte decimal value in the Variant format.
 ///
 /// This struct stores a decimal number using a 64-bit signed integer for the 
coefficient
@@ -99,6 +129,12 @@ impl VariantDecimal8 {
     }
 }
 
+impl fmt::Display for VariantDecimal8 {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        format_decimal!(f, self.integer, self.scale, i64)
+    }
+}
+
 /// Represents an 16-byte decimal value in the Variant format.
 ///
 /// This struct stores a decimal number using a 128-bit signed integer for the 
coefficient
@@ -141,6 +177,12 @@ impl VariantDecimal16 {
     }
 }
 
+impl fmt::Display for VariantDecimal16 {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        format_decimal!(f, self.integer, self.scale, i128)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -328,4 +370,211 @@ mod tests {
             "Decimal16 with scale = 38 should succeed"
         );
     }
+
+    #[test]
+    fn test_variant_decimal4_display() {
+        // Test zero scale (integers)
+        let d = VariantDecimal4::try_new(42, 0).unwrap();
+        assert_eq!(d.to_string(), "42");
+
+        let d = VariantDecimal4::try_new(-42, 0).unwrap();
+        assert_eq!(d.to_string(), "-42");
+
+        // Test basic decimal formatting
+        let d = VariantDecimal4::try_new(12345, 2).unwrap();
+        assert_eq!(d.to_string(), "123.45");
+
+        let d = VariantDecimal4::try_new(-12345, 2).unwrap();
+        assert_eq!(d.to_string(), "-123.45");
+
+        // Test trailing zeros are trimmed
+        let d = VariantDecimal4::try_new(12300, 2).unwrap();
+        assert_eq!(d.to_string(), "123");
+
+        let d = VariantDecimal4::try_new(-12300, 2).unwrap();
+        assert_eq!(d.to_string(), "-123");
+
+        // Test leading zeros in decimal part
+        let d = VariantDecimal4::try_new(1005, 3).unwrap();
+        assert_eq!(d.to_string(), "1.005");
+
+        let d = VariantDecimal4::try_new(-1005, 3).unwrap();
+        assert_eq!(d.to_string(), "-1.005");
+
+        // Test number smaller than scale (leading zero before decimal)
+        let d = VariantDecimal4::try_new(123, 4).unwrap();
+        assert_eq!(d.to_string(), "0.0123");
+
+        let d = VariantDecimal4::try_new(-123, 4).unwrap();
+        assert_eq!(d.to_string(), "-0.0123");
+
+        // Test zero
+        let d = VariantDecimal4::try_new(0, 0).unwrap();
+        assert_eq!(d.to_string(), "0");
+
+        let d = VariantDecimal4::try_new(0, 3).unwrap();
+        assert_eq!(d.to_string(), "0");
+
+        // Test max scale
+        let d = VariantDecimal4::try_new(123456789, 9).unwrap();
+        assert_eq!(d.to_string(), "0.123456789");
+
+        let d = VariantDecimal4::try_new(-123456789, 9).unwrap();
+        assert_eq!(d.to_string(), "-0.123456789");
+
+        // Test max precision
+        let d = VariantDecimal4::try_new(999999999, 0).unwrap();
+        assert_eq!(d.to_string(), "999999999");
+
+        let d = VariantDecimal4::try_new(-999999999, 0).unwrap();
+        assert_eq!(d.to_string(), "-999999999");
+
+        // Test trailing zeros with mixed decimal places
+        let d = VariantDecimal4::try_new(120050, 4).unwrap();
+        assert_eq!(d.to_string(), "12.005");
+
+        let d = VariantDecimal4::try_new(-120050, 4).unwrap();
+        assert_eq!(d.to_string(), "-12.005");
+    }
+
+    #[test]
+    fn test_variant_decimal8_display() {
+        // Test zero scale (integers)
+        let d = VariantDecimal8::try_new(42, 0).unwrap();
+        assert_eq!(d.to_string(), "42");
+
+        let d = VariantDecimal8::try_new(-42, 0).unwrap();
+        assert_eq!(d.to_string(), "-42");
+
+        // Test basic decimal formatting
+        let d = VariantDecimal8::try_new(1234567890, 3).unwrap();
+        assert_eq!(d.to_string(), "1234567.89");
+
+        let d = VariantDecimal8::try_new(-1234567890, 3).unwrap();
+        assert_eq!(d.to_string(), "-1234567.89");
+
+        // Test trailing zeros are trimmed
+        let d = VariantDecimal8::try_new(123000000, 6).unwrap();
+        assert_eq!(d.to_string(), "123");
+
+        let d = VariantDecimal8::try_new(-123000000, 6).unwrap();
+        assert_eq!(d.to_string(), "-123");
+
+        // Test leading zeros in decimal part
+        let d = VariantDecimal8::try_new(100005, 6).unwrap();
+        assert_eq!(d.to_string(), "0.100005");
+
+        let d = VariantDecimal8::try_new(-100005, 6).unwrap();
+        assert_eq!(d.to_string(), "-0.100005");
+
+        // Test number smaller than scale
+        let d = VariantDecimal8::try_new(123, 10).unwrap();
+        assert_eq!(d.to_string(), "0.0000000123");
+
+        let d = VariantDecimal8::try_new(-123, 10).unwrap();
+        assert_eq!(d.to_string(), "-0.0000000123");
+
+        // Test zero
+        let d = VariantDecimal8::try_new(0, 0).unwrap();
+        assert_eq!(d.to_string(), "0");
+
+        let d = VariantDecimal8::try_new(0, 10).unwrap();
+        assert_eq!(d.to_string(), "0");
+
+        // Test max scale
+        let d = VariantDecimal8::try_new(123456789012345678, 18).unwrap();
+        assert_eq!(d.to_string(), "0.123456789012345678");
+
+        let d = VariantDecimal8::try_new(-123456789012345678, 18).unwrap();
+        assert_eq!(d.to_string(), "-0.123456789012345678");
+
+        // Test max precision
+        let d = VariantDecimal8::try_new(999999999999999999, 0).unwrap();
+        assert_eq!(d.to_string(), "999999999999999999");
+
+        let d = VariantDecimal8::try_new(-999999999999999999, 0).unwrap();
+        assert_eq!(d.to_string(), "-999999999999999999");
+
+        // Test complex trailing zeros
+        let d = VariantDecimal8::try_new(1200000050000, 10).unwrap();
+        assert_eq!(d.to_string(), "120.000005");
+
+        let d = VariantDecimal8::try_new(-1200000050000, 10).unwrap();
+        assert_eq!(d.to_string(), "-120.000005");
+    }
+
+    #[test]
+    fn test_variant_decimal16_display() {
+        // Test zero scale (integers)
+        let d = VariantDecimal16::try_new(42, 0).unwrap();
+        assert_eq!(d.to_string(), "42");
+
+        let d = VariantDecimal16::try_new(-42, 0).unwrap();
+        assert_eq!(d.to_string(), "-42");
+
+        // Test basic decimal formatting
+        let d = VariantDecimal16::try_new(123456789012345, 4).unwrap();
+        assert_eq!(d.to_string(), "12345678901.2345");
+
+        let d = VariantDecimal16::try_new(-123456789012345, 4).unwrap();
+        assert_eq!(d.to_string(), "-12345678901.2345");
+
+        // Test trailing zeros are trimmed
+        let d = VariantDecimal16::try_new(12300000000, 8).unwrap();
+        assert_eq!(d.to_string(), "123");
+
+        let d = VariantDecimal16::try_new(-12300000000, 8).unwrap();
+        assert_eq!(d.to_string(), "-123");
+
+        // Test leading zeros in decimal part
+        let d = VariantDecimal16::try_new(10000005, 8).unwrap();
+        assert_eq!(d.to_string(), "0.10000005");
+
+        let d = VariantDecimal16::try_new(-10000005, 8).unwrap();
+        assert_eq!(d.to_string(), "-0.10000005");
+
+        // Test number smaller than scale
+        let d = VariantDecimal16::try_new(123, 20).unwrap();
+        assert_eq!(d.to_string(), "0.00000000000000000123");
+
+        let d = VariantDecimal16::try_new(-123, 20).unwrap();
+        assert_eq!(d.to_string(), "-0.00000000000000000123");
+
+        // Test zero
+        let d = VariantDecimal16::try_new(0, 0).unwrap();
+        assert_eq!(d.to_string(), "0");
+
+        let d = VariantDecimal16::try_new(0, 20).unwrap();
+        assert_eq!(d.to_string(), "0");
+
+        // Test max scale
+        let d = 
VariantDecimal16::try_new(12345678901234567890123456789012345678_i128, 
38).unwrap();
+        assert_eq!(d.to_string(), "0.12345678901234567890123456789012345678");
+
+        let d =
+            
VariantDecimal16::try_new(-12345678901234567890123456789012345678_i128, 
38).unwrap();
+        assert_eq!(d.to_string(), "-0.12345678901234567890123456789012345678");
+
+        // Test max precision integer
+        let d = 
VariantDecimal16::try_new(99999999999999999999999999999999999999_i128, 
0).unwrap();
+        assert_eq!(d.to_string(), "99999999999999999999999999999999999999");
+
+        let d = 
VariantDecimal16::try_new(-99999999999999999999999999999999999999_i128, 
0).unwrap();
+        assert_eq!(d.to_string(), "-99999999999999999999999999999999999999");
+
+        // Test complex trailing zeros
+        let d = VariantDecimal16::try_new(12000000000000050000000000000_i128, 
25).unwrap();
+        assert_eq!(d.to_string(), "1200.000000000005");
+
+        let d = VariantDecimal16::try_new(-12000000000000050000000000000_i128, 
25).unwrap();
+        assert_eq!(d.to_string(), "-1200.000000000005");
+
+        // Test large integer that would overflow i64 but fits in i128
+        let large_int = 12345678901234567890123456789_i128;
+        let d = VariantDecimal16::try_new(large_int, 0).unwrap();
+        assert_eq!(d.to_string(), "12345678901234567890123456789");
+
+        let d = VariantDecimal16::try_new(-large_int, 0).unwrap();
+        assert_eq!(d.to_string(), "-12345678901234567890123456789");
+    }
 }
diff --git a/parquet-variant/src/variant/metadata.rs 
b/parquet-variant/src/variant/metadata.rs
index 8fff65a6ee..16b4df6f3f 100644
--- a/parquet-variant/src/variant/metadata.rs
+++ b/parquet-variant/src/variant/metadata.rs
@@ -60,8 +60,7 @@ impl VariantMetadataHeader {
         let version = header_byte & 0x0F; // First four bits
         if version != CORRECT_VERSION_VALUE {
             let err_msg = format!(
-                "The version bytes in the header is not 
{CORRECT_VERSION_VALUE}, got {:b}",
-                version
+                "The version bytes in the header is not 
{CORRECT_VERSION_VALUE}, got {version:b}",
             );
             return Err(ArrowError::InvalidArgumentError(err_msg));
         }

Reply via email to