@@ -52,9 +52,17 @@ public static <X> Specification<X> startsWith(String field, String value) {
5252 return (root , cq , cb ) -> cb .like (cb .upper (getColumnPath (root , field )), EscapeCharacter .DEFAULT .escape (value ).toUpperCase () + "%" , EscapeCharacter .DEFAULT .getEscapeCharacter ());
5353 }
5454
55+ /**
56+ * Returns a specification where the field value is not equal within the given tolerance.
57+ */
5558 public static <X > Specification <X > notEqual (String field , Double value , Double tolerance ) {
5659 return (root , cq , cb ) -> {
5760 Expression <Double > doubleExpression = getColumnPath (root , field ).as (Double .class );
61+ /**
62+ * in order to be equal to doubleExpression, value has to fit :
63+ * value - tolerance <= doubleExpression <= value + tolerance
64+ * therefore in order to be different at least one of the opposite comparison needs to be true :
65+ */
5866 return cb .or (
5967 cb .greaterThan (doubleExpression , value + tolerance ),
6068 cb .lessThan (doubleExpression , value - tolerance )
@@ -137,13 +145,27 @@ private static <X> Specification<X> appendTextFilterToSpecification(Specificatio
137145
138146 @ NotNull
139147 private static <X > Specification <X > appendNumberFilterToSpecification (Specification <X > specification , ResourceFilterDTO resourceFilter ) {
140- final double tolerance = 0.00001 ; // tolerance for comparison
141148 String value = resourceFilter .value ().toString ();
142- return createNumberPredicate (specification , resourceFilter , value , tolerance );
149+ return createNumberPredicate (specification , resourceFilter , value );
143150 }
144151
145- private static <X > Specification <X > createNumberPredicate (Specification <X > specification , ResourceFilterDTO resourceFilter , String value , double tolerance ) {
146- Double valueDouble = Double .valueOf (value );
152+ private static <X > Specification <X > createNumberPredicate (Specification <X > specification , ResourceFilterDTO resourceFilter , String filterValue ) {
153+ double tolerance ;
154+ if (resourceFilter .tolerance () != null ) {
155+ tolerance = resourceFilter .tolerance ();
156+ } else {
157+ // the reference for the comparison is the number of digits after the decimal point in filterValue
158+ // extra digits are ignored, but the user may add '0's after the decimal point in order to get a better precision
159+ String [] splitValue = filterValue .split ("\\ ." );
160+ int numberOfDecimalAfterDot = 0 ;
161+ if (splitValue .length > 1 ) {
162+ numberOfDecimalAfterDot = splitValue [1 ].length ();
163+ }
164+ // tolerance is multiplied by 0.5 to simulate the fact that the database value is rounded (in the front, from the user viewpoint)
165+ // more than 13 decimal after dot will likely cause rounding errors due to double precision
166+ tolerance = Math .pow (10 , -numberOfDecimalAfterDot ) * 0.5 ;
167+ }
168+ Double valueDouble = Double .valueOf (filterValue );
147169 return switch (resourceFilter .type ()) {
148170 case NOT_EQUAL -> specification .and (notEqual (resourceFilter .column (), valueDouble , tolerance ));
149171 case LESS_THAN_OR_EQUAL ->
0 commit comments