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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 86e0ee42e3f5 CAMEL-25703: camel-bindy - keep all decimals of a number 
field without precision when marshalling (#27988)
86e0ee42e3f5 is described below

commit 86e0ee42e3f538cf758ca5f65982be2ab183aec9
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Oct 17 14:43:08 2026 +0530

    CAMEL-25703: camel-bindy - keep all decimals of a number field without 
precision when marshalling (#27988)
    
    Since CAMEL-24168 the default precision of @DataField and @KeyValuePairField
    is -1, so AbstractNumberFormat no longer sets the fraction digits of its
    DecimalFormat. The default pattern of DecimalFormat keeps at most three
    fraction digits, so a BigDecimal, Double or Float field without precision
    and pattern was marshalled rounded: 1.23456 became 1.235 and 0.000123
    became 0, and a record unmarshalled and marshalled again changed.
    
    Without a precision the formatter now keeps all fraction digits. A finite
    Float with a fraction part is formatted from Float.toString, so 1.1f is
    still written as 1.1 and not as its widening to double; a whole-number
    Float keeps its exact digits as before. Explicit precision, pattern and 
implied
    decimal separator are unchanged. Upgrade note in the 4.24 guide.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../bindy/format/AbstractNumberFormat.java         |   3 +
 .../bindy/format/factories/FloatFormatFactory.java |  12 +-
 ...ndyNumberWithoutPrecisionKeepsDecimalsTest.java | 174 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |   9 ++
 4 files changed, 195 insertions(+), 3 deletions(-)

diff --git 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
index 742761d00ef5..01d52b36d2ee 100644
--- 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
+++ 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
@@ -62,6 +62,9 @@ public abstract class AbstractNumberFormat<T extends Number> 
implements Format<T
         } else if (this.precision >= 0) {
             this.format.setMinimumFractionDigits(this.precision);
             this.format.setMaximumFractionDigits(this.precision);
+        } else {
+            // no precision: keep every decimal of the value (the default of 
DecimalFormat rounds to three)
+            this.format.setMaximumFractionDigits(Integer.MAX_VALUE);
         }
     }
 
diff --git 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/FloatFormatFactory.java
 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/FloatFormatFactory.java
index c9a589fc697a..72a55d5acff5 100644
--- 
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/FloatFormatFactory.java
+++ 
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/FloatFormatFactory.java
@@ -53,9 +53,15 @@ public class FloatFormatFactory extends 
AbstractFormatFactory {
 
         @Override
         public String format(Float object) throws Exception {
-            return !super.hasImpliedDecimalPosition()
-                    ? super.getFormat().format(object)
-                    : super.getFormat().format(object * super.getMultiplier());
+            if (super.hasImpliedDecimalPosition()) {
+                return super.getFormat().format(object * 
super.getMultiplier());
+            }
+            if (super.getPrecision() < 0 && Float.isFinite(object) && object 
!= Math.rint(object)) {
+                // no precision: the decimal digits of the float (1.1), not of 
its widening to double (1.100000023841858);
+                // a whole number keeps its exact digits (123456792, not the 
shorter 1.2345679E8 of Float.toString)
+                return super.getFormat().format(new 
BigDecimal(object.toString()));
+            }
+            return super.getFormat().format(object);
         }
 
         @Override
diff --git 
a/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyNumberWithoutPrecisionKeepsDecimalsTest.java
 
