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 5ad94933 fix: use xnor for boolean equals function (#505)
5ad94933 is described below
commit 5ad94933ed16b2b2434c32d7426c275f37fe93b4
Author: Dhruvit Maniya <[email protected]>
AuthorDate: Wed Sep 17 21:39:14 2025 +0530
fix: use xnor for boolean equals function (#505)
### Rationale for this change
fixes https://github.com/apache/arrow-go/issues/493
### What changes are included in this PR?
Add bitxnor operation, utilize bitxnor for boolean equality.
### Are these changes tested?
Yes
### Are there any user-facing changes?
Boolean equality operations now return correct results. No changes to
the public API.
---
arrow/bitutil/bitmap_ops.go | 23 ++++++++++++++++++++++
arrow/bitutil/bitmaps.go | 13 ++++++++++++
arrow/bitutil/bitmaps_test.go | 18 +++++++++++++++++
.../compute/internal/kernels/scalar_comparisons.go | 10 ++++++----
arrow/compute/scalar_compare_test.go | 15 ++++++++++++++
5 files changed, 75 insertions(+), 4 deletions(-)
diff --git a/arrow/bitutil/bitmap_ops.go b/arrow/bitutil/bitmap_ops.go
index 7db750a6..57b231f9 100644
--- a/arrow/bitutil/bitmap_ops.go
+++ b/arrow/bitutil/bitmap_ops.go
@@ -107,3 +107,26 @@ func alignedBitXorGo(left, right, out []byte) {
out[i] = left[i] ^ right[i]
}
}
+
+func alignedBitXnorGo(left, right, out []byte) {
+ var (
+ nbytes = len(out)
+ i = 0
+ )
+ if nbytes > uint64SizeBytes {
+ // case where we have enough bytes to operate on words
+ leftWords := bytesToUint64(left[i:])
+ rightWords := bytesToUint64(right[i:])
+ outWords := bytesToUint64(out[i:])
+
+ for w := range outWords {
+ outWords[w] = ^(leftWords[w] ^ rightWords[w])
+ }
+
+ i += len(outWords) * uint64SizeBytes
+ }
+ // grab any remaining bytes that were fewer than a word
+ for ; i < nbytes; i++ {
+ out[i] = ^(left[i] ^ right[i])
+ }
+}
diff --git a/arrow/bitutil/bitmaps.go b/arrow/bitutil/bitmaps.go
index c6b156e7..666b904a 100644
--- a/arrow/bitutil/bitmaps.go
+++ b/arrow/bitutil/bitmaps.go
@@ -484,6 +484,11 @@ var (
opByte: func(l, r byte) byte { return l ^ r },
opAligned: alignedBitXorGo,
}
+ bitXnorOp = bitOp{
+ opWord: func(l, r uint64) uint64 { return ^(l ^ r) },
+ opByte: func(l, r byte) byte { return ^(l ^ r) },
+ opAligned: alignedBitXnorGo,
+ }
)
func alignedBitmapOp(op bitOp, left, right []byte, lOffset, rOffset int64, out
[]byte, outOffset int64, length int64) {
@@ -592,6 +597,14 @@ func BitmapXorAlloc(mem memory.Allocator, left, right
[]byte, lOffset, rOffset i
return BitmapOpAlloc(mem, bitXorOp, left, right, lOffset, rOffset,
length, outOffset)
}
+func BitmapXnor(left, right []byte, lOffset, rOffset int64, out []byte,
outOffset int64, length int64) {
+ BitmapOp(bitXnorOp, left, right, lOffset, rOffset, out, outOffset,
length)
+}
+
+func BitmapXnorAlloc(mem memory.Allocator, left, right []byte, lOffset,
rOffset int64, length, outOffset int64) *memory.Buffer {
+ return BitmapOpAlloc(mem, bitXnorOp, left, right, lOffset, rOffset,
length, outOffset)
+}
+
func BitmapEquals(left, right []byte, lOffset, rOffset int64, length int64)
bool {
if lOffset%8 == 0 && rOffset%8 == 0 {
// byte aligned, fast path, can use bytes.Equal (memcmp)
diff --git a/arrow/bitutil/bitmaps_test.go b/arrow/bitutil/bitmaps_test.go
index a18c7c18..dd7e936a 100644
--- a/arrow/bitutil/bitmaps_test.go
+++ b/arrow/bitutil/bitmaps_test.go
@@ -517,6 +517,24 @@ func (s *BitmapOpSuite) TestBitmapOr() {
})
}
+func (s *BitmapOpSuite) TestBitmapXnor() {
+ op := bitmapOp{
+ noAlloc: bitutil.BitmapXnor,
+ alloc: bitutil.BitmapXnorAlloc,
+ }
+
+ leftBits := []int{0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1}
+ rightBits := []int{0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0}
+ resultBits := []bool{true, false, true, false, false, false, true,
false, false, true, false, false, false, false}
+
+ s.Run("aligned", func() {
+ s.testAligned(op, leftBits, rightBits, resultBits)
+ })
+ s.Run("unaligned", func() {
+ s.testUnaligned(op, leftBits, rightBits, resultBits)
+ })
+}
+
func TestBitmapOps(t *testing.T) {
suite.Run(t, new(BitmapOpSuite))
}
diff --git a/arrow/compute/internal/kernels/scalar_comparisons.go
b/arrow/compute/internal/kernels/scalar_comparisons.go
index 8cf23a2e..6d7611f7 100644
--- a/arrow/compute/internal/kernels/scalar_comparisons.go
+++ b/arrow/compute/internal/kernels/scalar_comparisons.go
@@ -36,9 +36,11 @@ import (
type binaryKernel func(left, right, out []byte, offset int)
-type cmpFn[LeftT, RightT arrow.FixedWidthType] func([]LeftT, []RightT,
[]uint32)
-type cmpScalarLeft[LeftT, RightT arrow.FixedWidthType] func(LeftT, []RightT,
[]uint32)
-type cmpScalarRight[LeftT, RightT arrow.FixedWidthType] func([]LeftT, RightT,
[]uint32)
+type (
+ cmpFn[LeftT, RightT arrow.FixedWidthType] func([]LeftT,
[]RightT, []uint32)
+ cmpScalarLeft[LeftT, RightT arrow.FixedWidthType] func(LeftT,
[]RightT, []uint32)
+ cmpScalarRight[LeftT, RightT arrow.FixedWidthType] func([]LeftT,
RightT, []uint32)
+)
type cmpOp[T arrow.FixedWidthType] struct {
arrArr cmpFn[T, T]
@@ -589,7 +591,7 @@ func compareTimestampKernel(ty exec.InputType, op
CompareOperator) exec.ScalarKe
var (
boolEQ = binaryBoolOps{
arrArr: func(_ *exec.KernelCtx, lhs, rhs, out bitutil.Bitmap)
error {
- bitutil.BitmapAnd(lhs.Data, rhs.Data, lhs.Offset,
rhs.Offset, out.Data, out.Offset, out.Len)
+ bitutil.BitmapXnor(lhs.Data, rhs.Data, lhs.Offset,
rhs.Offset, out.Data, out.Offset, out.Len)
return nil
},
arrScalar: func(_ *exec.KernelCtx, lhs bitutil.Bitmap, rhs
bool, out bitutil.Bitmap) error {
diff --git a/arrow/compute/scalar_compare_test.go
b/arrow/compute/scalar_compare_test.go
index 4b4d78bb..5a3078fc 100644
--- a/arrow/compute/scalar_compare_test.go
+++ b/arrow/compute/scalar_compare_test.go
@@ -264,6 +264,20 @@ func simpleArrArrCompare[T arrow.NumericType | string](mem
memory.Allocator, op
return compute.NewDatum(result)
}
+type BooleanCompareSuite struct {
+ CompareSuite
+}
+
+func (b *BooleanCompareSuite) TestBooleanBasics() {
+ var (
+ example1JSON = `[true, false, true, false]`
+ example2JSON = `[true, false, false, true]`
+ )
+
+ b.validateCompare(kernels.CmpEQ, arrow.FixedWidthTypes.Boolean,
example1JSON, example2JSON, `[true, true, false, false]`)
+ b.validateCompare(kernels.CmpNE, arrow.FixedWidthTypes.Boolean,
example1JSON, example2JSON, `[false, false, true, true]`)
+}
+
type NumericCompareSuite[T arrow.NumericType] struct {
CompareSuite
}
@@ -1224,6 +1238,7 @@ func (c *CompareStringSuite)
TestRandomCompareArrayArray() {
}
func TestComparisons(t *testing.T) {
+ suite.Run(t, new(BooleanCompareSuite))
suite.Run(t, new(NumericCompareSuite[int8]))
suite.Run(t, new(NumericCompareSuite[int16]))
suite.Run(t, new(NumericCompareSuite[int32]))