diff --git a/src/nanoarrow/common/inline_buffer.h b/src/nanoarrow/common/inline_buffer.h index 929fe8159..bd74afb48 100644 --- a/src/nanoarrow/common/inline_buffer.h +++ b/src/nanoarrow/common/inline_buffer.h @@ -117,14 +117,16 @@ static inline uint16_t ArrowFloatToHalfFloat(float value) { if (exp == 0) { res = 0; + fc = 0; } else if (exp == 0xff) { res = 0x1f; } else if (res > 0x1e) { res = 0x1f; fc = 0; } else if (res < 0x01) { + // Restore the implicit leading bit and shift for a subnormal exponent. + fc = res < -9 ? 0 : (uint16_t)((fc | 0x400) >> (1 - res)); res = 0; - fc = 0; } return (uint16_t)((sn << 15) | (uint16_t)(res << 10) | fc); @@ -140,6 +142,15 @@ static inline float ArrowHalfFloatToFloat(uint16_t value) { if (exp == 0) { res = 0; + if (fc != 0) { + // Normalize the subnormal significand before rebiasing its exponent. + res = 127 - 14; + while ((fc & 0x400) == 0) { + fc <<= 1; + res--; + } + fc &= 0x3ff; + } } else if (exp == 0x1f) { res = 0xff; } diff --git a/src/nanoarrow/common/utils_test.cc b/src/nanoarrow/common/utils_test.cc index d4e43ad57..0d52d0f95 100644 --- a/src/nanoarrow/common/utils_test.cc +++ b/src/nanoarrow/common/utils_test.cc @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +#include #include #include @@ -759,6 +760,28 @@ TEST(HalfFloatTest, FloatAndHalfFloatRoundTrip) { } } +TEST(HalfFloatTest, FloatAndHalfFloatSubnormals) { + // Include signed zeros and the smallest normals at the subnormal boundaries. + for (uint16_t bits = 0; bits <= 0x400; bits++) { + SCOPED_TRACE(bits); + float expected = std::ldexp(static_cast(bits), -24); + EXPECT_EQ(ArrowHalfFloatToFloat(bits), expected); + EXPECT_EQ(ArrowHalfFloatToFloat(bits | 0x8000), -expected); + EXPECT_EQ(ArrowFloatToHalfFloat(expected), bits); + EXPECT_EQ(ArrowFloatToHalfFloat(-expected), bits | 0x8000); + } + + EXPECT_FALSE(std::signbit(ArrowHalfFloatToFloat(0x0000))); + EXPECT_TRUE(std::signbit(ArrowHalfFloatToFloat(0x8000))); + + float underflow[] = {std::ldexp(1.0f, -25), std::ldexp(1.0f, -126), + std::ldexp(1.5f, -127)}; + for (float value : underflow) { + EXPECT_EQ(ArrowFloatToHalfFloat(value), 0x0000); + EXPECT_EQ(ArrowFloatToHalfFloat(-value), 0x8000); + } +} + TEST(UtilsTest, ArrowResolveChunk64Test) { int64_t offsets[] = {0, 2, 3, 6}; int64_t n_offsets = 4;