b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyNumberWithoutPrecisionKeepsDecimalsTest.java
new file mode 100644
index 000000000000..38a04eaea55a
--- /dev/null
+++ 
b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyNumberWithoutPrecisionKeepsDecimalsTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dataformat.bindy.csv;
+
+import java.math.BigDecimal;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dataformat.bindy.annotation.CsvRecord;
+import org.apache.camel.dataformat.bindy.annotation.DataField;
+import org.apache.camel.dataformat.bindy.annotation.FixedLengthRecord;
+import org.apache.camel.dataformat.bindy.annotation.KeyValuePairField;
+import org.apache.camel.dataformat.bindy.annotation.Message;
+import org.apache.camel.model.dataformat.BindyDataFormat;
+import org.apache.camel.model.dataformat.BindyType;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A BigDecimal, Double or Float field without a precision keeps all its 
decimals when it is marshalled, so that a
+ * record unmarshalled and marshalled again is the same text. The default 
formatter of the JDK keeps at most three
+ * decimals.
+ */
+public class BindyNumberWithoutPrecisionKeepsDecimalsTest extends 
CamelTestSupport {
+
+    @Test
+    public void csvMarshalKeepsAllDecimals() {
+        CsvModel model = new CsvModel();
+        model.rate = new BigDecimal("1.23456");
+        model.ratio = 0.000123d;
+        model.weight = 1.1f;
+
+        String csv = template.requestBody("direct:csv-marshal", model, 
String.class);
+
+        assertEquals("1.23456,0.000123,1.1\r\n", csv);
+    }
+
+    @Test
+    public void csvRecordIsTheSameAfterUnmarshalAndMarshal() {
+        String record = "0.98765,12.3456789,2.5\r\n";
+
+        CsvModel model = template.requestBody("direct:csv-unmarshal", record, 
CsvModel.class);
+        assertEquals(new BigDecimal("0.98765"), model.rate);
+        assertEquals(12.3456789d, model.ratio);
+        assertEquals(2.5f, model.weight);
+
+        assertEquals(record, template.requestBody("direct:csv-marshal", model, 
String.class));
+    }
+
+    @Test
+    public void wholeAndShortNumbersAreUnchanged() {
+        CsvModel model = new CsvModel();
+        model.rate = new BigDecimal("10");
+        model.ratio = 10d;
+        model.weight = 0.5f;
+
+        assertEquals("10,10,0.5\r\n", 
template.requestBody("direct:csv-marshal", model, String.class));
+    }
+
+    @Test
+    public void wholeFloatKeepsItsDigits() {
+        CsvModel model = new CsvModel();
+        model.rate = BigDecimal.ONE;
+        model.ratio = 1d;
+        // a float that is a whole number above 2^24: Float.toString gives the 
shorter 1.2345679E8
+        model.weight = 123456792f;
+
+        assertEquals("1,1,123456792\r\n", 
template.requestBody("direct:csv-marshal", model, String.class));
+    }
+
+    @Test
+    public void fixedLengthRecordIsTheSameAfterUnmarshalAndMarshal() {
+        String record = "   1.23456  0.000123";
+
+        FixedModel model = template.requestBody("direct:fixed-unmarshal", 
record, FixedModel.class);
+        assertEquals(new BigDecimal("1.23456"), model.amount);
+        assertEquals(0.000123d, model.factor);
+
+        assertEquals(record + "\r\n", 
template.requestBody("direct:fixed-marshal", model, String.class));
+    }
+
+    @Test
+    public void floatWithScientificToStringIsWrittenInPlainDecimals() {
+        CsvModel model = new CsvModel();
+        model.rate = new BigDecimal("1");
+        model.ratio = 1d;
+        // Float.toString gives 1.23E-4 for this value
+        model.weight = 0.000123f;
+
+        assertEquals("1,1,0.000123\r\n", 
template.requestBody("direct:csv-marshal", model, String.class));
+    }
+
+    @Test
+    public void keyValuePairRecordIsTheSameAfterUnmarshalAndMarshal() {
+        String record = "1=1.23456 2=0.000123 3=1.1";
+
+        KvpModel model = template.requestBody("direct:kvp-unmarshal", record, 
KvpModel.class);
+        assertEquals(new BigDecimal("1.23456"), model.rate);
+        assertEquals(0.000123d, model.ratio);
+        assertEquals(1.1f, model.weight);
+
+        assertEquals(record, template.requestBody("direct:kvp-marshal", model, 
String.class).trim());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                BindyDataFormat csv = new 
BindyDataFormat().type(BindyType.Csv).classType(CsvModel.class).locale("en");
+                BindyDataFormat fixed
+                        = new 
BindyDataFormat().type(BindyType.Fixed).classType(FixedModel.class).locale("en");
+
+                from("direct:csv-marshal").marshal(csv);
+                from("direct:csv-unmarshal").unmarshal(csv);
+                from("direct:fixed-marshal").marshal(fixed);
+                from("direct:fixed-unmarshal").unmarshal(fixed);
+
+                BindyDataFormat kvp
+                        = new 
BindyDataFormat().type(BindyType.KeyValue).classType(KvpModel.class).locale("en");
+                from("direct:kvp-marshal").marshal(kvp);
+                from("direct:kvp-unmarshal").unmarshal(kvp);
+            }
+        };
+    }
+
+    @CsvRecord(separator = ",")
+    public static class CsvModel {
+        @DataField(pos = 1)
+        public BigDecimal rate;
+
+        @DataField(pos = 2)
+        public Double ratio;
+
+        @DataField(pos = 3)
+        public Float weight;
+    }
+
+    @FixedLengthRecord(length = 20)
+    public static class FixedModel {
+        @DataField(pos = 1, length = 10)
+        public BigDecimal amount;
+
+        @DataField(pos = 11, length = 10)
+        public Double factor;
+    }
+
+    @Message(pairSeparator = " ", keyValuePairSeparator = "=")
+    public static class KvpModel {
+        @KeyValuePairField(tag = 1)
+        public BigDecimal rate;
+
+        @KeyValuePairField(tag = 2)
+        public Double ratio;
+
+        @KeyValuePairField(tag = 3)
+        public Float weight;
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index f1cc998b316e..84afa10e507b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -753,6 +753,15 @@ now resolves to no value, as it already did without a 
field, so its default valu
 (`{{?hashicorp:secret:missing#field}}`) applies. Before, without a default 
value, it failed with a
 `NullPointerException`.
 
+=== camel-bindy - a number field without precision keeps all its decimals when 
marshalled
+
+A `BigDecimal`, `Double` or `Float` field whose `@DataField` (or 
`@KeyValuePairField`) has no `precision` and no
+`pattern` was marshalled with at most three decimals, rounded: `1.23456` was 
written as `1.235` and `0.000123` as `0`
+(since Camel 4.22, CAMEL-24168; up to Camel 4.21 the default `precision = 0` 
wrote these fields without any decimals).
+It is now written with all its decimals, so a record that is unmarshalled and 
marshalled again is the same text. A
+`Double` that is the result of a calculation is written with all its digits 
too (`0.1 + 0.2` as `0.30000000000000004`,
+before `0.3`). Set `precision` to get a fixed number of decimals, as before.
+
 === camel-ical - each unmarshal parses with its own calendar builder
 
 The `ical` data format used one ical4j `CalendarBuilder` for all exchanges. 
Two exchanges that were unmarshalled at

Reply via email to