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 ae27070e71 [Variant] Align cast logic for from/to_decimal for variant 
(#9689)
ae27070e71 is described below

commit ae27070e71ececd9daefccc99b92394f361c87d4
Author: Congxian Qiu <[email protected]>
AuthorDate: Tue May 19 02:55:20 2026 +0800

    [Variant] Align cast logic for from/to_decimal for variant (#9689)
    
    # Which issue does this PR close?
    
    <!--
    We generally require a GitHub issue to be filed for all bug fixes and
    enhancements and this helps us generate change logs for our releases.
    You can link an issue to this PR using the GitHub syntax.
    -->
    
    - Closes #9688 .
    
    
    # What changes are included in this PR?
    
    <!--
    There is no need to duplicate the description in the issue here but it
    is sometimes worth providing a summary of the individual changes in this
    PR.
    -->
    - Extract some logic in arrow-cast
    - Reuse the extracted logic in arrow-cast and parquet-variant
    
    # Are these changes tested?
    Reuse the existing tests in arrow-test
    
    # Are there any user-facing changes?
    
    Yes, changed the docs
    <!--
    If there are user-facing changes then we may require documentation to be
    updated before approving the PR.
    
    If there are any breaking changes to public APIs, please call them out.
    -->
---
 arrow-cast/src/cast/decimal.rs                 |  17 +-
 arrow-cast/src/cast/mod.rs                     |  23 ++-
 parquet-variant-compute/src/type_conversion.rs |  20 ++-
 parquet-variant/src/variant.rs                 | 231 +++++++++++++++++++------
 4 files changed, 230 insertions(+), 61 deletions(-)

diff --git a/arrow-cast/src/cast/decimal.rs b/arrow-cast/src/cast/decimal.rs
index 3553f2b6a7..789dcea89e 100644
--- a/arrow-cast/src/cast/decimal.rs
+++ b/arrow-cast/src/cast/decimal.rs
@@ -531,7 +531,7 @@ where
 
 /// Parses given string to specified decimal native (i128/i256) based on given
 /// scale. Returns an `Err` if it cannot parse given string.
-pub(crate) fn parse_string_to_decimal_native<T: DecimalType>(
+pub fn parse_string_to_decimal_native<T: DecimalType>(
     value_str: &str,
     scale: usize,
 ) -> Result<T::Native, ArrowError>
@@ -777,7 +777,7 @@ where
     if cast_options.safe {
         array
             .unary_opt::<_, D>(|v| {
-                D::Native::from_f64((mul * v.as_()).round())
+                single_float_to_decimal::<D>(v.as_(), mul)
                     .filter(|v| D::is_valid_decimal_precision(*v, precision))
             })
             .with_precision_and_scale(precision, scale)
@@ -785,7 +785,7 @@ where
     } else {
         array
             .try_unary::<_, D, _>(|v| {
-                D::Native::from_f64((mul * v.as_()).round())
+                single_float_to_decimal::<D>(v.as_(), mul)
                     .ok_or_else(|| {
                         ArrowError::CastError(format!(
                             "Cannot cast to {}({}, {}). Overflowing on {:?}",
@@ -802,6 +802,17 @@ where
     }
 }
 
+/// Cast a single floating point value to a decimal native with the given 
multiple.
+/// Returns `None` if the value cannot be represented with the requested 
precision.
+#[inline(always)]
+pub fn single_float_to_decimal<D>(input: f64, mul: f64) -> Option<D::Native>
+where
+    D: DecimalType + ArrowPrimitiveType,
+    <D as ArrowPrimitiveType>::Native: DecimalCast,
+{
+    D::Native::from_f64((mul * input).round())
+}
+
 pub(crate) fn cast_decimal_to_integer<D, T>(
     array: &dyn Array,
     base: D::Native,
diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs
index 2d9923c9a2..e100b22d72 100644
--- a/arrow-cast/src/cast/mod.rs
+++ b/arrow-cast/src/cast/mod.rs
@@ -72,9 +72,25 @@ use arrow_schema::*;
 use arrow_select::take::take;
 use num_traits::{NumCast, ToPrimitive, cast::AsPrimitive};
 
-pub use decimal::{DecimalCast, rescale_decimal};
+pub use decimal::{
+    DecimalCast, parse_string_to_decimal_native, rescale_decimal, 
single_float_to_decimal,
+};
 pub use string::cast_single_string_to_boolean_default;
 
+/// Lossy conversion from decimal to float.
+///
+/// Conversion is lossy and follows standard floating point semantics. Values
+/// that exceed the representable range become `INFINITY` or `-INFINITY` 
without
+/// returning an error.
+#[inline(always)]
+pub fn single_decimal_to_float_lossy<D, F>(f: &F, x: D::Native, scale: i32) -> 
f64
+where
+    D: DecimalType,
+    F: Fn(D::Native) -> f64,
+{
+    f(x) / 10_f64.powi(scale)
+}
+
 /// CastOptions provides a way to override the default cast behaviors
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct CastOptions<'a> {
@@ -2314,10 +2330,11 @@ where
         Int32 => cast_decimal_to_integer::<D, Int32Type>(array, base, *scale, 
cast_options),
         Int64 => cast_decimal_to_integer::<D, Int64Type>(array, base, *scale, 
cast_options),
         Float32 => cast_decimal_to_float::<D, Float32Type, _>(array, |x| {
-            (as_float(x) / 10_f64.powi(*scale as i32)) as f32
+            single_decimal_to_float_lossy::<D, F>(&as_float, x, <i32 as 
From<i8>>::from(*scale))
+                as f32
         }),
         Float64 => cast_decimal_to_float::<D, Float64Type, _>(array, |x| {
-            as_float(x) / 10_f64.powi(*scale as i32)
+            single_decimal_to_float_lossy::<D, F>(&as_float, x, <i32 as 
From<i8>>::from(*scale))
         }),
         Utf8View => value_to_string_view(array, cast_options),
         Utf8 => value_to_string::<i32>(array, cast_options),
diff --git a/parquet-variant-compute/src/type_conversion.rs 
b/parquet-variant-compute/src/type_conversion.rs
index 7b9eb67d1a..2255d4316b 100644
--- a/parquet-variant-compute/src/type_conversion.rs
+++ b/parquet-variant-compute/src/type_conversion.rs
@@ -17,7 +17,10 @@
 
 //! Module for transforming a typed arrow `Array` to `VariantArray`.
 
-use arrow::compute::{CastOptions, DecimalCast, rescale_decimal};
+use arrow::compute::{
+    CastOptions, DecimalCast, parse_string_to_decimal_native, rescale_decimal,
+    single_float_to_decimal,
+};
 use arrow::datatypes::{
     self, ArrowPrimitiveType, ArrowTimestampType, Decimal32Type, 
Decimal64Type, Decimal128Type,
     DecimalType,
@@ -204,9 +207,12 @@ impl_timestamp_from_variant!(
 ///
 /// - `precision` and `scale` specify the target Arrow decimal parameters
 /// - Integer variants (`Int8/16/32/64`) are treated as decimals with scale 0
+/// - Floating point variants (`Float/Double`) are converted to decimals with 
the given scale
+/// - String variants (`String/ShortString`) are parsed as decimals with the 
given scale
 /// - Decimal variants (`Decimal4/8/16`) use their embedded precision and scale
 ///
-/// The value is rescaled to (`precision`, `scale`) using `rescale_decimal` and
+/// The value is rescaled to (`precision`, `scale`) using `rescale_decimal` 
for integers,
+/// `single_float_to_decimal` for floats, and `parse_string_to_decimal_native` 
for strings.
 /// returns `None` if it cannot fit the requested precision.
 pub(crate) fn variant_to_unscaled_decimal<O>(
     variant: &Variant<'_, '_>,
@@ -217,6 +223,8 @@ where
     O: DecimalType,
     O::Native: DecimalCast,
 {
+    let mul = 10_f64.powi(scale as i32);
+
     match variant {
         Variant::Int8(i) => rescale_decimal::<Decimal32Type, O>(
             *i as i32,
@@ -246,6 +254,14 @@ where
             precision,
             scale,
         ),
+        Variant::Float(f) => single_float_to_decimal::<O>(f64::from(*f), mul),
+        Variant::Double(f) => single_float_to_decimal::<O>(*f, mul),
+        // arrow-cast only support cast string to decimal with scale >=0 for 
now
+        // Please see `cast_string_to_decimal` in 
arrow-cast/src/cast/decimal.rs for more detail
+        Variant::String(v) if scale >= 0 => 
parse_string_to_decimal_native::<O>(v, scale as _).ok(),
+        Variant::ShortString(v) if scale >= 0 => {
+            parse_string_to_decimal_native::<O>(v, scale as _).ok()
+        }
         Variant::Decimal4(d) => rescale_decimal::<Decimal32Type, O>(
             d.integer(),
             VariantDecimal4::MAX_PRECISION,
diff --git a/parquet-variant/src/variant.rs b/parquet-variant/src/variant.rs
index accff00904..3b5a04ddfd 100644
--- a/parquet-variant/src/variant.rs
+++ b/parquet-variant/src/variant.rs
@@ -29,9 +29,14 @@ use crate::decoder::{
 };
 use crate::path::{VariantPath, VariantPathElement};
 use crate::utils::{first_byte_from_slice, slice_from_slice};
+use arrow::array::ArrowNativeTypeOp;
 use arrow::compute::{
-    cast_num_to_bool, cast_single_string_to_boolean_default, num_cast, 
single_bool_to_numeric,
+    DecimalCast, cast_num_to_bool, cast_single_string_to_boolean_default, 
num_cast,
+    parse_string_to_decimal_native, single_bool_to_numeric, 
single_decimal_to_float_lossy,
+    single_float_to_decimal,
 };
+use arrow::datatypes::{Decimal32Type, Decimal64Type, Decimal128Type, 
DecimalType};
+
 use arrow_schema::ArrowError;
 use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
 use num_traits::NumCast;
@@ -166,10 +171,11 @@ impl Deref for ShortString<'_> {
 ///   Arrow UTF8-to-boolean cast rules.
 /// - Numeric accessors such as [`Self::as_int8`], [`Self::as_int64`], 
[`Self::as_u8`],
 ///   [`Self::as_u64`], [`Self::as_f16`], [`Self::as_f32`], and 
[`Self::as_f64`] accept
-///   boolean and numeric variants (integers, floating-point, and decimals 
with scale `0`).
+///   boolean and numeric variants (integers, floating-point, and decimals).
 ///   They return `None` when conversion is not possible.
 /// - Decimal accessors such as [`Self::as_decimal4`], [`Self::as_decimal8`], 
and
-///   [`Self::as_decimal16`] accept compatible decimal variants and integer 
variants.
+///   [`Self::as_decimal16`] accept compatible decimal variants, integer 
variants,
+///   float variants and string variants.
 ///   They return `None` when conversion is not possible.
 ///
 /// # Examples:
@@ -294,6 +300,35 @@ pub enum Variant<'m, 'v> {
 // We don't want this to grow because it could hurt performance of a 
frequently-created type.
 const _: () = crate::utils::expect_size_of::<Variant>(80);
 
+enum NumericKind {
+    Integer,
+    Float,
+}
+
+trait DecimalCastTarget: NumCast + Default {
+    const KIND: NumericKind;
+}
+
+macro_rules! impl_decimal_cast_target {
+    ($raw_type: ident, $target_kind:expr) => {
+        impl DecimalCastTarget for $raw_type {
+            const KIND: NumericKind = $target_kind;
+        }
+    };
+}
+
+impl_decimal_cast_target!(i8, NumericKind::Integer);
+impl_decimal_cast_target!(i16, NumericKind::Integer);
+impl_decimal_cast_target!(i32, NumericKind::Integer);
+impl_decimal_cast_target!(i64, NumericKind::Integer);
+impl_decimal_cast_target!(u8, NumericKind::Integer);
+impl_decimal_cast_target!(u16, NumericKind::Integer);
+impl_decimal_cast_target!(u32, NumericKind::Integer);
+impl_decimal_cast_target!(u64, NumericKind::Integer);
+impl_decimal_cast_target!(f16, NumericKind::Float);
+impl_decimal_cast_target!(f32, NumericKind::Float);
+impl_decimal_cast_target!(f64, NumericKind::Float);
+
 impl<'m, 'v> Variant<'m, 'v> {
     /// Attempts to interpret a metadata and value buffer pair as a new 
`Variant`.
     ///
@@ -797,14 +832,37 @@ impl<'m, 'v> Variant<'m, 'v> {
         }
     }
 
-    /// Converts a boolean or numeric variant(integers, floating-point, and 
decimals with scale 0)
+    fn cast_decimal_to_num<D, T, F>(raw: D::Native, scale: u8, as_float: F) -> 
Option<T>
+    where
+        D: DecimalType,
+        D::Native: NumCast + ArrowNativeTypeOp,
+        T: DecimalCastTarget,
+        F: Fn(D::Native) -> f64,
+    {
+        let base: D::Native = NumCast::from(10)?;
+
+        let div = base.pow_checked(<u32 as From<u8>>::from(scale)).ok()?;
+        match T::KIND {
+            NumericKind::Integer => raw
+                .div_checked(div)
+                .ok()
+                .and_then(<T as NumCast>::from::<D::Native>),
+            NumericKind::Float => T::from(single_decimal_to_float_lossy::<D, 
_>(
+                &as_float,
+                raw,
+                <i32 as From<u8>>::from(scale),
+            )),
+        }
+    }
+
+    /// Converts a boolean or numeric variant(integers, floating-point, and 
decimals)
     /// to the specified numeric type `T`.
     ///
     /// Uses Arrow's casting logic to perform the conversion. Returns 
`Some(T)` if
     /// the conversion succeeds, `None` if the variant can't be casted to type 
`T`.
     fn as_num<T>(&self) -> Option<T>
     where
-        T: NumCast + Default,
+        T: DecimalCastTarget,
     {
         match *self {
             Variant::BooleanFalse => single_bool_to_numeric(false),
@@ -815,9 +873,21 @@ impl<'m, 'v> Variant<'m, 'v> {
             Variant::Int64(i) => num_cast(i),
             Variant::Float(f) => num_cast(f),
             Variant::Double(d) => num_cast(d),
-            Variant::Decimal4(d) if d.scale() == 0 => num_cast(d.integer()),
-            Variant::Decimal8(d) if d.scale() == 0 => num_cast(d.integer()),
-            Variant::Decimal16(d) if d.scale() == 0 => num_cast(d.integer()),
+            Variant::Decimal4(d) => {
+                Self::cast_decimal_to_num::<Decimal32Type, T, _>(d.integer(), 
d.scale(), |x| {
+                    x as f64
+                })
+            }
+            Variant::Decimal8(d) => {
+                Self::cast_decimal_to_num::<Decimal64Type, T, _>(d.integer(), 
d.scale(), |x| {
+                    x as f64
+                })
+            }
+            Variant::Decimal16(d) => {
+                Self::cast_decimal_to_num::<Decimal128Type, T, _>(d.integer(), 
d.scale(), |x| {
+                    x as f64
+                })
+            }
             _ => None,
         }
     }
@@ -962,17 +1032,17 @@ impl<'m, 'v> Variant<'m, 'v> {
     ///  let v2 = Variant::from(d);
     ///  assert_eq!(v2.as_u8(), Some(26u8));
     ///
+    ///  // or a variant that decimal with scale not equal to zero
+    ///  let d = VariantDecimal4::try_new(123, 2).unwrap();
+    ///  let v3 = Variant::from(d);
+    ///  assert_eq!(v3.as_u8(), Some(1));
+    ///
     /// // or from boolean variant
-    /// let v3 = Variant::BooleanFalse;
-    /// assert_eq!(v3.as_u8(), Some(0));
+    /// let v4 = Variant::BooleanFalse;
+    /// assert_eq!(v4.as_u8(), Some(0));
     ///
     ///  // but not a variant that can't fit into the range
-    ///  let v4 = Variant::from(-1);
-    ///  assert_eq!(v4.as_u8(), None);
-    ///
-    ///  // not a variant that decimal with scale not equal to zero
-    ///  let d = VariantDecimal4::try_new(1, 2).unwrap();
-    ///  let v5 = Variant::from(d);
+    ///  let v5 = Variant::from(-1);
     ///  assert_eq!(v5.as_u8(), None);
     ///
     ///  // or not a variant that cannot be cast into an integer
@@ -1003,17 +1073,17 @@ impl<'m, 'v> Variant<'m, 'v> {
     ///  let v2 = Variant::from(d);
     ///  assert_eq!(v2.as_u16(), Some(u16::MAX));
     ///
+    ///  // or a variant that decimal with scale not equal to zero
+    ///  let d = VariantDecimal4::try_new(123, 2).unwrap();
+    ///  let v3 = Variant::from(d);
+    ///  assert_eq!(v3.as_u16(), Some(1));
+    ///
     /// // or from boolean variant
-    /// let v3= Variant::BooleanFalse;
-    /// assert_eq!(v3.as_u16(), Some(0));
+    /// let v4= Variant::BooleanFalse;
+    /// assert_eq!(v4.as_u16(), Some(0));
     ///
     ///  // but not a variant that can't fit into the range
-    ///  let v4 = Variant::from(-1);
-    ///  assert_eq!(v4.as_u16(), None);
-    ///
-    ///  // not a variant that decimal with scale not equal to zero
-    ///  let d = VariantDecimal4::try_new(1, 2).unwrap();
-    ///  let v5 = Variant::from(d);
+    ///  let v5 = Variant::from(-1);
     ///  assert_eq!(v5.as_u16(), None);
     ///
     ///  // or not a variant that cannot be cast into an integer
@@ -1044,17 +1114,17 @@ impl<'m, 'v> Variant<'m, 'v> {
     ///  let v2 = Variant::from(d);
     ///  assert_eq!(v2.as_u32(), Some(u32::MAX));
     ///
+    ///  // or a variant that decimal with scale not equal to zero
+    ///  let d = VariantDecimal8::try_new(123, 2).unwrap();
+    ///  let v3 = Variant::from(d);
+    ///  assert_eq!(v3.as_u32(), Some(1));
+    ///
     /// // or from boolean variant
-    /// let v3 = Variant::BooleanFalse;
-    /// assert_eq!(v3.as_u32(), Some(0));
+    /// let v4 = Variant::BooleanFalse;
+    /// assert_eq!(v4.as_u32(), Some(0));
     ///
     ///  // but not a variant that can't fit into the range
-    ///  let v4 = Variant::from(-1);
-    ///  assert_eq!(v4.as_u32(), None);
-    ///
-    ///  // not a variant that decimal with scale not equal to zero
-    ///  let d = VariantDecimal8::try_new(1, 2).unwrap();
-    ///  let v5 = Variant::from(d);
+    ///  let v5 = Variant::from(-1);
     ///  assert_eq!(v5.as_u32(), None);
     ///
     ///  // or not a variant that cannot be cast into an integer
@@ -1085,17 +1155,17 @@ impl<'m, 'v> Variant<'m, 'v> {
     ///  let v2 = Variant::from(d);
     ///  assert_eq!(v2.as_u64(), Some(u64::MAX));
     ///
+    ///  // or a variant that decimal with scale not equal to zero
+    /// let d = VariantDecimal16::try_new(123, 2).unwrap();
+    ///  let v3 = Variant::from(d);
+    ///  assert_eq!(v3.as_u64(), Some(1));
+    ///
     /// // or from boolean variant
-    /// let v3 = Variant::BooleanFalse;
-    /// assert_eq!(v3.as_u64(), Some(0));
+    /// let v4 = Variant::BooleanFalse;
+    /// assert_eq!(v4.as_u64(), Some(0));
     ///
     ///  // but not a variant that can't fit into the range
-    ///  let v4 = Variant::from(-1);
-    ///  assert_eq!(v4.as_u64(), None);
-    ///
-    ///  // not a variant that decimal with scale not equal to zero
-    /// let d = VariantDecimal16::try_new(1, 2).unwrap();
-    ///  let v5 = Variant::from(d);
+    ///  let v5 = Variant::from(-1);
     ///  assert_eq!(v5.as_u64(), None);
     ///
     ///  // or not a variant that cannot be cast into an integer
@@ -1106,6 +1176,21 @@ impl<'m, 'v> Variant<'m, 'v> {
         self.as_num()
     }
 
+    fn convert_string_to_decimal<D, VD>(input: &str) -> Option<VD>
+    where
+        D: DecimalType,
+        VD: VariantDecimalType<Native = D::Native>,
+        D::Native: NumCast + DecimalCast,
+    {
+        // find the last '.'
+        let scale_usize = input.rsplit_once('.').map_or(0, |(_, frac)| 
frac.len());
+
+        let scale = u8::try_from(scale_usize).ok()?;
+
+        let raw = parse_string_to_decimal_native::<D>(input, 
scale_usize).ok()?;
+        VD::try_new(raw, scale).ok()
+    }
+
     /// Converts this variant to tuple with a 4-byte unscaled value if 
possible.
     ///
     /// Returns `Some((i32, u8))` for decimal variants where the unscaled value
@@ -1125,19 +1210,31 @@ impl<'m, 'v> Variant<'m, 'v> {
     /// let v2 = Variant::from(VariantDecimal8::try_new(1234_i64, 2).unwrap());
     /// assert_eq!(v2.as_decimal4(), VariantDecimal4::try_new(1234_i32, 
2).ok());
     ///
+    /// // or from string variants if they can be parsed as decimals
+    /// let v3 = Variant::from("123.45");
+    /// assert_eq!(v3.as_decimal4(), VariantDecimal4::try_new(12345, 2).ok());
+    ///
     /// // but not if the value would overflow i32
-    /// let v3 = Variant::from(VariantDecimal8::try_new(12345678901i64, 
2).unwrap());
-    /// assert_eq!(v3.as_decimal4(), None);
+    /// let v4 = Variant::from(VariantDecimal8::try_new(12345678901i64, 
2).unwrap());
+    /// assert_eq!(v4.as_decimal4(), None);
     ///
     /// // or if the variant is not a decimal
-    /// let v4 = Variant::from("hello!");
-    /// assert_eq!(v4.as_decimal4(), None);
+    /// let v5 = Variant::from("hello!");
+    /// assert_eq!(v5.as_decimal4(), None);
     /// ```
     pub fn as_decimal4(&self) -> Option<VariantDecimal4> {
         match *self {
             Variant::Int8(_) | Variant::Int16(_) | Variant::Int32(_) | 
Variant::Int64(_) => {
                 self.as_num::<i32>().and_then(|x| x.try_into().ok())
             }
+            Variant::Float(f) => single_float_to_decimal::<Decimal32Type>(f as 
_, 1f64)
+                .and_then(|x: i32| x.try_into().ok()),
+            Variant::Double(f) => single_float_to_decimal::<Decimal32Type>(f, 
1f64)
+                .and_then(|x: i32| x.try_into().ok()),
+            Variant::String(v) => 
Self::convert_string_to_decimal::<Decimal32Type, _>(v),
+            Variant::ShortString(v) => {
+                Self::convert_string_to_decimal::<Decimal32Type, _>(v.as_str())
+            }
             Variant::Decimal4(decimal4) => Some(decimal4),
             Variant::Decimal8(decimal8) => decimal8.try_into().ok(),
             Variant::Decimal16(decimal16) => decimal16.try_into().ok(),
@@ -1148,7 +1245,7 @@ impl<'m, 'v> Variant<'m, 'v> {
     /// Converts this variant to tuple with an 8-byte unscaled value if 
possible.
     ///
     /// Returns `Some((i64, u8))` for decimal variants where the unscaled value
-    /// fits in `i64` range,
+    /// fits in `i64` range, the scale will be 0 if the input is string 
variants.
     /// `None` for non-decimal variants or decimal values that would overflow.
     ///
     /// # Examples
@@ -1164,19 +1261,31 @@ impl<'m, 'v> Variant<'m, 'v> {
     /// let v2 = Variant::from(VariantDecimal16::try_new(1234_i128, 
2).unwrap());
     /// assert_eq!(v2.as_decimal8(), VariantDecimal8::try_new(1234_i64, 
2).ok());
     ///
+    /// // or from string variants if they can be parsed as decimals
+    /// let v3 = Variant::from("123.45");
+    /// assert_eq!(v3.as_decimal8(), VariantDecimal8::try_new(12345, 2).ok());
+    ///
     /// // but not if the value would overflow i64
-    /// let v3 = Variant::from(VariantDecimal16::try_new(2e19 as i128, 
2).unwrap());
-    /// assert_eq!(v3.as_decimal8(), None);
+    /// let v4 = Variant::from(VariantDecimal16::try_new(2e19 as i128, 
2).unwrap());
+    /// assert_eq!(v4.as_decimal8(), None);
     ///
     /// // or if the variant is not a decimal
-    /// let v4 = Variant::from("hello!");
-    /// assert_eq!(v4.as_decimal8(), None);
+    /// let v5 = Variant::from("hello!");
+    /// assert_eq!(v5.as_decimal8(), None);
     /// ```
     pub fn as_decimal8(&self) -> Option<VariantDecimal8> {
         match *self {
             Variant::Int8(_) | Variant::Int16(_) | Variant::Int32(_) | 
Variant::Int64(_) => {
                 self.as_num::<i64>().and_then(|x| x.try_into().ok())
             }
+            Variant::Float(f) => single_float_to_decimal::<Decimal64Type>(f as 
_, 1f64)
+                .and_then(|x: i64| x.try_into().ok()),
+            Variant::Double(f) => single_float_to_decimal::<Decimal64Type>(f, 
1f64)
+                .and_then(|x: i64| x.try_into().ok()),
+            Variant::String(v) => 
Self::convert_string_to_decimal::<Decimal64Type, _>(v),
+            Variant::ShortString(v) => {
+                Self::convert_string_to_decimal::<Decimal64Type, _>(v.as_str())
+            }
             Variant::Decimal4(decimal4) => Some(decimal4.into()),
             Variant::Decimal8(decimal8) => Some(decimal8),
             Variant::Decimal16(decimal16) => decimal16.try_into().ok(),
@@ -1187,7 +1296,7 @@ impl<'m, 'v> Variant<'m, 'v> {
     /// Converts this variant to tuple with a 16-byte unscaled value if 
possible.
     ///
     /// Returns `Some((i128, u8))` for decimal variants where the unscaled 
value
-    /// fits in `i128` range,
+    /// fits in `i128` range, the scale will be 0 if the input is string 
variants.
     /// `None` for non-decimal variants or decimal values that would overflow.
     ///
     /// # Examples
@@ -1199,14 +1308,30 @@ impl<'m, 'v> Variant<'m, 'v> {
     /// let v1 = Variant::from(VariantDecimal4::try_new(1234_i32, 2).unwrap());
     /// assert_eq!(v1.as_decimal16(), VariantDecimal16::try_new(1234_i128, 
2).ok());
     ///
+    /// // or from a string variant if it can be parsed as decimal
+    /// let v2 = Variant::from("123.45");
+    /// assert_eq!(v2.as_decimal16(), VariantDecimal16::try_new(12345, 
2).ok());
+    ///
     /// // but not if the variant is not a decimal
-    /// let v2 = Variant::from("hello!");
-    /// assert_eq!(v2.as_decimal16(), None);
+    /// let v3 = Variant::from("hello!");
+    /// assert_eq!(v3.as_decimal16(), None);
     /// ```
     pub fn as_decimal16(&self) -> Option<VariantDecimal16> {
         match *self {
             Variant::Int8(_) | Variant::Int16(_) | Variant::Int32(_) | 
Variant::Int64(_) => {
-                self.as_num::<i128>().and_then(|x| x.try_into().ok())
+                let x = self.as_num::<i64>()?;
+                <i128 as From<i64>>::from(x).try_into().ok()
+            }
+            Variant::Float(f) => {
+                single_float_to_decimal::<Decimal128Type>(<f64 as 
From<f32>>::from(f), 1f64)
+                    .and_then(|x| x.try_into().ok())
+            }
+            Variant::Double(f) => {
+                single_float_to_decimal::<Decimal128Type>(f, 
1f64).and_then(|x| x.try_into().ok())
+            }
+            Variant::String(v) => 
Self::convert_string_to_decimal::<Decimal128Type, _>(v),
+            Variant::ShortString(v) => {
+                Self::convert_string_to_decimal::<Decimal128Type, 
_>(v.as_str())
             }
             Variant::Decimal4(decimal4) => Some(decimal4.into()),
             Variant::Decimal8(decimal8) => Some(decimal8.into()),

Reply via email to