sahvx655-wq commented on code in PR #410:
URL: https://github.com/apache/commons-validator/pull/410#discussion_r3486674831


##########
src/main/java/org/apache/commons/validator/routines/DoubleValidator.java:
##########
@@ -68,6 +69,16 @@ public class DoubleValidator extends AbstractNumberValidator 
{
 
     private static final DoubleValidator VALIDATOR = new DoubleValidator();
 
+    private static boolean isFinite(final Number value) {

Review Comment:
   Done. Rebased on master and dropped the local helper in both DoubleValidator 
and FloatValidator so they use the inherited isFinite(Number).



##########
src/main/java/org/apache/commons/validator/routines/DoubleValidator.java:
##########
@@ -183,6 +194,42 @@ public boolean minValue(final Double value, final double 
min) {
         return minValue(value.doubleValue(), min);
     }
 
+    /**
+     * Tests if the value is less than or equal to a maximum, comparing the 
exact values.
+     *
+     * <p>
+     * This overrides the {@link Number} overload inherited from the 
superclass, which narrows the bound to a {@code double} before comparing and so 
loses
+     * precision for a {@code BigDecimal} or {@code BigInteger} bound that 
carries more significant digits than a {@code double} can hold. A non-finite
+     * {@link Double} or {@link Float} operand keeps the {@code doubleValue()} 
comparison so the documented infinity behaviour is unchanged.
+     * </p>
+     *
+     * @param value The value validation is being performed on.
+     * @param max   The maximum value.
+     * @return {@code true} if the value is less than or equal to the maximum.
+     */
+    @Override
+    public boolean maxValue(final Number value, final Number max) {
+        return isFinite(value) && isFinite(max) ? 
BigDecimal.valueOf(value.doubleValue()).compareTo(toBigDecimal(max)) <= 0 : 
value.doubleValue() <= max.doubleValue();

Review Comment:
   Switched over. Both minValue and maxValue in both validators now call the 
superclass compareTo(value, bound) instead of building the BigDecimal by hand.



##########
src/main/java/org/apache/commons/validator/routines/FloatValidator.java:
##########
@@ -183,6 +194,42 @@ public boolean minValue(final Float value, final float 
min) {
         return minValue(value.floatValue(), min);
     }
 
+    /**
+     * Tests if the value is less than or equal to a maximum, comparing the 
exact values.
+     *
+     * <p>
+     * This overrides the {@link Number} overload inherited from the 
superclass, which narrows the bound to a {@code double} before comparing and so 
loses
+     * precision for a {@code BigDecimal} or {@code BigInteger} bound that 
carries more significant digits than a {@code double} can hold. A non-finite
+     * {@link Double} or {@link Float} operand keeps the {@code doubleValue()} 
comparison so the documented infinity behaviour is unchanged.
+     * </p>
+     *
+     * @param value The value validation is being performed on.
+     * @param max   The maximum value.
+     * @return {@code true} if the value is less than or equal to the maximum.
+     */
+    @Override
+    public boolean maxValue(final Number value, final Number max) {
+        return isFinite(value) && isFinite(max) ? 
BigDecimal.valueOf(value.doubleValue()).compareTo(toBigDecimal(max)) <= 0 : 
value.doubleValue() <= max.doubleValue();

Review Comment:
   Right, that was the wrong basis. Going through compareTo(value, max) runs 
the value through toBigDecimal, which for a Float uses Float.toString, so the 
float is compared at its own precision (9.007199E15) rather than the extra 
digits doubleValue() invents when it widens the float. I adjusted the 
FloatValidatorTest expectations to that value.



##########
src/test/java/org/apache/commons/validator/routines/DoubleValidatorTest.java:
##########
@@ -132,6 +134,26 @@ void testDoubleRangeMinMaxNaN() {
         assertFalse(validator.maxValue(Double.NaN, 20), "maxValue() NaN");
     }
 
+    /**
+     * Test the {@link Number} range checks against a bound that carries more 
precision than a {@code double}.
+     * 2^53 is the largest integer with an exact {@code double} 
representation, so 2^53 + 1 cannot be narrowed
+     * onto the value: a value of 2^53 is below a minimum of 2^53 + 1 and 
above a maximum of 2^53 - 0.5.
+     */
+    @Test
+    void testDoubleNumberRangeExactBound() {
+        final DoubleValidator validator = (DoubleValidator) strictValidator;
+        final long maxExactInt = 1L << 53; // 2^53
+        final Double value = Double.valueOf(maxExactInt);
+        final BigInteger above = 
BigInteger.valueOf(maxExactInt).add(BigInteger.ONE); // 2^53 + 1
+        final BigInteger below = 
BigInteger.valueOf(maxExactInt).subtract(BigInteger.ONE); // 2^53 - 1
+        final BigDecimal justBelow = new 
BigDecimal(BigInteger.valueOf(maxExactInt)).subtract(BigDecimal.valueOf(0.5)); 
// 2^53 - 0.5

Review Comment:
   Simplified to BigDecimal.valueOf(maxExactInt) in the Double test. Same 
value, less noise.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to