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 <61140756+dhruvi...@users.noreply.github.com> 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]))