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]