shashank created CAMEL-25703:
--------------------------------

             Summary: camel-bindy - a BigDecimal, Double or Float field without 
precision is marshalled with at most three decimals (1.23456 is written as 
1.235, 0.000123 as 0), so data is silently rounded and a record does not round 
trip
                 Key: CAMEL-25703
                 URL: https://issues.apache.org/jira/browse/CAMEL-25703
             Project: Camel
          Issue Type: Bug
          Components: camel-bindy
            Reporter: shashank
            Assignee: shashank


Since CAMEL-24168 (4.22.0) the default {{precision}} of {{@DataField}} and 
{{@KeyValuePairField}} is {{-1}}, documented as "Use -1 (default) to preserve 
the original scale from the input". Unmarshal now keeps the scale, but marshal 
does not keep the decimals.

{{AbstractNumberFormat}} (main d46d51c24550, lines 50-65) creates a {{new 
DecimalFormat()}} and, for a precision of {{-1}}, does not set the fraction 
digits at all ("skip DecimalFormat fraction digit configuration when precision 
is unset", PR #24919). The default pattern of {{DecimalFormat}} is 
{{#,##0.###}}: at most three fraction digits, rounded {{HALF_EVEN}}. 
{{BigDecimalFormatFactory}}, {{DoubleFormatFactory}} and {{FloatFormatFactory}} 
format with it, so every value with more than three decimals is rounded when it 
is written, without an error or a log line:

||field type||value||written on main||
|{{BigDecimal}}|{{1.23456}}|{{1.235}}|
|{{BigDecimal}}|{{0.98765}}|{{0.988}}|
|{{Double}}|{{12.3456789}}|{{12.346}}|
|{{Double}}|{{0.000123}}|{{0}}|

Unmarshal reads {{1.23456}} correctly, so a route that unmarshals a CSV, 
fixed-length or key-value-pair record and marshals it again changes the 
numbers. Exchange rates, unit prices, quantities and coordinates with four or 
more decimals are common in the files Bindy is used for, and the default 
annotation ({{@DataField(pos = 1)}}) is enough to hit it. The only test of the 
unset case ({{BindyBigDecimalNoPrecisionTest}}, added by CAMEL-24168) uses 
{{10.35}}, two decimals, so it passes.

Trigger: default options; a {{BigDecimal}}, {{Double}} or {{Float}} field with 
neither {{precision}} nor {{pattern}}; any value with more than three decimals; 
{{marshal().bindy(...)}} of type Csv, Fixed or KeyValue.

h3. Reproduction

New {{BindyNumberWithoutPrecisionKeepsDecimalsTest}} (5 tests, locale {{en}}):
* CSV marshal of {{BigDecimal 1.23456}}, {{Double 0.000123}}, {{Float 1.1}}: 
expected {{1.23456,0.000123,1.1}}, main writes {{1.235,0,1.1}};
* CSV unmarshal of {{0.98765,12.3456789,2.5}} then marshal: main writes 
{{0.988,12.346,2.5}};
* fixed-length record ({{BigDecimal}} and {{Double}}, length 10 each) 
unmarshalled and marshalled: {{   1.23456  0.000123}} becomes {{     1.235      
   0}};
* controls (pass on main and with the fix): {{10}}, {{10.0}} and {{0.5f}} are 
written as {{10,10,0.5}}; the whole-number float {{123456792f}} is written as 
{{123456792}}.

On main 3 of the first 4 fail (the first control passes; the whole-number float 
control was not run against main, which writes that float through the same 
{{double}} path). With the fix the five pass, and the camel-bindy tests: 243 
run, 0 failures, 0 errors, 3 skipped.

h3. Proposed fix

* {{AbstractNumberFormat}}: when no precision is set (and no implied decimal 
separator), {{setMaximumFractionDigits(Integer.MAX_VALUE)}}, so the formatter 
keeps every decimal of a {{BigDecimal}} and the shortest decimal form of a 
{{double}} ({{DecimalFormat}} caps a {{double}} at 340 fraction digits itself). 
Grouping, locale symbols and the whole-number output ({{10}}, not {{10.0}}) are 
unchanged.
* {{FloatFormatFactory}}: a finite {{Float}} with a fraction part and without 
precision is formatted through {{new BigDecimal(Float.toString(value))}}; 
otherwise the widening to {{double}} would now show as {{1.100000023841858}} 
instead of {{1.1}}. A whole-number {{Float}} keeps the exact digits it had 
before ({{123456792}}, not the shorter {{123456790}} of {{Float.toString}}).
* An explicit {{precision}}, a {{pattern}} and {{impliedDecimalSeparator}} are 
not affected.

Users can notice that such values are written with all their decimals, also a 
{{Double}} that is the result of a calculation ({{0.1 + 0.2}} is written 
{{0.30000000000000004}}, before {{0.3}}); a note goes into the 4.24 upgrade 
guide.

How it was found: round r29, track I (silent data corruption in data formats), 
a metamorphic check "unmarshal then marshal is the identity on valid input" 
applied by reading the number formats of Bindy; a JShell probe of {{new 
DecimalFormat()}} with grouping off gave {{12.346}} for {{12.34567}} and {{0}} 
for {{0.000123}}, then the test above on the real data format.

Affected: main (d46d51c24550; tested). camel-4.23.x and camel-4.22.x have the 
same {{AbstractNumberFormat}} lines and {{precision() default -1}} (read, not 
run). camel-4.18.x and camel-4.14.x still have the older code of CAMEL-24168 
(precision 0, all decimals dropped), which is that ticket, not this one.

Duplicate check (2026-10-16): JIRA text "bindy BigDecimal", "bindy precision", 
"bindy decimal digits", "bindy fraction digits", "bindy rounded three decimals" 
and the camel-bindy issues created since 2026-06-01 (CAMEL-24168 is the only 
related one, resolved; CAMEL-25150, 25151, 25152 are other defects); GitHub 
pull requests "bindy precision", "bindy decimal" and "bindy in:title" (PR 
#24919 for CAMEL-24168, merged; nothing open).

_Filed with Claude Code on behalf of allthingssecurity._




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to