davidlghellin commented on code in PR #10509:
URL: https://github.com/apache/arrow-rs/pull/10509#discussion_r3990348575


##########
arrow-cast/src/cast/mod.rs:
##########
@@ -88,7 +95,196 @@ where
     D: DecimalType,
     F: Fn(D::Native) -> f64,
 {
-    f(x) / 10_f64.powi(scale)
+    let unscaled = f(x);
+    // Fast path: below 2^53 the integer -> f64 conversion is exact, and 
10^|scale|
+    // is exactly representable up to 22, so this rounds exactly once and 
gives the
+    // double nearest to the decimal value. A negative scale has to multiply:
+    // `10^scale` is inexact there, while `10^-scale` is the exact power of 
ten.
+    if (-22..=22).contains(&scale) && unscaled.abs() < F64_EXACT_INT_LIMIT {
+        return if scale >= 0 {
+            unscaled / 10_f64.powi(scale)
+        } else {
+            unscaled * 10_f64.powi(-scale)
+        };
+    }
+    decimal_to_f64_rounded_once::<D>(x, scale, unscaled)
+}
+
+/// The out-of-line half of [`single_decimal_to_float_lossy`], kept out of the 
hot
+/// loop so the common case stays a comparison and a division.
+///
+/// `unscaled` and/or the power of ten are inexact here, so combining them 
rounds
+/// twice and can land on the wrong double. Round once instead, via a correctly
+/// rounded decimal-string parse. The precision passed to `format_decimal` only
+/// bounds how many digits are kept, and values are not guaranteed to fit the
+/// declared precision, so it must not truncate here.
+#[cold]
+#[inline(never)]
+fn decimal_to_f64_rounded_once<D: DecimalType>(x: D::Native, scale: i32, 
unscaled: f64) -> f64 {
+    D::format_decimal(x, u8::MAX, scale as i8)
+        .parse::<f64>()
+        .unwrap_or_else(|_| unscaled / 10_f64.powi(scale))
+}
+
+/// Lossy conversion from decimal to `f32`.
+///
+/// Returns the `f32` nearest to the decimal's exact value, rounding once.
+///
+/// Narrowing through [`single_decimal_to_float_lossy`] and then to `f32` 
rounds
+/// twice, and the two steps disagree with a single rounding: a decimal just
+/// above an `f32` midpoint can collapse onto that midpoint in `f64`, and
+/// round-half-even then sends it the wrong way. So this narrows directly 
rather
+/// than reusing the `f64` conversion.
+#[inline(always)]
+pub fn single_decimal_to_f32_lossy<D, F>(f: &F, x: D::Native, scale: i32) -> 
f32

Review Comment:
   Removed it entirely — once the indirection was gone it had no callers left.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to