Skip to content

Commit 23273bb

Browse files
authored
Merge pull request #410 from sahvx655-wq/double-float-exact-bound
Compare exact values in DoubleValidator range checks
2 parents 2deb0a9 + 84ab5c3 commit 23273bb

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

src/main/java/org/apache/commons/validator/routines/DoubleValidator.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,42 @@ public boolean minValue(final Double value, final double min) {
183183
return minValue(value.doubleValue(), min);
184184
}
185185

186+
/**
187+
* Tests if the value is less than or equal to a maximum, comparing the exact values.
188+
*
189+
* <p>
190+
* This overrides the {@link Number} overload inherited from the superclass, which narrows the bound to a {@code double} before comparing and so loses
191+
* precision for a {@code BigDecimal} or {@code BigInteger} bound that carries more significant digits than a {@code double} can hold. A non-finite
192+
* {@link Double} or {@link Float} operand keeps the {@code doubleValue()} comparison so the documented infinity behaviour is unchanged.
193+
* </p>
194+
*
195+
* @param value The value validation is being performed on.
196+
* @param max The maximum value.
197+
* @return {@code true} if the value is less than or equal to the maximum.
198+
*/
199+
@Override
200+
public boolean maxValue(final Number value, final Number max) {
201+
return isFinite(value) && isFinite(max) ? compareTo(value, max) <= 0 : value.doubleValue() <= max.doubleValue();
202+
}
203+
204+
/**
205+
* Tests if the value is greater than or equal to a minimum, comparing the exact values.
206+
*
207+
* <p>
208+
* This overrides the {@link Number} overload inherited from the superclass, which narrows the bound to a {@code double} before comparing and so loses
209+
* precision for a {@code BigDecimal} or {@code BigInteger} bound that carries more significant digits than a {@code double} can hold. A non-finite
210+
* {@link Double} or {@link Float} operand keeps the {@code doubleValue()} comparison so the documented infinity behaviour is unchanged.
211+
* </p>
212+
*
213+
* @param value The value validation is being performed on.
214+
* @param min The minimum value.
215+
* @return {@code true} if the value is greater than or equal to the minimum.
216+
*/
217+
@Override
218+
public boolean minValue(final Number value, final Number min) {
219+
return isFinite(value) && isFinite(min) ? compareTo(value, min) >= 0 : value.doubleValue() >= min.doubleValue();
220+
}
221+
186222
/**
187223
* Convert the parsed value to a {@code Double}.
188224
*

src/test/java/org/apache/commons/validator/routines/DoubleValidatorTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import static org.junit.jupiter.api.Assertions.assertNull;
2323
import static org.junit.jupiter.api.Assertions.assertTrue;
2424

25+
import java.math.BigDecimal;
26+
import java.math.BigInteger;
2527
import java.text.DecimalFormatSymbols;
2628
import java.util.Locale;
2729

@@ -132,6 +134,26 @@ void testDoubleRangeMinMaxNaN() {
132134
assertFalse(validator.maxValue(Double.NaN, 20), "maxValue() NaN");
133135
}
134136

137+
/**
138+
* Test the {@link Number} range checks against a bound that carries more precision than a {@code double}.
139+
* {@code 2^53} is the largest integer with an exact {@code double} representation, so {@code 2^53} + 1 cannot be narrowed
140+
* onto the value: a value of {@code 2^53} is below a minimum of {@code 2^53 + 1} and above a maximum of {@code 2^53 - 0.5}.
141+
*/
142+
@Test
143+
void testDoubleNumberRangeExactBound() {
144+
final DoubleValidator validator = (DoubleValidator) strictValidator;
145+
final long maxExactInt = 1L << 53; // 2^53
146+
final Double value = Double.valueOf(maxExactInt);
147+
final BigInteger above = BigInteger.valueOf(maxExactInt).add(BigInteger.ONE); // 2^53 + 1
148+
final BigInteger below = BigInteger.valueOf(maxExactInt).subtract(BigInteger.ONE); // 2^53 - 1
149+
final BigDecimal justBelow = BigDecimal.valueOf(maxExactInt).subtract(BigDecimal.valueOf(0.5)); // 2^53 - 0.5
150+
assertFalse(validator.minValue(value, above), "minValue() bound above value");
151+
assertTrue(validator.minValue(value, below), "minValue() bound below value");
152+
assertFalse(validator.maxValue(value, justBelow), "maxValue() bound below value");
153+
assertTrue(validator.maxValue(value, above), "maxValue() bound above value");
154+
assertFalse(validator.isInRange(value, above, above.add(BigInteger.ONE)), "isInRange() value below range");
155+
}
156+
135157
/**
136158
* Test DoubleValidator validate Methods
137159
*/

0 commit comments

Comments
 (0)