/*
* Copyright (C) 2024 Dominik Klumpp (klumpp@informatik.uni-freiburg.de)
* Copyright (C) 2024 University of Freiburg
*
* This file is part of the ULTIMATE Util Library.
*
* The ULTIMATE Util Library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ULTIMATE Util Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ULTIMATE Util Library. If not, see .
*
* Additional permission under GNU GPL version 3 section 7:
* If you modify the ULTIMATE Util Library, or any covered work, by linking
* or combining it with Eclipse RCP (or a modified version of Eclipse RCP),
* containing parts covered by the terms of the Eclipse Public License, the
* licensors of the ULTIMATE Util Library grant you additional permission
* to convey the resulting work.
*/
package de.uni_freiburg.informatik.ultimate.util.datastructures;
import java.math.BigInteger;
import java.util.List;
/**
* Represents a non-empty interval of {@link BigInteger}s.
*
* @author Dominik Klumpp (klumpp@informatik.uni-freiburg.de)
*/
public class BigInterval {
private final BigInteger mMinValue;
private final BigInteger mMaxValue;
public BigInterval(final BigInteger minValue, final BigInteger maxValue) {
mMinValue = minValue;
mMaxValue = maxValue;
assert mMinValue == null || mMaxValue == null
|| maxValue.compareTo(minValue) >= 0 : "empty interval not supported";
}
public static BigInterval unbounded() {
return new BigInterval(null, null);
}
public static BigInterval booleanRange() {
return new BigInterval(BigInteger.ZERO, BigInteger.ONE);
}
public static BigInterval singleton(final BigInteger value) {
return new BigInterval(value, value);
}
/**
* @return the minimum value in the interval, or {@code null} is the interval is unbounded from below
*/
public BigInteger getMinValue() {
return mMinValue;
}
/**
* @return the maximum value in the interval, or {@code null} is the interval is unbounded from above
*/
public BigInteger getMaxValue() {
return mMaxValue;
}
public BigInteger length() {
return mMinValue != null && mMaxValue != null ? mMaxValue.subtract(mMinValue).add(BigInteger.ONE) : null;
}
public boolean isSingleton() {
return mMinValue != null && mMaxValue != null && mMinValue.equals(mMaxValue);
}
public boolean isStrictlyPositive() {
return mMinValue != null && mMinValue.signum() == 1;
}
public boolean isStrictlyNegative() {
return mMaxValue != null && mMaxValue.signum() == -1;
}
public boolean isStrictlyNonNegative() {
return mMinValue != null && mMinValue.signum() >= 0;
}
public boolean isStrictlyNonPositive() {
return mMaxValue != null && mMaxValue.signum() <= 0;
}
public boolean isZero() {
return isSingleton() && mMinValue.signum() == 0;
}
public boolean contains(final BigInterval subset) {
final boolean minBoundOk =
mMinValue == null || (subset.mMinValue != null && mMinValue.compareTo(subset.mMinValue) <= 0);
final boolean maxBoundOk =
mMaxValue == null || (subset.mMaxValue != null && mMaxValue.compareTo(subset.mMaxValue) >= 0);
return minBoundOk && maxBoundOk;
}
public boolean contains(final BigInteger element) {
final boolean minBoundOk = mMinValue == null || mMinValue.compareTo(element) <= 0;
final boolean maxBoundOk = mMaxValue == null || mMaxValue.compareTo(element) >= 0;
return minBoundOk && maxBoundOk;
}
public BigInterval join(final BigInterval other) {
final var minValue = mMinValue == null || other.mMinValue == null ? null : mMinValue.min(other.mMinValue);
final var maxValue = mMaxValue == null || other.mMaxValue == null ? null : mMaxValue.max(other.mMaxValue);
return new BigInterval(minValue, maxValue);
}
public BigInterval intersect(final BigInterval other) {
final var minValue = mMinValue == null ? other.mMinValue
: other.mMinValue == null ? mMinValue : mMinValue.max(other.mMinValue);
final var maxValue = mMaxValue == null ? other.mMaxValue
: other.mMaxValue == null ? mMaxValue : mMaxValue.min(other.mMaxValue);
if (maxValue.compareTo(mMinValue) < 0) {
// empty interval not supported
return null;
}
return new BigInterval(minValue, maxValue);
}
public BigInterval negate() {
final var minValue = mMaxValue == null ? null : mMaxValue.negate();
final var maxValue = mMinValue == null ? null : mMinValue.negate();
return new BigInterval(minValue, maxValue);
}
public BigInterval abs() {
if (mMinValue != null && mMinValue.signum() >= 0) {
// zero-or-positive interval
return this;
}
if (mMaxValue != null && mMaxValue.signum() < 0) {
// strictly negative interval
return new BigInterval(mMaxValue.abs(), mMinValue == null ? null : mMinValue.abs());
}
assert contains(BigInteger.ZERO);
final var maxValue = mMinValue == null || mMaxValue == null ? null
: mMinValue.abs().compareTo(mMaxValue) >= 1 ? mMinValue.abs() : mMaxValue;
return new BigInterval(BigInteger.ZERO, maxValue);
}
public BigInterval add(final BigInterval other) {
final var minValue = mMinValue == null || other.getMinValue() == null ? null : mMinValue.add(other.mMinValue);
final var maxValue = mMaxValue == null || other.getMaxValue() == null ? null : mMaxValue.add(other.mMaxValue);
return new BigInterval(minValue, maxValue);
}
public BigInterval subtract(final BigInterval subtrahend) {
final var minValue =
mMinValue == null || subtrahend.mMaxValue == null ? null : mMinValue.subtract(subtrahend.mMaxValue);
final var maxValue =
mMaxValue == null || subtrahend.mMinValue == null ? null : mMaxValue.subtract(subtrahend.mMinValue);
return new BigInterval(minValue, maxValue);
}
public BigInterval multiply(final BigInterval other) {
if (mMinValue == null || mMaxValue == null || other.mMinValue == null || other.mMaxValue == null) {
return BigInterval.unbounded();
}
final List results =
List.of(mMinValue.multiply(other.mMinValue), mMinValue.multiply(other.mMaxValue),
mMaxValue.multiply(other.mMinValue), mMaxValue.multiply(other.mMaxValue));
final var minValue = results.stream().min(BigInteger::compareTo).get();
final var maxValue = results.stream().max(BigInteger::compareTo).get();
return new BigInterval(minValue, maxValue);
}
public BigInterval euclideanDivide(final BigInterval divisor) {
if (divisor.contains(BigInteger.ZERO)) {
// Division by zero is unspecified, so we assume it can yield any number.
return unbounded();
}
if (divisor.isStrictlyNegative()) {
// Use property of euclidean division for negative divisors
// (https://www.microsoft.com/en-us/research/publication/division-and-modulus-for-computer-scientists/)
return euclideanDivide(divisor.negate()).negate();
}
assert divisor.isStrictlyPositive();
final BigInteger minValue;
if (mMinValue == null) {
// If this interval is unbounded from below, the quotient interval (with positive divisor) will be as well.
minValue = null;
} else if (mMinValue.signum() < 0) {
// If the lower bound is negative, divide it by the smallest possible (positive) value to get the new bound.
minValue = euclideanDivide(mMinValue, divisor.mMinValue);
} else if (divisor.mMaxValue != null) {
// If the lower bound is positive or zero, divide it by the largest possible (positive) value.
// This requires of course that there is a largest possible value in the divisor.
minValue = euclideanDivide(mMinValue, divisor.mMaxValue);
} else {
// Final case: if the divisor is unbounded from above, the quotient can be as small as zero
// (e.g. by taking the divisor to be mMinValue+1)
minValue = BigInteger.ZERO;
}
final BigInteger maxValue;
if (mMaxValue == null) {
// If this interval is unbounded from below, the quotient interval (with positive divisor) will be as well.
maxValue = null;
} else if (mMaxValue.signum() >= 0) {
// If the upper bound is positive or zero, divide it by the smallest possible (positive) value.
maxValue = euclideanDivide(mMaxValue, divisor.mMinValue);
} else if (divisor.mMaxValue != null) {
// If the upper bound is negative, divide it by the largest possible (positive) value.
// This requires of course that there is a largest possible value in the divisor.
maxValue = euclideanDivide(mMaxValue, divisor.mMaxValue);
} else {
// Final case: if the divisor is unbounded from above, the quotient can be as large as zero
// (e.g. by taking the divisor to be |mMaxValue|+1)
maxValue = BigInteger.ZERO;
}
return new BigInterval(minValue, maxValue);
}
private static BigInteger euclideanDivide(final BigInteger dividend, final BigInteger divisor) {
assert !BigInteger.ZERO.equals(divisor) : "divisor ZERO not supported";
// https://www.microsoft.com/en-us/research/publication/division-and-modulus-for-computer-scientists/
final var quotient = dividend.divide(divisor);
if (dividend.signum() >= 0) {
return quotient;
}
if (quotient.signum() > 0) {
return quotient.subtract(BigInteger.ONE);
}
return quotient.add(BigInteger.ONE);
}
public BigInterval euclideanModulo(final BigInteger divisor) {
assert !BigInteger.ZERO.equals(divisor) : "divisor ZERO not supported";
// For euclidean modulo, divisors D and (- D) yield the same result. So we take the absolute value.
// (https://www.microsoft.com/en-us/research/publication/division-and-modulus-for-computer-scientists/)
final var absDivisor = divisor.abs();
final var length = length();
if (length != null && length.compareTo(absDivisor) < 0) {
final var lowerMod = mMinValue.mod(absDivisor);
final var upperMod = mMaxValue.mod(absDivisor);
if (upperMod.compareTo(lowerMod) >= 0) {
return new BigInterval(lowerMod, upperMod);
}
}
// If the interval has infinite length or finite length >= divisor, we get the entire range for modulo.
// Alternatively, the interval might have length < divisor, but contains both a k*divisor (modulo will be 0) and
// k*divisor-1 (modulo will be divisor-1), so the smallest interval encompassing all values is the entire range.
return new BigInterval(BigInteger.ZERO, absDivisor.subtract(BigInteger.ONE));
}
// Code adapted from https://stackoverflow.com/a/56918042 (there shown for truncating remainder)
public BigInterval euclideanModulo(final BigInterval divisor) {
if (divisor.contains(BigInteger.ZERO)) {
// Modulo resp. division by zero is unspecified, so we assume it can yield any number.
return unbounded();
}
if (divisor.isSingleton()) {
return euclideanModulo(divisor.mMinValue);
}
// For euclidean modulo, divisors D and (- D) yield the same result. So we take the absolute value.
// (https://www.microsoft.com/en-us/research/publication/division-and-modulus-for-computer-scientists/)
final var absDivisor = divisor.abs();
final var length = length();
if (length != null && absDivisor.mMaxValue != null && length.compareTo(absDivisor.mMaxValue) >= 0) {
// If length is greater than the largest possible divisor, the interval contains all possible mod values.
return new BigInterval(BigInteger.ZERO, absDivisor.mMaxValue.subtract(BigInteger.ONE));
}
if (length != null && length.compareTo(absDivisor.mMinValue) >= 0) {
// If the length is somewhere inside the interval of possible divisors, we split into two cases:
// all divisors up to length, and all greater divisors.
// For divisors up to length, we know (as in the previous case) that we can get all possible mod values (for
// the new sub-interval of divisors); for divisors greater than length, we use a recursive call.
// (NOTE that the recursion depth should be limited to 2, as length is outside the new sub-interval.)
final var lowerHalf = new BigInterval(BigInteger.ZERO, length.subtract(BigInteger.ONE));
final var upperHalf = euclideanModulo(new BigInterval(length.add(BigInteger.ONE), absDivisor.mMaxValue));
return lowerHalf.join(upperHalf);
}
if (absDivisor.mMinValue.compareTo(mMaxValue) > 0) {
// trivial case: modulo is guaranteed to have no effect
return this;
}
if (absDivisor.mMaxValue != null && absDivisor.mMaxValue.compareTo(mMaxValue) > 0) {
return new BigInterval(BigInteger.ZERO, mMaxValue);
}
// fallback to imprecise approximation
return new BigInterval(BigInteger.ZERO,
absDivisor.mMaxValue == null ? null : absDivisor.mMaxValue.subtract(BigInteger.ONE));
}
}