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

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


The following commit(s) were added to refs/heads/main by this push:
     new 08a64ff4 fix: ArrowDecimalSetDigits() on s390x + test fixes for big 
endian (#732)
08a64ff4 is described below

commit 08a64ff44fce354dfca9c011443dbe555f3a8715
Author: Dewey Dunnington <[email protected]>
AuthorDate: Mon Mar 24 09:20:28 2025 -0500

    fix: ArrowDecimalSetDigits() on s390x + test fixes for big endian (#732)
    
    This PR fixes Decimal32 and Decimal64 handling on big endian. The
    previous code used a cast on the least significant word to convert
    between 64-bit words and the 32 bit value; however, this is only correct
    on big endian. This was exposed by #720, which in turn exposed that the
    get/set digits behaviour was not tested for Decimal 32/64. This code was
    updated to use a `memcpy()` instead of a cast for the 32-bit case. The
    s390x workflow had been failing because of a segfaulting compiler for a
    while and so I'd missed that the decimal 32/64 change had broken it 😬 .
    
    There is also one change to get the Windows verification job passing
    that I noticed when running the verify.yaml workflow to test big endian.
    
    This can be checked locally with:
    
    ```shell
    export NANOARROW_ARCH=s390x
    docker compose run --rm verify
    ```
    
    (and was also checked via the verify.yaml workflow)
---
 src/nanoarrow/common/inline_types.h | 28 +++++++++-----
 src/nanoarrow/common/utils.c        | 19 +++++++++-
 src/nanoarrow/common/utils_test.cc  | 76 ++++++++++++++++++++-----------------
 src/nanoarrow/ipc/decoder_test.cc   |  2 +
 src/nanoarrow/ipc/files_test.cc     | 24 ++++++++++--
 5 files changed, 100 insertions(+), 49 deletions(-)

diff --git a/src/nanoarrow/common/inline_types.h 
b/src/nanoarrow/common/inline_types.h
index 603d566e..a8d194f1 100644
--- a/src/nanoarrow/common/inline_types.h
+++ b/src/nanoarrow/common/inline_types.h
@@ -954,8 +954,11 @@ static inline void ArrowDecimalInit(struct ArrowDecimal* 
decimal, int32_t bitwid
 /// to 18 is sufficiently small).
 static inline int64_t ArrowDecimalGetIntUnsafe(const struct ArrowDecimal* 
decimal) {
   if (decimal->n_words == 0) {
-    return (int32_t)decimal->words[0];
+    int32_t value;
+    memcpy(&value, decimal->words, sizeof(int32_t));
+    return value;
   }
+
   return (int64_t)decimal->words[decimal->low_word_index];
 }
 
@@ -963,25 +966,29 @@ static inline int64_t ArrowDecimalGetIntUnsafe(const 
struct ArrowDecimal* decima
 /// \ingroup nanoarrow-utils
 static inline void ArrowDecimalGetBytes(const struct ArrowDecimal* decimal,
                                         uint8_t* out) {
-  memcpy(out, decimal->words,
-         (decimal->n_words > 0 ? decimal->n_words : 1) * sizeof(uint64_t));
+  if (decimal->n_words == 0) {
+    memcpy(out, decimal->words, sizeof(int32_t));
+  } else {
+    memcpy(out, decimal->words, decimal->n_words * sizeof(uint64_t));
+  }
 }
 
 /// \brief Returns 1 if the value represented by decimal is >= 0 or -1 
otherwise
 /// \ingroup nanoarrow-utils
 static inline int64_t ArrowDecimalSign(const struct ArrowDecimal* decimal) {
   if (decimal->n_words == 0) {
-    return 1 | ((int32_t)(decimal->words[0]) >> 31);
+    return ArrowDecimalGetIntUnsafe(decimal) >= 0 ? 1 : -1;
+  } else {
+    return 1 | ((int64_t)(decimal->words[decimal->high_word_index]) >> 63);
   }
-
-  return 1 | ((int64_t)(decimal->words[decimal->high_word_index]) >> 63);
 }
 
 /// \brief Sets the integer value of this decimal
 /// \ingroup nanoarrow-utils
 static inline void ArrowDecimalSetInt(struct ArrowDecimal* decimal, int64_t 
value) {
   if (decimal->n_words == 0) {
-    decimal->words[0] = (int32_t)value;
+    int32_t value32 = (int32_t)value;
+    memcpy(decimal->words, &value32, sizeof(int32_t));
     return;
   }
 
@@ -998,9 +1005,10 @@ static inline void ArrowDecimalSetInt(struct 
ArrowDecimal* decimal, int64_t valu
 /// \ingroup nanoarrow-utils
 static inline void ArrowDecimalNegate(struct ArrowDecimal* decimal) {
   if (decimal->n_words == 0) {
-    uint32_t elem = (uint32_t)decimal->words[0];
-    elem = ~elem + 1;
-    decimal->words[0] = (int32_t)elem;
+    int32_t value;
+    memcpy(&value, decimal->words, sizeof(int32_t));
+    value = -value;
+    memcpy(decimal->words, &value, sizeof(int32_t));
     return;
   }
 
diff --git a/src/nanoarrow/common/utils.c b/src/nanoarrow/common/utils.c
index 9dad5607..5b5116a1 100644
--- a/src/nanoarrow/common/utils.c
+++ b/src/nanoarrow/common/utils.c
@@ -346,13 +346,14 @@ ArrowErrorCode ArrowDecimalSetDigits(struct ArrowDecimal* 
decimal,
 
   // Use 32-bit words for portability
   uint32_t words32[8];
+  memset(words32, 0, sizeof(words32));
   int n_words32 = decimal->n_words > 0 ? decimal->n_words * 2 : 1;
   NANOARROW_DCHECK(n_words32 <= 8);
   memset(words32, 0, sizeof(words32));
 
   ShiftAndAdd(value, words32, n_words32);
 
-  if (decimal->low_word_index == 0) {
+  if (_ArrowIsLittleEndian() || n_words32 == 1) {
     memcpy(decimal->words, words32, sizeof(uint32_t) * n_words32);
   } else {
     uint64_t lo;
@@ -378,6 +379,22 @@ ArrowErrorCode ArrowDecimalAppendDigitsToBuffer(const 
struct ArrowDecimal* decim
                                                 struct ArrowBuffer* buffer) {
   NANOARROW_DCHECK(decimal->n_words == 0 || decimal->n_words == 1 ||
                    decimal->n_words == 2 || decimal->n_words == 4);
+
+  // For the 32-bit case, just use snprintf()
+  if (decimal->n_words == 0) {
+    int32_t value;
+    memcpy(&value, decimal->words, sizeof(int32_t));
+    NANOARROW_RETURN_NOT_OK(ArrowBufferReserve(buffer, 16));
+    int n_chars = snprintf((char*)buffer->data + buffer->size_bytes,
+                           (buffer->capacity_bytes - buffer->size_bytes), 
"%d", value);
+    if (n_chars <= 0) {
+      return EINVAL;
+    }
+
+    buffer->size_bytes += n_chars;
+    return NANOARROW_OK;
+  }
+
   int is_negative = ArrowDecimalSign(decimal) < 0;
 
   uint64_t words_little_endian[4];
diff --git a/src/nanoarrow/common/utils_test.cc 
b/src/nanoarrow/common/utils_test.cc
index 0d9c52f8..b9b64870 100644
--- a/src/nanoarrow/common/utils_test.cc
+++ b/src/nanoarrow/common/utils_test.cc
@@ -19,13 +19,13 @@
 #include <string>
 
 #if defined(NANOARROW_BUILD_TESTS_WITH_ARROW)
+#include <arrow/config.h>
 #include <arrow/util/decimal.h>
 #endif
 #include <gmock/gmock-matchers.h>
 #include <gtest/gtest.h>
 
 #include "nanoarrow/nanoarrow.hpp"
-#include "nanoarrow/nanoarrow_testing.hpp"
 
 #if defined(NANOARROW_BUILD_TESTS_WITH_ARROW)
 using namespace arrow;
@@ -403,7 +403,7 @@ TEST(DecimalTest, DecimalNegateTest) {
     } else if (bitwidth == 64) {
       decimal.words[decimal.low_word_index] = 
std::numeric_limits<int64_t>::max();
     } else {
-      decimal.words[decimal.low_word_index] = 
std::numeric_limits<int32_t>::max();
+      ArrowDecimalSetInt(&decimal, std::numeric_limits<int32_t>::max());
     }
     ASSERT_EQ(ArrowDecimalSign(&decimal), 1);
     ArrowDecimalNegate(&decimal);
@@ -417,8 +417,7 @@ TEST(DecimalTest, DecimalNegateTest) {
       EXPECT_EQ(decimal.words[decimal.low_word_index],
                 std::numeric_limits<int64_t>::max());
     } else {
-      EXPECT_EQ(decimal.words[decimal.low_word_index],
-                std::numeric_limits<int32_t>::max());
+      EXPECT_EQ(ArrowDecimalGetIntUnsafe(&decimal), 
std::numeric_limits<int32_t>::max());
     }
 
     if (bitwidth > 64) {
@@ -582,37 +581,44 @@ TEST(DecimalTest, DecimalDigitsTestInvalid) {
 }
 
 TEST(DecimalTest, DecimalRoundtripPowerOfTenTest) {
-  struct ArrowDecimal decimal;
-  ArrowDecimalInit(&decimal, 256, 76, 0);
-
-  struct ArrowBuffer buffer;
-  ArrowBufferInit(&buffer);
-
-  // Generate test strings with positive and negative powers of 10 and check
-  // roundtrip back to string.
-  std::stringstream ss;
-
-  for (const auto& sign : {"", "-"}) {
-    for (int i = 0; i < 76; i++) {
-      ss.str("");
-      ss << sign;
-      ss << "1";
-      for (int j = 0; j < i; j++) {
-        ss << "0";
+  std::vector<std::pair<int, int>> bitwidth_and_max_precision = {
+      {32, 9}, {64, 18}, {128, 38}, {256, 76}};
+
+  for (const auto& item : bitwidth_and_max_precision) {
+    SCOPED_TRACE(item.first);
+
+    struct ArrowDecimal decimal;
+    ArrowDecimalInit(&decimal, item.first, item.second, 0);
+
+    struct ArrowBuffer buffer;
+    ArrowBufferInit(&buffer);
+
+    // Generate test strings with positive and negative powers of 10 and check
+    // roundtrip back to string.
+    std::stringstream ss;
+
+    for (const auto& sign : {"", "-"}) {
+      for (int i = 0; i < item.second; i++) {
+        ss.str("");
+        ss << sign;
+        ss << "1";
+        for (int j = 0; j < i; j++) {
+          ss << "0";
+        }
+
+        SCOPED_TRACE(ss.str());
+        ASSERT_EQ(ArrowDecimalSetDigits(&decimal, 
ArrowCharView(ss.str().c_str())),
+                  NANOARROW_OK);
+
+        buffer.size_bytes = 0;
+        ASSERT_EQ(ArrowDecimalAppendDigitsToBuffer(&decimal, &buffer), 
NANOARROW_OK);
+        ASSERT_EQ(std::string(reinterpret_cast<char*>(buffer.data), 
buffer.size_bytes),
+                  ss.str());
       }
-
-      SCOPED_TRACE(ss.str());
-      ASSERT_EQ(ArrowDecimalSetDigits(&decimal, 
ArrowCharView(ss.str().c_str())),
-                NANOARROW_OK);
-
-      buffer.size_bytes = 0;
-      ASSERT_EQ(ArrowDecimalAppendDigitsToBuffer(&decimal, &buffer), 
NANOARROW_OK);
-      EXPECT_EQ(std::string(reinterpret_cast<char*>(buffer.data), 
buffer.size_bytes),
-                ss.str());
     }
-  }
 
-  ArrowBufferReset(&buffer);
+    ArrowBufferReset(&buffer);
+  }
 }
 
 TEST(DecimalTest, DecimalRoundtripBitshiftTest) {
@@ -665,7 +671,7 @@ TEST(DecimalTest, DecimalTestStringPositiveScale) {
   using namespace nanoarrow::literals;
 
   struct ArrowDecimal decimal;
-  ArrowDecimalInit(&decimal, 32, 9, 3);
+  ArrowDecimalInit(&decimal, 128, 9, 3);
 
   nanoarrow::UniqueBuffer buffer;
 
@@ -688,7 +694,7 @@ TEST(DecimalTest, DecimalTestStringZeroScale) {
   using namespace nanoarrow::literals;
 
   struct ArrowDecimal decimal;
-  ArrowDecimalInit(&decimal, 32, 9, 0);
+  ArrowDecimalInit(&decimal, 128, 9, 0);
 
   nanoarrow::UniqueBuffer buffer;
 
@@ -710,7 +716,7 @@ TEST(DecimalTest, DecimalTestStringNegativeScale) {
   using namespace nanoarrow::literals;
 
   struct ArrowDecimal decimal;
-  ArrowDecimalInit(&decimal, 32, 9, -3);
+  ArrowDecimalInit(&decimal, 128, 9, -3);
 
   nanoarrow::UniqueBuffer buffer;
 
diff --git a/src/nanoarrow/ipc/decoder_test.cc 
b/src/nanoarrow/ipc/decoder_test.cc
index b3856656..eb084f0b 100644
--- a/src/nanoarrow/ipc/decoder_test.cc
+++ b/src/nanoarrow/ipc/decoder_test.cc
@@ -372,6 +372,8 @@ void TestDecodeInt32Batch(const uint8_t* batch, size_t 
batch_len,
 
   ASSERT_EQ(ArrowIpcDecoderInit(decoder.get()), NANOARROW_OK);
   ASSERT_EQ(ArrowIpcDecoderSetSchema(decoder.get(), schema.get(), &error), 
NANOARROW_OK);
+  ASSERT_EQ(ArrowIpcDecoderSetEndianness(decoder.get(), 
NANOARROW_IPC_ENDIANNESS_LITTLE),
+            NANOARROW_OK);
 
   ASSERT_EQ(ArrowIpcDecoderDecodeHeader(decoder.get(), data, &error), 
NANOARROW_OK);
   struct ArrowBufferView body;
diff --git a/src/nanoarrow/ipc/files_test.cc b/src/nanoarrow/ipc/files_test.cc
index 5d39ec41..55b988bb 100644
--- a/src/nanoarrow/ipc/files_test.cc
+++ b/src/nanoarrow/ipc/files_test.cc
@@ -526,14 +526,16 @@ TEST_P(TestFileFixture, NanoarrowIpcTestFileIPCCheckJSON) 
{
   param.TestIPCCheckJSON(dir_builder.str());
 }
 
+// At least one Windows MSVC version does not allow the #if defined()
+// to be within a macro invocation, so we define these two cases
+// with some repetition.
+#if defined(NANOARROW_IPC_WITH_ZSTD)
 INSTANTIATE_TEST_SUITE_P(
     NanoarrowIpcTest, TestFileFixture,
     ::testing::Values(
-// Testing of other files
-#if defined(NANOARROW_IPC_WITH_ZSTD)
+        // Testing of other files
         TestFile::OK("2.0.0-compression/generated_uncompressible_zstd.stream"),
         TestFile::OK("2.0.0-compression/generated_zstd.stream"),
-#endif
         TestFile::OK("0.17.1/generated_union.stream"),
         TestFile::OK("0.14.1/generated_datetime.stream"),
         TestFile::OK("0.14.1/generated_decimal.stream"),
@@ -545,5 +547,21 @@ INSTANTIATE_TEST_SUITE_P(
         TestFile::OK("0.14.1/generated_primitive_zerolength.stream")
         // Comment to keep line from wrapping
         ));
+#else
+INSTANTIATE_TEST_SUITE_P(NanoarrowIpcTest, TestFileFixture,
+                         ::testing::Values(
+                             // Testing of other files
+                             TestFile::OK("0.17.1/generated_union.stream"),
+                             TestFile::OK("0.14.1/generated_datetime.stream"),
+                             TestFile::OK("0.14.1/generated_decimal.stream"),
+                             TestFile::OK("0.14.1/generated_interval.stream"),
+                             TestFile::OK("0.14.1/generated_map.stream"),
+                             TestFile::OK("0.14.1/generated_nested.stream"),
+                             TestFile::OK("0.14.1/generated_primitive.stream"),
+                             
TestFile::OK("0.14.1/generated_primitive_no_batches.stream"),
+                             
TestFile::OK("0.14.1/generated_primitive_zerolength.stream")
+                             // Comment to keep line from wrapping
+                             ));
+#endif
 
 #endif

Reply via email to