Skip to content

Commit cc41c28

Browse files
authored
Fix multipleOf bugs (#114)
* Fix bug when using multipleOf values with decimal places * Allow resetting multipleOf back to null * Add tests for multipleOf bugs * Fix incorrect formatting of multipleOf in error message - `%d` casts the multipleOf to a whole number even if its a float with decimals - `%f` would add unnecessary decimals, e.g. `0.01` would become `0.010000` - `%s` maintains the same number of decimals, e.g. `0.01` stays `0.01` --------- Co-authored-by: bertramakers <[email protected]>
1 parent e913512 commit cc41c28

File tree

2 files changed

+27
-3
lines changed

2 files changed

+27
-3
lines changed

src/Schema/Type/Number.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,16 @@ public function validate(mixed $value, callable $fail): void
5050
}
5151
}
5252

53-
if ($this->multipleOf !== null && $value % $this->multipleOf !== 0) {
54-
$fail(sprintf('must be a multiple of %d', $this->multipleOf));
53+
// Divide the value by multipleOf instead of using the modulo operator to avoid bugs when using a multipleOf
54+
// that has decimal places. (Since the modulo operator converts the multipleOf to int)
55+
// Note that dividing two integers returns another integer if the result is a whole number. So to make the
56+
// comparison work at all times we need to cast the result to float. Casting both to integer will not work
57+
// as intended since then the result of the division would also be rounded.
58+
if (
59+
$this->multipleOf !== null &&
60+
(float) ($value / $this->multipleOf) !== round($value / $this->multipleOf)
61+
) {
62+
$fail(sprintf('must be a multiple of %s', $this->multipleOf));
5563
}
5664
}
5765

@@ -85,7 +93,7 @@ public function maximum(?float $maximum, bool $exclusive = false): static
8593

8694
public function multipleOf(?float $number): static
8795
{
88-
if ($number <= 0) {
96+
if ($number !== null && $number <= 0) {
8997
throw new InvalidArgumentException('multipleOf must be a positive number');
9098
}
9199

tests/unit/NumberTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ public static function validationProvider(): array
4040
[Number::make()->maximum(10, exclusive: true), 10, false],
4141
[Number::make()->multipleOf(2), 1, false],
4242
[Number::make()->multipleOf(2), 2, true],
43+
[Number::make()->multipleOf(0.01), 100, true],
44+
[Number::make()->multipleOf(0.01), 100.5, true],
45+
[Number::make()->multipleOf(0.01), 100.56, true],
46+
[Number::make()->multipleOf(0.01), 100.567, false],
4347
];
4448
}
4549

@@ -56,4 +60,16 @@ public function test_validation(Type $type, mixed $value, bool $valid)
5660

5761
$type->validate($value, $fail);
5862
}
63+
64+
public function test_multipleOf_reset(): void
65+
{
66+
$number = Number::make()
67+
->multipleOf(2)
68+
->multipleOf(null);
69+
70+
$fail = $this->createMock(MockedCaller::class);
71+
$fail->expects($this->never())->method('__invoke');
72+
73+
$number->validate(5, $fail);
74+
}
5975
}

0 commit comments

Comments
 (0)