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

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


The following commit(s) were added to refs/heads/main by this push:
     new d577b68a fix(arrow/decimal): widen Decimal64 rescale to 64 bits (#1307)
d577b68a is described below

commit d577b68a4f0330b4c9c42889023736f72883ff91
Author: Madan Kumar <[email protected]>
AuthorDate: Wed Sep 16 00:28:26 2026 +0530

    fix(arrow/decimal): widen Decimal64 rescale to 64 bits (#1307)
    
    ### Rationale for this change
    
    `Decimal64.rescaleWouldCauseDataLoss` (arrow/decimal/decimal.go) is a
    copy of the `Decimal32` version that was never widened to 64 bits — it
    still uses `bits.Div32` / `bits.Mul32` with `uint32(n)` /
    `uint32(multiplier)`. Any `Decimal64` whose magnitude exceeds `uint32`
    max (~4.29e9, well inside Decimal64's 18-digit range) is truncated to 32
    bits before the multiply/divide, so `Rescale` returns a corrupt value
    and/or a spurious `rescale data loss` error.
    
    Concretely, on `main`:
    
    ```go
    decimal.Decimal64(10_000_000_000).Rescale(2, 1)
    // returns (141006540, error "rescale data loss")
    // correct: (1000000000, nil)   // 1e10 / 10 = 1e9, exact
    ```
    
    ### What changes are included in this PR?
    
    - Use `bits.Div64` / `bits.Mul64` over `uint64` in
    `Decimal64.rescaleWouldCauseDataLoss` (the `Decimal32` version is
    already correct for 32 bits).
    - `TestDecimalRescale` had baked in the truncated behavior: it asserted
    that `Decimal64(555555).Rescale(0, 5)` returns `rescale data loss`, but
    `555555` at scale 5 is `55,555,500,000`, which fits in `Decimal64` and
    is lossless. Corrected that assertion, checked the exact result, and
    added a value that genuinely overflows `Decimal64` (`5e18` rescaled up)
    so the real data-loss path stays covered.
    
    RED→GREEN verified: the updated `TestDecimalRescale` fails on the
    unpatched code and passes with the fix; the full `arrow/decimal` package
    suite is green; `gofmt`/`go vet`/`golangci-lint` clean.
    
    ### Are these changes tested?
    
    Yes — `arrow/decimal/decimal_test.go`.
    
    ### Are there any user-facing changes?
    
    `Decimal64.Rescale` now returns correct results (and no spurious error)
    for values above ~4.29e9. No API change.
    
    Signed-off-by: Madan Kumar <[email protected]>
---
 arrow/decimal/decimal.go      | 4 ++--
 arrow/decimal/decimal_test.go | 9 ++++++++-
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/arrow/decimal/decimal.go b/arrow/decimal/decimal.go
index 1a1f00da..4d6563cc 100644
--- a/arrow/decimal/decimal.go
+++ b/arrow/decimal/decimal.go
@@ -419,11 +419,11 @@ func (n Decimal32) rescaleWouldCauseDataLoss(deltaScale 
int32, multiplier Decima
 func (n Decimal64) rescaleWouldCauseDataLoss(deltaScale int32, multiplier 
Decimal64) (out Decimal64, loss bool) {
        if deltaScale < 0 {
                debug.Assert(multiplier != 0, "multiplier must not be zero")
-               quo, remainder := bits.Div32(0, uint32(n), uint32(multiplier))
+               quo, remainder := bits.Div64(0, uint64(n), uint64(multiplier))
                return Decimal64(quo), remainder != 0
        }
 
-       overflow, result := bits.Mul32(uint32(n), uint32(multiplier))
+       overflow, result := bits.Mul64(uint64(n), uint64(multiplier))
        if overflow != 0 {
                return Decimal64(result), true
        }
diff --git a/arrow/decimal/decimal_test.go b/arrow/decimal/decimal_test.go
index 9b4f04f5..55635762 100644
--- a/arrow/decimal/decimal_test.go
+++ b/arrow/decimal/decimal_test.go
@@ -386,7 +386,14 @@ func TestDecimalRescale(t *testing.T) {
 
        _, err = decimal.Decimal32(555555).Rescale(0, 5)
        assert.ErrorContains(t, err, "rescale data loss")
-       _, err = decimal.Decimal64(555555).Rescale(0, 5)
+       // 555555 at scale 5 is 55,555,500,000, which fits comfortably in
+       // Decimal64's range, so the rescale is lossless. (The 32-bit path 
wrongly
+       // reported data loss by truncating the value to uint32 before 
multiplying.)
+       out64, err := decimal.Decimal64(555555).Rescale(0, 5)
+       assert.NoError(t, err)
+       assert.Equal(t, decimal.Decimal64(55_555_500_000), out64)
+       // A value that genuinely overflows Decimal64 still reports data loss.
+       _, err = decimal.Decimal64(5_000_000_000_000_000_000).Rescale(0, 1)
        assert.ErrorContains(t, err, "rescale data loss")
 }
 

Reply via email to