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));
}