[ 
https://issues.apache.org/jira/browse/CAMEL-25013?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen resolved CAMEL-25013.
---------------------------------
    Resolution: Fixed

Fixed by https://github.com/apache/camel/pull/26930 (merged to main for 4.23.0).

_Claude Code on behalf of davsclaus_

> Simple and PredicateBuilder comparisons truncate decimals when the two 
> numbers have different types: ${header.amount} > 100 is false for a 
> BigDecimal 100.50, and 2.5 == 2 is true
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25013
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25013
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-core
>            Reporter: shashank
>            Priority: Minor
>             Fix For: 4.23.0
>
>
> {{ObjectHelper.typeCoerceCompare}} 
> ({{core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java:281-363}})
>  has fast paths for two values of the same type and for String/number pairs. 
> When both values are numbers of *different* types ({{Double}} vs {{Integer}}, 
> {{BigDecimal}} vs {{Integer}}, {{BigDecimal}} vs {{Double}}, {{Float}} vs 
> {{Long}}, ...), none of them applies and the code falls through to:
> {code:java}
> // if both values is numeric then compare using numeric
> Long leftNum = converter.tryConvertTo(Long.class, leftValue);
> Long rightNum = converter.tryConvertTo(Long.class, rightValue);
> if (leftNum != null && rightNum != null) {
>     return leftNum.compareTo(rightNum);
> }
> {code}
> The type converter converts a {{Number}} to {{Long}} with {{longValue()}} 
> ({{ObjectConverter.toLong(Number)}}), which drops the fractional part. So 
> {{2.5}} and {{2}} compare as equal.
> {{typeCoerceEquals}} has the same problem in {{tryConverters}} 
> ({{ObjectHelper.java:142-170}}): it converts the left value to the class of 
> the right value, so {{Double 2.5}} becomes {{Integer 2}}, and {{2.5 == 2}} is 
> true.
> These methods back all Simple comparison operators ({{>}}, {{>=}}, {{<}}, 
> {{<=}}, {{==}}, {{!=}}, {{range}}) and the Java DSL predicates 
> ({{header("x").isGreaterThan(...)}}, {{isEqualTo(...)}}). The left side is 
> often a {{Double}} (JSONPath, Jackson) or a {{BigDecimal}} (JDBC/SQL). The 
> right side is often an integer literal. The result is silent wrong routing: 
> no error and no log.
> *Reproduction* (standalone program against main):
> {noformat}
> typeCoerceCompare(Double 2.5, Integer 2)          = 0    (expected > 0)
> typeCoerceCompare(BigDecimal 100.50, Integer 100) = 0    (expected > 0)
> typeCoerceCompare(BigDecimal 2.5, Double 2.0)     = 0    (expected > 0)
> typeCoerceCompare(Double -0.9, Integer 0)         = 0    (expected < 0)
> typeCoerceEquals(Double 2.5, Integer 2)           = true (expected false)
> typeCoerceEquals(BigDecimal 99.99, Long 99)       = true (expected false)
> header p = 2.5 (Double, BigDecimal or Float):
>   ${header.p} > 2            -> false
>   ${header.p} == 2           -> true
>   ${header.p} != 2           -> false
>   ${header.p} <= 2           -> true
>   ${header.p} range '1..2'   -> true
>   ${header.p} > 2.0          -> false for BigDecimal and Float (true for 
> Double)
> from("direct:order").choice()
>     .when(simple("${header.amount} > 100")).to("large")
>     .otherwise().to("small");
>   amount = new BigDecimal("100.50")  -> small
>   amount = 100.75d                   -> small
> from("direct:java").filter(header("amount").isGreaterThan(100)) ...
>   amount = BigDecimal 100.50 or Double 100.75 -> filtered out
> {noformat}
> Controls behave correctly: same-type values, the String "2.5" vs "2" 
> (CAMEL-21109), and a String header "100.50". The same happens for a String 
> right-hand side against a {{BigDecimal}}: {{typeCoerceCompare(BigDecimal 2.5, 
> "2") = 0}}. A property-based test (3000 random Double/BigDecimal vs Integer 
> pairs) shrinks the failure to {{0.01}} vs {{0}} comparing as equal.
> Two {{BigDecimal}} (or two {{BigInteger}}) values have no fast path either 
> and go through the same {{Long}} fallback: {{typeCoerceCompare(BigDecimal 
> 2.5, BigDecimal 2.4) = 0}}, and a {{BigInteger}} that does not fit in a long 
> wraps. {{typeCoerceEquals}} of two {{BigDecimal}} values uses 
> {{BigDecimal.equals}}, which also compares the scale, so {{2.50 == 2.5}} is 
> false while {{<=}} and {{>=}} are both true.
> The same fallback code is in camel-2.25.4, camel-3.0.0, camel-4.0.0 and 
> camel-4.14.0, so all maintained versions are affected.
> A Lean model proves the following. For every integer n ≥ 0 and every digit d 
> = 1..9, the current code says {{n.d == n}}. Values without a fractional part 
> always compare correctly, so the fix only changes the result when a value has 
> a fractional part.
> *Proposed fix:* in {{typeCoerceCompare}}, after the existing fast paths, 
> compare two numbers, or a number and a numeric String, by their values: as 
> {{long}} when both are integral, otherwise as {{BigDecimal}} 
> ({{Double}}/{{Float}} through their decimal representation, NaN and infinity 
> as {{double}}). In {{typeCoerceEquals}}, do the same for two numbers of 
> different types and for two {{BigDecimal}} values. {{2.0 == 2}} and {{2 < 
> 2.5}} keep working, and {{2.5 == 2}} becomes false. Add an upgrade guide 
> note, as existing routes may get a different result.
> _Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to