11package ch .njol .skript .conditions ;
22
33import ch .njol .skript .Skript ;
4+ import ch .njol .skript .config .Node ;
45import ch .njol .skript .doc .Description ;
56import ch .njol .skript .doc .Examples ;
67import ch .njol .skript .doc .Name ;
1112import ch .njol .util .Kleenean ;
1213import org .bukkit .event .Event ;
1314import org .jetbrains .annotations .Nullable ;
15+ import org .skriptlang .skript .log .runtime .SyntaxRuntimeErrorProducer ;
1416
1517@ Name ("Is Evenly Divisible By" )
16- @ Description ("Check if a number is evenly divisible by another number." )
18+ @ Description ("""
19+ Checks if a number is evenly divisible by another number.
20+ An optional tolerance can be provided to counteract floating point error. The default tolerance is 1e-10.
21+ Any input smaller than the tolerance is considered to be 0.
22+ This means divisors that are too small will always return false, and dividends that are too small will always return true.
23+ """ )
1724@ Examples ({
1825 "if 5 is evenly divisible by 5:" ,
1926 "if 11 cannot be evenly divided by 10:" ,
27+ "if 0.3 can be evenly divided by 0.1 with a tolerance of 0.0000001:"
2028})
21- @ Since ("2.10" )
22- public class CondIsDivisibleBy extends Condition {
29+ @ Since ("2.10, INSERT VERSION (tolerance) " )
30+ public class CondIsDivisibleBy extends Condition implements SyntaxRuntimeErrorProducer {
2331
2432 static {
2533 Skript .registerCondition (CondIsDivisibleBy .class ,
26- "%numbers% (is|are) evenly divisible by %number%" ,
27- "%numbers% (isn't|is not|aren't|are not) evenly divisible by %number%" ,
28- "%numbers% can be evenly divided by %number%" ,
29- "%numbers% (can't|can[ ]not) be evenly divided by %number%" );
34+ "%numbers% (is|are) evenly divisible by %number% [with [a] tolerance [of] %-number%] " ,
35+ "%numbers% (isn't|is not|aren't|are not) evenly divisible by %number% [with [a] tolerance [of] %-number%] " ,
36+ "%numbers% can be evenly divided by %number% [with [a] tolerance [of] %-number%] " ,
37+ "%numbers% (can't|can[ ]not) be evenly divided by %number% [with [a] tolerance [of] %-number%] " );
3038 }
31- @ SuppressWarnings ( "null" )
39+
3240 private Expression <Number > dividend ;
33- @ SuppressWarnings ("null" )
3441 private Expression <Number > divisor ;
42+ private @ Nullable Expression <Number > epsilon ;
43+ private Node node ;
3544
3645 @ Override
46+ @ SuppressWarnings ("unchecked" )
3747 public boolean init (Expression <?>[] exprs , int matchedPattern , Kleenean isDelayed , ParseResult parseResult ) {
3848 dividend = (Expression <Number >) exprs [0 ];
3949 divisor = (Expression <Number >) exprs [1 ];
50+ epsilon = (Expression <Number >) exprs [2 ];
4051 setNegated (matchedPattern == 1 || matchedPattern == 3 );
52+ node = getParser ().getNode ();
4153 return true ;
4254 }
4355
@@ -47,7 +59,36 @@ public boolean check(Event event) {
4759 if (divisorNumber == null )
4860 return isNegated ();
4961 double divisor = divisorNumber .doubleValue ();
50- return dividend .check (event , dividendNumber -> (dividendNumber .doubleValue () % divisor == 0 ), isNegated ());
62+ if (divisor == 0 ) {
63+ // Division by zero never passes
64+ return isNegated ();
65+ }
66+
67+ Number epsilonNumber = epsilon != null ? epsilon .getSingle (event ) : Skript .EPSILON ;
68+ if (epsilonNumber == null ) {
69+ epsilonNumber = Skript .EPSILON ;
70+ }
71+ double epsilon = epsilonNumber .doubleValue ();
72+
73+ if (epsilon <= 0 || Double .isNaN (epsilon )) {
74+ error ("Tolerance must be a positive, non-zero number, but was " + epsilonNumber + "." );
75+ return isNegated ();
76+ }
77+
78+ if (divisor < epsilon ) {
79+ // If the divisor is smaller than the tolerance, we consider it to be 0
80+ return isNegated ();
81+ }
82+
83+ return dividend .check (event , dividendNumber -> {
84+ double remainder = Math .abs (dividendNumber .doubleValue () % divisor );
85+ return remainder <= epsilon || remainder >= divisor - epsilon ;
86+ }, isNegated ());
87+ }
88+
89+ @ Override
90+ public Node getNode () {
91+ return node ;
5192 }
5293
5394 @ Override
0 commit comments