Skip to content

Commit ed15f6c

Browse files
authored
Fix float multiple_of validation for negative numbers (#1373)
1 parent e89bd8c commit ed15f6c

File tree

3 files changed

+64
-23
lines changed

3 files changed

+64
-23
lines changed

src/validators/float.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,10 @@ impl Validator for ConstrainedFloatValidator {
109109
return Err(ValError::new(ErrorTypeDefaults::FiniteNumber, input));
110110
}
111111
if let Some(multiple_of) = self.multiple_of {
112-
let rem = float % multiple_of;
113-
let threshold = float.abs() / 1e9;
114-
if rem.abs() > threshold && (rem - multiple_of).abs() > threshold {
112+
let tolerance = 1e-9;
113+
let rounded_div = (float / multiple_of).round();
114+
let diff = (float - (rounded_div * multiple_of)).abs();
115+
if diff > tolerance {
115116
return Err(ValError::new(
116117
ErrorType::MultipleOf {
117118
multiple_of: multiple_of.into(),

tests/validators/test_decimal.py

+30-10
Original file line numberDiff line numberDiff line change
@@ -171,24 +171,44 @@ def test_decimal_kwargs(py_and_json: PyAndJson, kwargs: dict[str, Any], input_va
171171
@pytest.mark.parametrize(
172172
'multiple_of,input_value,error',
173173
[
174-
(0.5, 0.5, None),
175-
(0.5, 1, None),
174+
# Test cases for multiples of 0.5
175+
*[(0.5, round(i * 0.5, 1), None) for i in range(-4, 5)],
176+
(0.5, 0.49, Err('Input should be a multiple of 0.5')),
176177
(0.5, 0.6, Err('Input should be a multiple of 0.5')),
177-
(0.5, 0.51, Err('Input should be a multiple of 0.5')),
178+
(0.5, -0.75, Err('Input should be a multiple of 0.5')),
178179
(0.5, 0.501, Err('Input should be a multiple of 0.5')),
179180
(0.5, 1_000_000.5, None),
180181
(0.5, 1_000_000.49, Err('Input should be a multiple of 0.5')),
182+
(0.5, int(5e10), None),
183+
# Test cases for multiples of 0.1
184+
*[(0.1, round(i * 0.1, 1), None) for i in range(-10, 11)],
181185
(0.1, 0, None),
182-
(0.1, 0.0, None),
183-
(0.1, 0.2, None),
184-
(0.1, 0.3, None),
185-
(0.1, 0.4, None),
186-
(0.1, 0.5, None),
187186
(0.1, 0.5001, Err('Input should be a multiple of 0.1')),
187+
(0.1, 0.05, Err('Input should be a multiple of 0.1')),
188+
(0.1, -0.15, Err('Input should be a multiple of 0.1')),
189+
(0.1, 1_000_000.1, None),
190+
(0.1, 1_000_000.05, Err('Input should be a multiple of 0.1')),
188191
(0.1, 1, None),
189-
(0.1, 1.0, None),
190192
(0.1, int(5e10), None),
191-
(2.0, -2.0, None),
193+
# Test cases for multiples of 2.0
194+
*[(2.0, i * 2.0, None) for i in range(-5, 6)],
195+
(2.0, -2.1, Err('Input should be a multiple of 2')),
196+
(2.0, -3.0, Err('Input should be a multiple of 2')),
197+
(2.0, 1_000_002.0, None),
198+
(2.0, 1_000_001.0, Err('Input should be a multiple of 2')),
199+
(2.0, int(5e10), None),
200+
# Test cases for multiples of 0.01
201+
*[(0.01, round(i * 0.01, 2), None) for i in range(-10, 11)],
202+
(0.01, 0.005, Err('Input should be a multiple of 0.01')),
203+
(0.01, -0.015, Err('Input should be a multiple of 0.01')),
204+
(0.01, 1_000_000.01, None),
205+
(0.01, 1_000_000.005, Err('Input should be a multiple of 0.01')),
206+
(0.01, int(5e10), None),
207+
# Test cases for values very close to zero
208+
(0.1, 0.00001, Err('Input should be a multiple of 0.1')),
209+
(0.1, -0.00001, Err('Input should be a multiple of 0.1')),
210+
(0.01, 0.00001, Err('Input should be a multiple of 0.01')),
211+
(0.01, -0.00001, Err('Input should be a multiple of 0.01')),
192212
],
193213
ids=repr,
194214
)

tests/validators/test_float.py

+30-10
Original file line numberDiff line numberDiff line change
@@ -105,24 +105,44 @@ def test_float_kwargs(py_and_json: PyAndJson, kwargs: dict[str, Any], input_valu
105105
@pytest.mark.parametrize(
106106
'multiple_of,input_value,error',
107107
[
108-
(0.5, 0.5, None),
109-
(0.5, 1, None),
108+
# Test cases for multiples of 0.5
109+
*[(0.5, round(i * 0.5, 1), None) for i in range(-4, 5)],
110+
(0.5, 0.49, Err('Input should be a multiple of 0.5')),
110111
(0.5, 0.6, Err('Input should be a multiple of 0.5')),
111-
(0.5, 0.51, Err('Input should be a multiple of 0.5')),
112+
(0.5, -0.75, Err('Input should be a multiple of 0.5')),
112113
(0.5, 0.501, Err('Input should be a multiple of 0.5')),
113114
(0.5, 1_000_000.5, None),
114115
(0.5, 1_000_000.49, Err('Input should be a multiple of 0.5')),
116+
(0.5, int(5e10), None),
117+
# Test cases for multiples of 0.1
118+
*[(0.1, round(i * 0.1, 1), None) for i in range(-10, 11)],
115119
(0.1, 0, None),
116-
(0.1, 0.0, None),
117-
(0.1, 0.2, None),
118-
(0.1, 0.3, None),
119-
(0.1, 0.4, None),
120-
(0.1, 0.5, None),
121120
(0.1, 0.5001, Err('Input should be a multiple of 0.1')),
121+
(0.1, 0.05, Err('Input should be a multiple of 0.1')),
122+
(0.1, -0.15, Err('Input should be a multiple of 0.1')),
123+
(0.1, 1_000_000.1, None),
124+
(0.1, 1_000_000.05, Err('Input should be a multiple of 0.1')),
122125
(0.1, 1, None),
123-
(0.1, 1.0, None),
124126
(0.1, int(5e10), None),
125-
(2.0, -2.0, None),
127+
# Test cases for multiples of 2.0
128+
*[(2.0, i * 2.0, None) for i in range(-5, 6)],
129+
(2.0, -2.1, Err('Input should be a multiple of 2')),
130+
(2.0, -3.0, Err('Input should be a multiple of 2')),
131+
(2.0, 1_000_002.0, None),
132+
(2.0, 1_000_001.0, Err('Input should be a multiple of 2')),
133+
(2.0, int(5e10), None),
134+
# Test cases for multiples of 0.01
135+
*[(0.01, round(i * 0.01, 2), None) for i in range(-10, 11)],
136+
(0.01, 0.005, Err('Input should be a multiple of 0.01')),
137+
(0.01, -0.015, Err('Input should be a multiple of 0.01')),
138+
(0.01, 1_000_000.01, None),
139+
(0.01, 1_000_000.005, Err('Input should be a multiple of 0.01')),
140+
(0.01, int(5e10), None),
141+
# Test cases for values very close to zero
142+
(0.1, 0.00001, Err('Input should be a multiple of 0.1')),
143+
(0.1, -0.00001, Err('Input should be a multiple of 0.1')),
144+
(0.01, 0.00001, Err('Input should be a multiple of 0.01')),
145+
(0.01, -0.00001, Err('Input should be a multiple of 0.01')),
126146
],
127147
ids=repr,
128148
)

0 commit comments

Comments
 (0)