|
18 | 18 | import com.retailsvc.http.spec.schema.Schema; |
19 | 19 | import com.retailsvc.http.spec.schema.StringSchema; |
20 | 20 | import com.retailsvc.http.spec.schema.TypeName; |
| 21 | +import java.math.BigDecimal; |
21 | 22 | import java.time.LocalDate; |
22 | 23 | import java.time.OffsetDateTime; |
23 | 24 | import java.util.ArrayList; |
@@ -175,11 +176,22 @@ private void validateNumber(Object value, NumberSchema s, String pointer) { |
175 | 176 | if (s.exclusiveMaximum() != null && n >= s.exclusiveMaximum().doubleValue()) { |
176 | 177 | fail(pointer, "exclusiveMaximum", "number not less than " + s.exclusiveMaximum(), n); |
177 | 178 | } |
178 | | - if (s.multipleOf() != null && (n / s.multipleOf().doubleValue()) % 1 != 0) { |
| 179 | + if (s.multipleOf() != null && !isMultipleOf(n, s.multipleOf().doubleValue())) { |
179 | 180 | fail(pointer, "multipleOf", "not a multiple of " + s.multipleOf(), n); |
180 | 181 | } |
181 | 182 | } |
182 | 183 |
|
| 184 | + /** |
| 185 | + * Returns whether {@code value} is an exact multiple of {@code divisor}, using {@link BigDecimal} |
| 186 | + * to avoid floating-point rounding artifacts that {@code (value / divisor) % 1 == 0} would |
| 187 | + * produce (e.g., {@code 0.3 / 0.1} is not exactly {@code 3.0} as a double). |
| 188 | + */ |
| 189 | + private static boolean isMultipleOf(double value, double divisor) { |
| 190 | + BigDecimal v = BigDecimal.valueOf(value); |
| 191 | + BigDecimal d = BigDecimal.valueOf(divisor); |
| 192 | + return v.remainder(d).compareTo(BigDecimal.ZERO) == 0; |
| 193 | + } |
| 194 | + |
183 | 195 | @SuppressWarnings("unchecked") |
184 | 196 | private void validateObject(Object value, ObjectSchema s, String pointer) { |
185 | 197 | require(value instanceof Map, pointer, "type", "expected object"); |
|
0 commit comments