@@ -29,6 +29,7 @@ public function testImmutability(): void
2929 self ::assertNotSame ($ schema , $ schema ->exclusiveMinimum (0.0 ));
3030 self ::assertNotSame ($ schema , $ schema ->exclusiveMaximum (0.0 ));
3131 self ::assertNotSame ($ schema , $ schema ->maximum (0.0 ));
32+ self ::assertNotSame ($ schema , $ schema ->multipleOf (0.1 ));
3233 }
3334
3435 public function testParseSuccess (): void
@@ -367,6 +368,117 @@ public function testParseWithInvalidMaximum(): void
367368 }
368369 }
369370
371+ public function testParseWithValidMultipleOf (): void
372+ {
373+ $ input = 0.3 ;
374+ $ multipleOf = 0.1 ;
375+
376+ $ schema = (new FloatSchema ())->multipleOf ($ multipleOf );
377+
378+ self ::assertSame ($ input , $ schema ->parse ($ input ));
379+ }
380+
381+ public function testParseWithInvalidMultipleOf (): void
382+ {
383+ $ input = 0.35 ;
384+ $ multipleOf = 0.1 ;
385+
386+ $ schema = (new FloatSchema ())->multipleOf ($ multipleOf );
387+
388+ try {
389+ $ schema ->parse ($ input );
390+
391+ throw new \Exception ('code should not be reached ' );
392+ } catch (ErrorsException $ errorsException ) {
393+ self ::assertSame ([
394+ [
395+ 'path ' => '' ,
396+ 'error ' => [
397+ 'code ' => 'float.multipleOf ' ,
398+ 'template ' => 'Value should be multiple of {{multipleOf}}, {{given}} given ' ,
399+ 'variables ' => [
400+ 'multipleOf ' => $ multipleOf ,
401+ 'given ' => $ input ,
402+ ],
403+ ],
404+ ],
405+ ], $ errorsException ->errors ->jsonSerialize ());
406+ }
407+ }
408+
409+ public function testMultipleOfWithInvalidMultipleOf (): void
410+ {
411+ $ this ->expectException (\InvalidArgumentException::class);
412+ $ this ->expectExceptionMessage ('Argument #1 ($multipleOf) must be greater than 0, 0 given ' );
413+
414+ (new FloatSchema ())->multipleOf (0.0 );
415+ }
416+
417+ public function testParseWithValidMultipleOfTrickyFloatingPointValues (): void
418+ {
419+ $ cases = [
420+ [0.3 , 0.1 ],
421+ [0.7 , 0.1 ],
422+ [0.58 , 0.01 ],
423+ [-0.3 , 0.1 ],
424+ [1.0E-12 , 1.0E-13 ],
425+ ];
426+
427+ foreach ($ cases as [$ input , $ multipleOf ]) {
428+ $ schema = (new FloatSchema ())->multipleOf ($ multipleOf );
429+
430+ self ::assertSame ($ input , $ schema ->parse ($ input ));
431+ }
432+ }
433+
434+ public function testParseWithInvalidMultipleOfTrickyFloatingPointValues (): void
435+ {
436+ $ cases = [
437+ [0.3000000000001 , 0.1 ],
438+ [0.3 , 0.2 ],
439+ [1.0E-12 , 3.0E-13 ],
440+ [-0.35 , 0.1 ],
441+ ];
442+
443+ foreach ($ cases as [$ input , $ multipleOf ]) {
444+ $ schema = (new FloatSchema ())->multipleOf ($ multipleOf );
445+
446+ try {
447+ $ schema ->parse ($ input );
448+
449+ throw new \Exception ('code should not be reached ' );
450+ } catch (ErrorsException $ errorsException ) {
451+ self ::assertSame ('float.multipleOf ' , $ errorsException ->errors ->jsonSerialize ()[0 ]['error ' ]['code ' ]);
452+ }
453+ }
454+ }
455+
456+ public function testParseWithValidMultipleOfAtEpsilonBoundary (): void
457+ {
458+ $ multipleOf = 1.0 ;
459+ $ input = 1.0 - 10 * PHP_FLOAT_EPSILON ;
460+
461+ $ schema = (new FloatSchema ())->multipleOf ($ multipleOf );
462+
463+ self ::assertSame ($ input , $ schema ->parse ($ input ));
464+ }
465+
466+ public function testParseWithInvalidMultipleOfJustAboveEpsilonBoundary (): void
467+ {
468+ $ multipleOf = 1.0 ;
469+ $ input = 1.0 - 21 * PHP_FLOAT_EPSILON / 2 ;
470+
471+ $ schema = (new FloatSchema ())->multipleOf ($ multipleOf );
472+
473+ try {
474+ $ schema ->parse ($ input );
475+
476+ throw new \Exception ('code should not be reached ' );
477+ } catch (ErrorsException $ errorsException ) {
478+ self ::assertSame ('float.multipleOf ' , $ errorsException ->errors ->jsonSerialize ()[0 ]['error ' ]['code ' ]);
479+ }
480+ }
481+
370482 public function testParseWithValidGte (): void
371483 {
372484 $ input = 4.1 ;
0 commit comments