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/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 8f7a65bf9 perf(transforms): avoid redundant decimal truncate modulus 
(#2129)
8f7a65bf9 is described below

commit 8f7a65bf968d27c520c743e9e5d95e2786d8b0d0
Author: Minh Vu <[email protected]>
AuthorDate: Tue Oct 6 19:46:22 2026 +0200

    perf(transforms): avoid redundant decimal truncate modulus (#2129)
    
    Signed-off-by: Minh Vu <[email protected]>
---
 transforms.go                    |  3 +-
 transforms_decimal_bench_test.go | 61 ++++++++++++++++++++++++++++++++++++++--
 transforms_test.go               | 52 ++++++++++++++++++++++++++++++++++
 3 files changed, 112 insertions(+), 4 deletions(-)

diff --git a/transforms.go b/transforms.go
index df0d3c363..6c66797df 100644
--- a/transforms.go
+++ b/transforms.go
@@ -784,9 +784,8 @@ func (t TruncateTransform) Transformer(src Type) (func(any) 
any, error) {
 
                        val := v.(Decimal)
                        unscaled := val.Val.BigInt()
-                       // unscaled - (((unscaled % width) + width) % width)
+                       // big.Int.Mod already gives a non-negative remainder 
for positive widths.
                        applied := (&big.Int{}).Mod(unscaled, bigWidth)
-                       applied.Add(applied, bigWidth).Mod(applied, bigWidth)
                        val.Val = decimal128.FromBigInt(unscaled.Sub(unscaled, 
applied))
 
                        return val
diff --git a/transforms_decimal_bench_test.go b/transforms_decimal_bench_test.go
index cd2a5e0f8..67c61b4f7 100644
--- a/transforms_decimal_bench_test.go
+++ b/transforms_decimal_bench_test.go
@@ -18,6 +18,7 @@
 package iceberg_test
 
 import (
+       "math"
        "testing"
 
        "github.com/apache/arrow-go/v18/arrow/decimal128"
@@ -25,8 +26,10 @@ import (
 )
 
 var (
-       benchmarkDecimalBucketLiteralResult iceberg.Optional[iceberg.Literal]
-       benchmarkDecimalBucketResult        iceberg.Optional[int32]
+       benchmarkDecimalBucketLiteralResult   iceberg.Optional[iceberg.Literal]
+       benchmarkDecimalBucketResult          iceberg.Optional[int32]
+       benchmarkDecimalTruncateLiteralResult iceberg.Optional[iceberg.Literal]
+       benchmarkDecimalTruncateResult        any
 )
 
 func BenchmarkBucketTransformDecimal(b *testing.B) {
@@ -67,3 +70,57 @@ func BenchmarkBucketTransformDecimal(b *testing.B) {
                }
        })
 }
+
+func BenchmarkTruncateTransformDecimal(b *testing.B) {
+       values := []iceberg.Decimal{
+               {Val: decimal128.FromI64(0), Scale: 2},
+               {Val: decimal128.FromI64(1065), Scale: 2},
+               {Val: decimal128.FromI64(-1065), Scale: 2},
+               {Val: decimal128.New(0x123456789abcdef, 0xfedcba9876543210), 
Scale: 2},
+               {Val: decimal128.New(-0x123456789abcdef, 0xfedcba9876543210), 
Scale: 2},
+       }
+       tests := []struct {
+               name   string
+               width  int
+               values []iceberg.Decimal
+       }{
+               {name: "zero", width: 50, values: values[:1]},
+               {name: "positive", width: 50, values: values[1:2]},
+               {name: "negative", width: 50, values: values[2:3]},
+               {name: "wide-positive", width: 50, values: values[3:4]},
+               {name: "wide-negative", width: 50, values: values[4:5]},
+               {name: "width-1", width: 1, values: values},
+               {name: "width-max", width: math.MaxInt32, values: values},
+       }
+       for _, tt := range tests {
+               transform := iceberg.TruncateTransform{Width: tt.width}
+               literals := make([]iceberg.Literal, len(tt.values))
+               boxedValues := make([]any, len(tt.values))
+               for i, value := range tt.values {
+                       literals[i] = iceberg.DecimalLiteral(value)
+                       boxedValues[i] = value
+               }
+
+               b.Run(tt.name+"/Apply", func(b *testing.B) {
+                       b.ReportAllocs()
+                       b.ResetTimer()
+                       for i := range b.N {
+                               benchmarkDecimalTruncateLiteralResult = 
transform.Apply(iceberg.Optional[iceberg.Literal]{
+                                       Valid: true,
+                                       Val:   literals[i%len(literals)],
+                               })
+                       }
+               })
+               b.Run(tt.name+"/Transformer", func(b *testing.B) {
+                       transformer, err := 
transform.Transformer(iceberg.DecimalTypeOf(38, 2))
+                       if err != nil {
+                               b.Fatal(err)
+                       }
+                       b.ReportAllocs()
+                       b.ResetTimer()
+                       for i := range b.N {
+                               benchmarkDecimalTruncateResult = 
transformer(boxedValues[i%len(boxedValues)])
+                       }
+               })
+       }
+}
diff --git a/transforms_test.go b/transforms_test.go
index b65f00244..493a53441 100644
--- a/transforms_test.go
+++ b/transforms_test.go
@@ -21,6 +21,7 @@ import (
        "bytes"
        "fmt"
        "math"
+       "math/big"
        "reflect"
        "strconv"
        "strings"
@@ -1162,6 +1163,57 @@ func TestTruncateTransform(t *testing.T) {
        }
 }
 
+func TestTruncateTransformDecimal(t *testing.T) {
+       t.Parallel()
+
+       values := []string{
+               "0", "1", "-1", "49", "-49", "50", "-50", "51", "-51",
+               "9223372036854775808", "-9223372036854775808",
+               "18446744073709551616", "-18446744073709551616",
+               "99999999999999999999999999999999999999",
+               "-99999999999999999999999999999999999999",
+       }
+       for _, width := range []int{1, 2, 3, 10, 50, 97, math.MaxInt32} {
+               transform := iceberg.TruncateTransform{Width: width}
+               for _, scale := range []int{0, 2, 9, 38} {
+                       transformer, err := 
transform.Transformer(iceberg.DecimalTypeOf(38, scale))
+                       require.NoError(t, err)
+                       for _, value := range values {
+                               t.Run(fmt.Sprintf("width=%d/scale=%d/value=%s", 
width, scale, value), func(t *testing.T) {
+                                       t.Parallel()
+
+                                       unscaled, ok := 
new(big.Int).SetString(value, 10)
+                                       require.True(t, ok)
+                                       input := iceberg.Decimal{Val: 
decimal128.FromBigInt(unscaled), Scale: scale}
+                                       divisor := big.NewInt(int64(width))
+                                       quotient := new(big.Int).Div(unscaled, 
divisor)
+                                       expected := iceberg.Decimal{
+                                               Val:   
decimal128.FromBigInt(quotient.Mul(quotient, divisor)),
+                                               Scale: scale,
+                                       }
+
+                                       assert.Equal(t, expected, 
transformer(input))
+                                       result := 
transform.Apply(iceberg.Optional[iceberg.Literal]{
+                                               Val:   
iceberg.DecimalLiteral(input),
+                                               Valid: true,
+                                       })
+                                       require.True(t, result.Valid)
+                                       assert.Equal(t, 
iceberg.DecimalLiteral(expected), result.Val)
+                                       assert.Equal(t, value, 
input.Val.BigInt().String())
+                               })
+                       }
+               }
+       }
+
+       t.Run("null", func(t *testing.T) {
+               transform := iceberg.TruncateTransform{Width: 50}
+               transformer, err := 
transform.Transformer(iceberg.DecimalTypeOf(38, 2))
+               require.NoError(t, err)
+               assert.Nil(t, transformer(nil))
+               assert.False(t, 
transform.Apply(iceberg.Optional[iceberg.Literal]{Valid: false}).Valid)
+       })
+}
+
 func TestTruncateTransformProjectStrictNumericBounds(t *testing.T) {
        tests := []struct {
                name      string

Reply via email to