Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,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) ? compareTo(value, max) <= 0 : value.doubleValue() <= max.doubleValue();
}

/**
* Tests if the value is greater than or equal to a minimum, 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 min The minimum value.
* @return {@code true} if the value is greater than or equal to the minimum.
*/
@Override
public boolean minValue(final Number value, final Number min) {
return isFinite(value) && isFinite(min) ? compareTo(value, min) >= 0 : value.doubleValue() >= min.doubleValue();
}

/**
* Convert the parsed value to a {@code Double}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,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) ? compareTo(value, max) <= 0 : value.doubleValue() <= max.doubleValue();
}

/**
* Tests if the value is greater than or equal to a minimum, 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 min The minimum value.
* @return {@code true} if the value is greater than or equal to the minimum.
*/
@Override
public boolean minValue(final Number value, final Number min) {
return isFinite(value) && isFinite(min) ? compareTo(value, min) >= 0 : value.doubleValue() >= min.doubleValue();
}

/**
* Perform further validation and convert the {@code Number} to
* a {@code Float}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

Expand Down Expand Up @@ -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 = BigDecimal.valueOf(maxExactInt).subtract(BigDecimal.valueOf(0.5)); // 2^53 - 0.5
assertFalse(validator.minValue(value, above), "minValue() bound above value");
assertTrue(validator.minValue(value, below), "minValue() bound below value");
assertFalse(validator.maxValue(value, justBelow), "maxValue() bound below value");
assertTrue(validator.maxValue(value, above), "maxValue() bound above value");
assertFalse(validator.isInRange(value, above, above.add(BigInteger.ONE)), "isInRange() value below range");
}

/**
* Test DoubleValidator validate Methods
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
Expand Down Expand Up @@ -136,6 +138,28 @@ void testFloatRangeMinMaxNaN() {
assertFalse(validator.maxValue(Float.NaN, 20), "maxValue() NaN");
}

/**
* Test the {@link Number} range checks against a bound that carries more precision than a {@code double}.
* A {@code float} of this magnitude holds the exact decimal value 9.007199E15; integer bounds one unit
* either side of it carry sixteen significant digits, more than a {@code double} can distinguish here
* (its values are two apart), so the exact comparison keeps the value below 9.007199E15 + 1 and above
* 9.007199E15 - 0.5 where narrowing the bound to a {@code double} would not.
*/
@Test
void testFloatNumberRangeExactBound() {
final FloatValidator validator = (FloatValidator) strictValidator;
final Float value = Float.valueOf(9.007199E15f); // exact decimal value of the float
final long exact = 9_007_199_000_000_000L;
final BigInteger above = BigInteger.valueOf(exact).add(BigInteger.ONE); // value + 1
final BigInteger below = BigInteger.valueOf(exact).subtract(BigInteger.ONE); // value - 1
final BigDecimal justBelow = BigDecimal.valueOf(exact).subtract(BigDecimal.valueOf(0.5)); // value - 0.5
assertFalse(validator.minValue(value, above), "minValue() bound above value");
assertTrue(validator.minValue(value, below), "minValue() bound below value");
assertFalse(validator.maxValue(value, justBelow), "maxValue() bound below value");
assertTrue(validator.maxValue(value, above), "maxValue() bound above value");
assertFalse(validator.isInRange(value, above, above.add(BigInteger.ONE)), "isInRange() value below range");
}

/**
* Test Float validation for values too small to handle. (slightly different from max/min which are the largest +ve/-ve
*/
Expand Down
Loading