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 ed543050 fix(arrow/scalar): preserve exact decimal string casts (#1107)
ed543050 is described below
commit ed543050a287d66c8a6652ea6826bccf7a08d96a
Author: Minh Vu <[email protected]>
AuthorDate: Fri Aug 7 20:43:47 2026 +0200
fix(arrow/scalar): preserve exact decimal string casts (#1107)
### Rationale for this change
Decimal string casts build their scale factor through math.Pow10 and
float64. Large decimal scales can therefore lose precision even though
the decimal types already provide an exact conversion.
### What changes are included in this PR?
Use the existing exact conversion for Decimal128 and Decimal256 string
casts and add high-precision coverage for both types.
### Are these changes tested?
- `go test ./arrow/scalar`
### Are there any user-facing changes?
No API changes. This corrects the reported behavior while preserving the
existing ownership and compatibility contracts.
---
arrow/scalar/scalar.go | 9 ++-------
arrow/scalar/scalar_test.go | 18 ++++++++++++++++++
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/arrow/scalar/scalar.go b/arrow/scalar/scalar.go
index 5b9bcac1..8cb31764 100644
--- a/arrow/scalar/scalar.go
+++ b/arrow/scalar/scalar.go
@@ -21,7 +21,6 @@ import (
"errors"
"fmt"
"math"
- "math/big"
"reflect"
"strconv"
"unsafe"
@@ -329,9 +328,7 @@ func (s *Decimal128) CastTo(to arrow.DataType) (Scalar,
error) {
return NewDecimal256Scalar(newVal, to), nil
case arrow.STRING:
dt := s.Type.(*arrow.Decimal128Type)
- scale := big.NewFloat(math.Pow10(int(dt.Scale)))
- val := (&big.Float{}).SetInt(s.Value.BigInt())
- return NewStringScalar(val.Quo(val, scale).Text('g',
int(dt.Precision))), nil
+ return NewStringScalar(s.Value.ToBigFloat(dt.Scale).Text('g',
int(dt.Precision))), nil
}
return nil, fmt.Errorf("cannot cast non-nil decimal128 scalar to type
%s", to)
@@ -386,9 +383,7 @@ func (s *Decimal256) CastTo(to arrow.DataType) (Scalar,
error) {
}
return NewDecimal256Scalar(newVal, to), nil
case arrow.STRING:
- scale := big.NewFloat(math.Pow10(int(dt.Scale)))
- val := (&big.Float{}).SetInt(s.Value.BigInt())
- return NewStringScalar(val.Quo(val, scale).Text('g',
int(dt.Precision))), nil
+ return NewStringScalar(s.Value.ToBigFloat(dt.Scale).Text('g',
int(dt.Precision))), nil
}
return nil, fmt.Errorf("cannot cast non-nil decimal256 scalar to type
%s", to)
diff --git a/arrow/scalar/scalar_test.go b/arrow/scalar/scalar_test.go
index 16366a7d..efb5088a 100644
--- a/arrow/scalar/scalar_test.go
+++ b/arrow/scalar/scalar_test.go
@@ -163,6 +163,24 @@ func TestBasicDecimal256(t *testing.T) {
assert.False(t, scalar.Equals(pi, pi2))
}
+func TestDecimalScalarStringCastUsesExactScaling(t *testing.T) {
+ decimal128Type := &arrow.Decimal128Type{Precision: 38, Scale: 20}
+ decimal128Value, err :=
decimal128.FromString("-123456789012345678.90123456789012345678",
decimal128Type.Precision, decimal128Type.Scale)
+ require.NoError(t, err)
+ decimal128Scalar := scalar.NewDecimal128Scalar(decimal128Value,
decimal128Type)
+ cast128, err := decimal128Scalar.CastTo(arrow.BinaryTypes.String)
+ require.NoError(t, err)
+ assert.Equal(t, "-123456789012345678.90123456789012345678",
cast128.String())
+
+ decimal256Type := &arrow.Decimal256Type{Precision: 76, Scale: 76}
+ decimal256Value, err :=
decimal256.FromString("-0.1234567812345678123456781234567812345678123456781234567812345678123456781234",
decimal256Type.Precision, decimal256Type.Scale)
+ require.NoError(t, err)
+ decimal256Scalar := scalar.NewDecimal256Scalar(decimal256Value,
decimal256Type)
+ cast256, err := decimal256Scalar.CastTo(arrow.BinaryTypes.String)
+ require.NoError(t, err)
+ assert.Equal(t,
"-0.1234567812345678123456781234567812345678123456781234567812345678123456781234",
cast256.String())
+}
+
func TestBinaryScalarBasics(t *testing.T) {
data := "test data"
buf := memory.NewBufferBytes([]byte(data))