@@ -41,6 +41,8 @@ public class BigInterval {
41
41
public BigInterval (final BigInteger minValue , final BigInteger maxValue ) {
42
42
mMinValue = minValue ;
43
43
mMaxValue = maxValue ;
44
+ assert mMinValue == null || mMaxValue == null
45
+ || maxValue .compareTo (minValue ) >= 0 : "empty interval not supported" ;
44
46
}
45
47
46
48
public static BigInterval unbounded () {
@@ -99,6 +101,24 @@ public boolean contains(final BigInteger element) {
99
101
return minBoundOk && maxBoundOk ;
100
102
}
101
103
104
+ public BigInterval join (final BigInterval other ) {
105
+ final var minValue = mMinValue == null || other .mMinValue == null ? null : mMinValue .min (other .mMinValue );
106
+ final var maxValue = mMaxValue == null || other .mMaxValue == null ? null : mMaxValue .max (other .mMaxValue );
107
+ return new BigInterval (minValue , maxValue );
108
+ }
109
+
110
+ public BigInterval intersect (final BigInterval other ) {
111
+ final var minValue = mMinValue == null ? other .mMinValue
112
+ : other .mMinValue == null ? mMinValue : mMinValue .max (other .mMinValue );
113
+ final var maxValue = mMaxValue == null ? other .mMaxValue
114
+ : other .mMaxValue == null ? mMaxValue : mMaxValue .min (other .mMaxValue );
115
+ if (maxValue .compareTo (mMinValue ) < 0 ) {
116
+ // empty interval not supported
117
+ return null ;
118
+ }
119
+ return new BigInterval (minValue , maxValue );
120
+ }
121
+
102
122
public BigInterval negate () {
103
123
final var minValue = mMaxValue == null ? null : mMaxValue .negate ();
104
124
final var maxValue = mMinValue == null ? null : mMinValue .negate ();
@@ -232,4 +252,47 @@ public BigInterval euclideanModulo(final BigInteger divisor) {
232
252
// k*divisor-1 (modulo will be divisor-1), so the smallest interval encompassing all values is the entire range.
233
253
return new BigInterval (BigInteger .ZERO , absDivisor .subtract (BigInteger .ONE ));
234
254
}
255
+
256
+ // Code adapted from https://stackoverflow.com/a/56918042 (there shown for truncating remainder)
257
+ public BigInterval euclideanModulo (final BigInterval divisor ) {
258
+ if (divisor .contains (BigInteger .ZERO )) {
259
+ // Modulo resp. division by zero is unspecified, so we assume it can yield any number.
260
+ return unbounded ();
261
+ }
262
+
263
+ if (divisor .isSingleton ()) {
264
+ return euclideanModulo (divisor .mMinValue );
265
+ }
266
+
267
+ // For euclidean modulo, divisors D and (- D) yield the same result. So we take the absolute value.
268
+ // (https://www.microsoft.com/en-us/research/publication/division-and-modulus-for-computer-scientists/)
269
+ final var absDivisor = divisor .abs ();
270
+
271
+ final var length = length ();
272
+ if (length != null && absDivisor .mMaxValue != null && length .compareTo (absDivisor .mMaxValue ) >= 0 ) {
273
+ // If length is greater than the largest possible divisor, the interval contains all possible mod values.
274
+ return new BigInterval (BigInteger .ZERO , absDivisor .mMaxValue .subtract (BigInteger .ONE ));
275
+ }
276
+ if (length != null && length .compareTo (absDivisor .mMinValue ) >= 0 ) {
277
+ // If the length is somewhere inside the interval of possible divisors, we split into two cases:
278
+ // all divisors up to length, and all greater divisors.
279
+ // For divisors up to length, we know (as in the previous case) that we can get all possible mod values (for
280
+ // the new sub-interval of divisors); for divisors greater than length, we use a recursive call.
281
+ // (NOTE that the recursion depth should be limited to 2, as length is outside the new sub-interval.)
282
+ final var lowerHalf = new BigInterval (BigInteger .ZERO , length .subtract (BigInteger .ONE ));
283
+ final var upperHalf = euclideanModulo (new BigInterval (length .add (BigInteger .ONE ), absDivisor .mMaxValue ));
284
+ return lowerHalf .join (upperHalf );
285
+ }
286
+ if (absDivisor .mMinValue .compareTo (mMaxValue ) > 0 ) {
287
+ // trivial case: modulo is guaranteed to have no effect
288
+ return this ;
289
+ }
290
+ if (absDivisor .mMaxValue != null && absDivisor .mMaxValue .compareTo (mMaxValue ) > 0 ) {
291
+ return new BigInterval (BigInteger .ZERO , mMaxValue );
292
+ }
293
+
294
+ // fallback to imprecise approximation
295
+ return new BigInterval (BigInteger .ZERO ,
296
+ absDivisor .mMaxValue == null ? null : absDivisor .mMaxValue .subtract (BigInteger .ONE ));
297
+ }
235
298
}
0 commit comments