From a250ae2bbb33a9723deabf1eb9595387f92b29e8 Mon Sep 17 00:00:00 2001 From: Sergio Cruz Santos Date: Mon, 26 Oct 2026 16:27:25 +0100 Subject: [PATCH] Read Float4 values as double and BigDecimal with correct precision ArrowFlightJdbcFloat4VectorAccessor widened the float to double in getDouble() and passed it to BigDecimal.valueOf(double) in getBigDecimal(), which exposed the binary approximation of the float: 0.1f was returned as 0.10000000149011612. Convert through the float's decimal representation instead, so getDouble(), getObject(Double.class) and getBigDecimal() return the value the float stands for. --- .../ArrowFlightJdbcFloat4VectorAccessor.java | 8 +- ...rowFlightJdbcFloat4VectorAccessorTest.java | 79 ++++++++++++++++++- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/numeric/ArrowFlightJdbcFloat4VectorAccessor.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/numeric/ArrowFlightJdbcFloat4VectorAccessor.java index 4d9d0d7261..04f049a5a5 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/numeric/ArrowFlightJdbcFloat4VectorAccessor.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/numeric/ArrowFlightJdbcFloat4VectorAccessor.java @@ -99,7 +99,7 @@ public float getFloat() { @Override public double getDouble() { - return this.getFloat(); + return Double.parseDouble(Float.toString(this.getFloat())); } @Override @@ -110,7 +110,7 @@ public BigDecimal getBigDecimal() throws SQLException { throw new SQLException("BigDecimal doesn't support Infinite/NaN."); } - return this.wasNull ? null : BigDecimal.valueOf(value); + return this.wasNull ? null : new BigDecimal(Float.toString(value)); } @Override @@ -119,7 +119,9 @@ public BigDecimal getBigDecimal(int scale) throws SQLException { if (Float.isInfinite(value) || Float.isNaN(value)) { throw new SQLException("BigDecimal doesn't support Infinite/NaN."); } - return this.wasNull ? null : BigDecimal.valueOf(value).setScale(scale, RoundingMode.HALF_UP); + return this.wasNull + ? null + : new BigDecimal(Float.toString(value)).setScale(scale, RoundingMode.HALF_UP); } @Override diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/numeric/ArrowFlightJdbcFloat4VectorAccessorTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/numeric/ArrowFlightJdbcFloat4VectorAccessorTest.java index 89ebc5336d..2d712934ac 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/numeric/ArrowFlightJdbcFloat4VectorAccessorTest.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/numeric/ArrowFlightJdbcFloat4VectorAccessorTest.java @@ -179,7 +179,80 @@ public void testShouldGetDoubleMethodFromFloat4Vector() throws Exception { accessorIterator.assertAccessorGetter( vector, ArrowFlightJdbcFloat4VectorAccessor::getDouble, - accessor -> is((double) accessor.getFloat())); + accessor -> is(Double.parseDouble(Float.toString(accessor.getFloat())))); + } + + @Test + public void testShouldGetDoubleThatRoundTripsToTheSameFloat() throws Exception { + accessorIterator.iterate( + vector, + (accessor, currentRow) -> + assertThat((float) accessor.getDouble(), is(accessor.getFloat()))); + } + + @Test + public void testShouldGetDecimalValuesWithoutFloatWideningArtifacts() throws Exception { + final float[] values = {0.1f, -0.1f, 0.2f, 0.3f, 1.1f, 123.456f, 3.4e-5f, -0.0f}; + try (final Float4Vector float4Vector = + new Float4Vector("ID", rootAllocatorTestExtension.getRootAllocator())) { + for (int i = 0; i < values.length; i++) { + float4Vector.setSafe(i, values[i]); + } + float4Vector.setValueCount(values.length); + + accessorIterator.iterate( + float4Vector, + (accessor, currentRow) -> { + final String expected = Float.toString(values[currentRow]); + assertThat(accessor.getDouble(), is(Double.parseDouble(expected))); + assertThat(accessor.getObject(Double.class), is(Double.parseDouble(expected))); + assertThat(accessor.getBigDecimal(), is(new BigDecimal(expected))); + assertThat( + accessor.getBigDecimal(9), + is(new BigDecimal(expected).setScale(9, RoundingMode.HALF_UP))); + }); + } + } + + @Test + public void testShouldGetDoubleFromFloat4VectorWithZeroPointOne() throws Exception { + try (final Float4Vector float4Vector = + new Float4Vector("ID", rootAllocatorTestExtension.getRootAllocator())) { + float4Vector.setSafe(0, 0.1f); + float4Vector.setValueCount(1); + + accessorIterator.iterate( + float4Vector, + (accessor, currentRow) -> { + assertThat(accessor.getDouble(), is(0.1)); + assertThat(accessor.getBigDecimal(), is(new BigDecimal("0.1"))); + assertThat(accessor.getBigDecimal(9), is(new BigDecimal("0.100000000"))); + }); + } + } + + @Test + public void testShouldGetDoubleMethodFromFloat4VectorWithNull() throws Exception { + try (final Float4Vector float4Vector = + new Float4Vector("ID", rootAllocatorTestExtension.getRootAllocator())) { + float4Vector.setNull(0); + float4Vector.setValueCount(1); + + accessorIterator.assertAccessorGetter( + float4Vector, ArrowFlightJdbcFloat4VectorAccessor::getDouble, is(0.0)); + } + } + + @Test + public void testShouldGetDoubleMethodFromFloat4VectorWithNaN() throws Exception { + try (final Float4Vector float4Vector = + new Float4Vector("ID", rootAllocatorTestExtension.getRootAllocator())) { + float4Vector.setSafe(0, Float.NaN); + float4Vector.setValueCount(1); + + accessorIterator.assertAccessorGetter( + float4Vector, ArrowFlightJdbcFloat4VectorAccessor::getDouble, is(Double.NaN)); + } } @Test @@ -191,7 +264,7 @@ public void testShouldGetBigDecimalMethodFromFloat4Vector() throws Exception { if (Float.isInfinite(value) || Float.isNaN(value)) { assertThrows(SQLException.class, accessor::getBigDecimal); } else { - assertThat(accessor.getBigDecimal(), is(BigDecimal.valueOf(value))); + assertThat(accessor.getBigDecimal(), is(new BigDecimal(Float.toString(value)))); } }); } @@ -207,7 +280,7 @@ public void testShouldGetBigDecimalWithScaleMethodFromFloat4Vector() throws Exce } else { assertThat( accessor.getBigDecimal(9), - is(BigDecimal.valueOf(value).setScale(9, RoundingMode.HALF_UP))); + is(new BigDecimal(Float.toString(value)).setScale(9, RoundingMode.HALF_UP))); } }); }