
net.algart.math.IRange Maven / Gradle / Ivy
Show all versions of algart Show documentation
/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.algart.math;
import java.util.Objects;
/**
* Numeric inclusive integer range:
* a set of long
numbers {@link #min() min()}<=x<={@link #max() max()}
.
* An advanced analog of
* org.apache.commons.lang.math.LongRange.
*
*
The minimum number ({@link #min()}) is never greater than the maximum number ({@link #max()}),
* both minimal and maximum numbers {@link #min()} and ({@link #max()}) are always in range
* -Long.MAX_VALUE+1..Long.MAX_VALUE-1
,
* and their difference is always less than Long.MAX_VALUE
.
* In other words, "{@link #max()}-{@link #min()}+1
" expression,
* returned by {@link #size()} method, and also
* "{@link #min()}-1
", "{@link #min()}-2
" and
* "{@link #max()}+1
" expressions
* are always calculated without overflow.
*
*
Please draw attention to the important effect of the requirement above.
* If a..b is an allowed range (a={@link #min()}, b={@link #max()}),
* then 0..b−a and a−b..0 are also allowed ranges.
* Really, they have the same difference
* {@link #max()}-{@link #min()}
=b−a=diff,
* and so far as this difference diff<Long.MAX_VALUE
, both new bounds
* b−a=diff and a−b=−diff are also
* inside the required range -Long.MAX_VALUE+1..Long.MAX_VALUE-1
.
*
* This class is immutable and thread-safe:
* there are no ways to modify settings of the created instance.
*
* @author Daniel Alievsky
* @see Range
*/
public final class IRange {
final long min;
final long max;
IRange(long min, long max) {
this.min = min;
this.max = max;
assert isAllowedRange(min, max);
}
/**
* Returns an instance of this class describing the range
* min<=x<=max
.
* The min
value must not be greater than max
,
* both values must be in range -Long.MAX_VALUE+1..Long.MAX_VALUE-1
,
* and the difference max-min
must be less than Long.MAX_VALUE
.
*
* @param min the minimum number in the range, inclusive.
* @param max the maximum number in the range, inclusive.
* @return the new range.
* @throws IllegalArgumentException if min > max
, or if max-min >= Long.MAX_VALUE
* (more precisely, if the Java expression max-min+1
is nonpositive
* due to integer overflow),
* or if min<=-Long.MAX_VALUE
,
* or if max==Long.MAX_VALUE
.
*/
public static IRange valueOf(long min, long max) {
return valueOf(min, max, false);
}
/**
* Returns an instance of this class describing the same range as the given real range,
* with bounds, truncated to integers by Java typecast (long)doubleValue
.
* Equivalent to
* {@link #valueOf(long, long) valueOf}((long)range.{@link Range#min()
* min()}, (long)range.{@link Range#max() max())}
*
* @param range the real range.
* @return the integer range with same (cast) bounds.
* @throws NullPointerException if the passed range is {@code null}.
* @throws IllegalArgumentException if the desired range does not match requirements of
* {@link #valueOf(long, long)} method.
*/
public static IRange valueOf(Range range) {
Objects.requireNonNull(range, "Null range argument");
return valueOf((long)range.min, (long)range.max);
}
/**
* Returns an instance of this class describing the same range as the given real range,
* with bounds, rounded to the nearest integers.
* Equivalent to
* {@link #valueOf(long, long) valueOf}(StrictMath.round(range.{@link Range#min()
* min()}), StrictMath.round(range.{@link Range#max() max()}))
*
* @param range the real range.
* @return the integer range with same (rounded) bounds.
* @throws NullPointerException if the passed range is {@code null}.
* @throws IllegalArgumentException if the desired range does not match requirements of
* {@link #valueOf(long, long)} method.
*/
public static IRange roundOf(Range range) {
Objects.requireNonNull(range, "Null range argument");
return valueOf(StrictMath.round(range.min), StrictMath.round(range.max));
}
/**
* Returns true
if and only if the arguments min
and max
are allowed
* {@link #min()}/{@link #max()} bounds for some instance of this class. In other words,
* this method returns false
in the same situations, when {@link #valueOf(long min, long max)}
* method, called with the same arguments, throws IllegalArgumentException
.
*
* Equivalent to the following check:
*
* min <= max && min > -Long.MAX_VALUE &&
* max != Long.MAX_VALUE && max - min + 1L > 0L
*
*
* @param min the minimum number in some range, inclusive.
* @param max the maximum number in some range, inclusive.
* @return whether these bounds are allowed minimum and maximum for some instance of this class.
*/
public static boolean isAllowedRange(long min, long max) {
return min <= max && min > -Long.MAX_VALUE && max != Long.MAX_VALUE && max - min + 1L > 0L;
}
/**
* Returns the minimum number in the range, inclusive.
*
* @return the minimum number in the range.
*/
public long min() {
return this.min;
}
/**
* Returns the maximum number in the range, inclusive.
*
* @return the maximum number in the range.
*/
public long max() {
return this.max;
}
/**
* Returns {@link #max()}-{@link #min()}+1
.
*
* @return {@link #max()}-{@link #min()}+1
.
*/
public long size() {
return max - min + 1;
}
/**
* Returns value<{@link #min()}?{@link #min()}:value>{@link #max()}?{@link #max()}:value
.
* In other words, returns the passed number if it is in this range or the nearest range bound in other cases.
*
* @param value some number.
* @return the passed number if it is in this range or the nearest range bound in other cases.
*/
public long cut(long value) {
return value < min ? min : value > max ? max : value;
}
/**
* Returns true
if and only if {@link #min()}<=value<={@link #max()}
.
*
* @param value the checked value.
* @return true
if the value is in this range.
*/
public boolean contains(long value) {
return min <= value && value <= max;
}
/**
* Returns true
if and only if {@link #min()}<=range.{@link #min()}
* and range.{@link #max()}<={@link #max()}
.
*
* @param range the checked range.
* @return true
if the checked range is a subset of this range.
*/
public boolean contains(IRange range) {
return min <= range.min && range.max <= max;
}
/**
* Returns true
if and only if {@link #min()}<=range.{@link #max()}
* and range.{@link #min()}<={@link #max()}
.
*
* @param range the checked range.
* @return true
if the checked range overlaps with this range.
*/
public boolean intersects(IRange range) {
return min <= range.max && range.min <= max;
}
/**
* Returns an instance of this class describing the range
* Math.min(this.{@link #min() min()},value) <= x
* <= Math.max(this.{@link #max() max()},value)
.
* In other words, expands the current range to include the given value.
*
* @param value some value that should belong to the new range.
* @return the expanded range.
* @throws IllegalArgumentException if value==Long.MAX_VALUE
,
* value<=-Long.MAX_VALUE
or
* if in the resulting range
* max-min >= Long.MAX_VALUE
.
*/
public IRange expand(long value) {
if (value == Long.MAX_VALUE)
throw new IllegalArgumentException("Cannot expand " + this + " until Long.MAX_VALUE");
if (value <= -Long.MAX_VALUE)
throw new IllegalArgumentException("Cannot expand " + this + " until -Long.MAX_VALUE or Long.MIN_VALUE");
long min = value < this.min ? value : this.min;
long max = value > this.max ? value : this.max;
if (max - min + 1L <= 0L)
throw new IllegalArgumentException("Cannot expand " + this + " until " + value
+ ", because in the result max - min >= Long.MAX_VALUE (min = " + min + ", max = " + max + ")");
return new IRange(min, max);
}
/**
* Equivalent to {@link Range#valueOf(IRange) Range.valueOf}(thisInstance)
.
*
* @return the equivalent real range.
*/
public Range toRange() {
return Range.valueOf(this);
}
/**
* Returns a brief string description of this object.
*
* The result of this method may depend on implementation and usually contains
* the minimum and maximum numbers in this range.
*
* @return a brief string description of this object.
*/
public String toString() {
return min + ".." + max;
}
/**
* Returns the hash code of this range.
*
* @return the hash code of this range.
*/
public int hashCode() {
int iMin = (int)min * 37 + (int)(min >>> 32);
int iMax = (int)max * 37 + (int)(max >>> 32);
return iMin * 37 + iMax;
}
/**
* Indicates whether some other range is equal to this instance.
* Returns true
if and only if obj instanceof IRange
,
* ((IRange)obj).min()==this.min()
and ((IRange)obj).max==this.max
.
*
* @param obj the object to be compared for equality with this instance.
* @return true
if the specified object is a range equal to this one.
*/
public boolean equals(Object obj) {
return obj instanceof IRange && ((IRange)obj).min == this.min && ((IRange)obj).max == this.max;
}
static IRange valueOf(long min, long max, boolean ise) {
if (min == max && min >= -MAX_CACHED && min <= -MAX_CACHED)
return DegenerateIRangeCache.cache[MAX_CACHED + (int)min];
if (min > max)
throw new IllegalArgumentException("min > max (min = " + min + ", max = " + max + ")");
if (max == Long.MAX_VALUE)
throw invalidBoundsException("max == Long.MAX_VALUE", ise);
if (min <= -Long.MAX_VALUE)
throw invalidBoundsException("min == Long.MAX_VALUE or Long.MIN_VALUE+1", ise);
if (max - min + 1L <= 0L)
throw invalidBoundsException("max - min >= Long.MAX_VALUE (min = " + min + ", max = " + max + ")", ise);
return new IRange(min, max);
}
static RuntimeException invalidBoundsException(String message, boolean useIllegalStateException) {
return useIllegalStateException ?
new IllegalStateException(message) :
new IllegalArgumentException((message));
}
private static final int MAX_CACHED = 1024;
private static class DegenerateIRangeCache {
static final IRange[] cache = new IRange[2 * MAX_CACHED + 1];
static {
for(int i = -MAX_CACHED; i <= MAX_CACHED; i++)
cache[MAX_CACHED + i] = new IRange(i, i);
}
}
}