All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jaitools.numeric.Range Maven / Gradle / Ivy

Go to download

Provides a single jar containing all JAI-tools modules which you can use instead of including individual modules in your project. Note: It does not include the Jiffle scripting language or Jiffle image operator.

The newest version!
/*
 * Copyright 2009-2011 Michael Bedward
 *
 * This file is part of jai-tools.
 *
 * jai-tools 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.
 *
 * jai-tools 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 jai-tools.  If not, see .
 *
 */
package jaitools.numeric;


/**
 * Yet another numeric interval class. It is used with the {@code RangeLookup} operator but
 * is also available for general use and does not depend on JAI classes.
 * 

* {@code Range} is a generic class that can be used all {@code Number} types. A range * is defined by its endpoints. If the min (lower bound) end-point is the same as the * max (upper-bound) end-point then the range represents a point, also referred to as * a degenerate interval. *

* An end-point can be positioned at a finite value, in which case it is said to be closed, * or at positive or negative infinity, in which case it is said to be open. For finite * end-points, a range can include or exclude the end-point. Included end-points correspond * to square-brackets in interval notation, while excluded end-points correspond to round * brackets. *

* The following rules apply to Float and Double NaN values... *

    *
  • Float and Double NaN are considered equal *
  • Point intervals can have value NaN *
  • Proper intervals treat NaN at their lower end-point as negative infinity and at * their upper end-point as positive infinity *
  • A test value of NaN can be equal to a point interval *
  • A test value of NaN will always be treated as outside a proper interval *
* * @param * @see RangeComparator * @see jaitools.media.jai.rangelookup.RangeLookupDescriptor * * @author Michael Bedward * @since 1.0 * @version $Id: Range.java 1504 2011-03-05 10:56:10Z michael.bedward $ */ public class Range { /** * Constants to specify whether a {@code Range} parameter defines * included or excluded values. */ public static enum Type { /** The range defines values to include in an operation */ INCLUDE, /** The range defines values to exclude in an operation */ EXCLUDE, /** The application of the range is undefined */ UNDEFINED } /** * Integer flag value for negative infinity. Can be used as an * argument to the {@linkplain #Range} constructor for point * (degenerate) intervals. */ public static final int NEG_INF = -1; /** * Integer flag value for positive infinity. Can be used as an * argument to the {@linkplain #Range} constructor for point * (degenerate) intervals. */ public static final int INF = 1; /** * Integer flag to indicate a finite argument. For internal use * and by RangeUtils. */ static final int FINITE = 0; private static final int NAN = -9999; private T minValue; private boolean minIncluded; private boolean minOpen; private int minType; private T maxValue; private boolean maxIncluded; private boolean maxOpen; private int maxType; private boolean isPoint; /** * Static create method. This just relieves the tedium of having to specify * the type parameter on both sides of the creation expression. So instead * of this... *

     *    Range<Integer> r = new Range<Integer>(10, false, 20, true);
     * 
* you can do this... *

     *    Range<Integer> r = Range.create(10, false, 20, true);
     * 
* * @param value type * * @param minValue the lower bound; passing null for this parameter * means an open lower bound; for Float or Double types, the * relevant NEGATIVE_INFINITY value can also be used. * * @param minIncluded true if the lower bound is to be included in the * range; false to exclude the lower bound; overridden to be false if the * lower bound is open * * @param maxValue the upper bound; passing null for this parameter * means an open upper bound; for Float or Double types, the * relevant NEGATIVE_INFINITY value can also be used. * * @param maxIncluded true if the upper bound is to be included in the * range; false to exclude the upper bound; overridden to be false if the * upper bound is open * * @return the new instance */ public static Range create(T minValue, boolean minIncluded, T maxValue, boolean maxIncluded) { return new Range(minValue, minIncluded, maxValue, maxIncluded); } /** * Creates a Range instance that is a point (degenerate) interval. If value * is null, for a point at infinity, the inf argument must be * provided and be one of Range.INF or Range.NEG_INF. * * @param value type * @param value the value to set for both min and max end-points * @param inf either Range.INF or Range.NEG_INF (ignored if value is non-null) * @return the new instance */ public static Range create(T value, int... inf) { return new Range(value, inf); } /** * Creates a new Range with non-zero width (may be infinite). * * @param minValue the lower bound; passing null for this parameter * means an open lower bound; for Float or Double types, the * relevant NEGATIVE_INFINITY value can also be used. * * @param minIncluded true if the lower bound is to be included in the * range; false to exclude the lower bound; overridden to be false if the * lower bound is open * * @param maxValue the upper bound; passing null for this parameter * means an open upper bound; for Float or Double types, the * relevant NEGATIVE_INFINITY value can also be used. * * @param maxIncluded true if the upper bound is to be included in the * range; false to exclude the upper bound; overridden to be false if the * upper bound is open */ public Range(T minValue, boolean minIncluded, T maxValue, boolean maxIncluded) { if (minValue != null && maxValue != null && minValue.compareTo(maxValue) > 0) { throw new IllegalArgumentException("minValue greater than maxValue"); } minType = FINITE; if (minValue == null) { // assume negative infinity minType = NEG_INF; } else if (minValue instanceof Double) { if (Double.isInfinite(minValue.doubleValue())) { if (Double.compare(minValue.doubleValue(), Double.POSITIVE_INFINITY) == 0) { minType = INF; } else { minType = NEG_INF; } } else if (Double.isNaN(minValue.doubleValue())) { // assume negative infinity minType = NEG_INF; } } else if (minValue instanceof Float) { if (Float.isInfinite(minValue.floatValue())) { if (Float.compare(minValue.floatValue(), Float.POSITIVE_INFINITY) == 0) { minType = INF; } else { minType = NEG_INF; } } else if (Float.isNaN(minValue.floatValue())) { // assume negative infinity minType = NEG_INF; } } if (minType == FINITE) { this.minValue = minValue; this.minOpen = false; this.minIncluded = minIncluded; } else { this.minValue = null; this.minOpen = true; this.minIncluded = false; } maxType = FINITE; if (maxValue == null) { // assume positive infinity maxType = INF; } else if (maxValue instanceof Double) { if (Double.isInfinite(maxValue.doubleValue())) { if (Double.compare(maxValue.doubleValue(), Double.POSITIVE_INFINITY) == 0) { maxType = INF; } else { maxType = NEG_INF; } } else if (Double.isNaN(maxValue.doubleValue())) { // assume positive infinity maxType = INF; } } else if (maxValue instanceof Float) { if (Float.isInfinite(maxValue.floatValue())) { if (Float.compare(maxValue.floatValue(), Float.POSITIVE_INFINITY) == 0) { maxType = INF; } else { maxType = NEG_INF; } } else if (Float.isNaN(maxValue.floatValue())) { // assume positive infinity maxType = INF; } } if (maxType == FINITE) { this.maxValue = maxValue; this.maxOpen = false; this.maxIncluded = maxIncluded; } else { this.maxValue = null; this.maxOpen = true; this.maxIncluded = false; } /* * Check if we have a point range trying to sneak in via * the interval constructor */ if (minType == FINITE && maxType == FINITE) { if (minValue == maxValue) { if (minIncluded && maxIncluded) { isPoint = true; } else { throw new IllegalArgumentException( "point range created with the interval constructor must have " + "min and max endpoints included"); } } } else if (minType == maxType) { // both at +ve or -ve Inf isPoint = true; } else if (minType == INF && maxType == NEG_INF) { // Inf endpoints wrong way round throw new IllegalArgumentException( "invalid to have min endpoint at Inf and max endpoint at Neg Inf"); } } /** * Creates a new point (degenerate) range. *

* Where value is finite the following apply: *

    *
  • min value is equal to max value *
  • upper and lower bounds are treated as closed *
  • upper and lower bounds are treated as included *
* * If the type parameter T is Float or Double then value can be * set to the relevant POSITIVE_INFINITY or NEGATIVE_INFINITY. For * other types such as Integer which lack infinity flag values a * point range at infinity can be created as in this example... *

     *
     *    Range<Integer> rInf = new Range<Integer>(null, Range.INF);
     *    Range<Integer> rNegInf = new Range<Integer>(null, Range.NEG_INF);
     *
     *    // or with the static create method...
     *    Range<Integer> rInf2 = Range.create(null, Range.INF);
     * 
*

* For a point interval at positive or negative infinity the following apply: *

    *
  • getting the min or max value of the range will return null *
  • the upper and lower bounds are treated as open *
  • the upper and lower bounds are treated as not included (ie. an * infinite point does not contain itself !) *
* * @param value the value to set for both min and max end-points * @param inf either Range.INF or Range.NEG_INF (ignored if present when value is non-null) */ public Range(T value, int... inf) { isPoint = true; int bound = FINITE; if (value == null) { if (inf == null || inf.length < 1 || !(inf[0] == INF || inf[0] == NEG_INF)) { throw new IllegalArgumentException("one of BOUND_INF or BOUND_NEG_INF must be provided with a null value"); } bound = inf[0]; } else if (value instanceof Double) { if (Double.isInfinite(value.doubleValue())) { if (Double.compare(value.doubleValue(), Double.POSITIVE_INFINITY) == 0) { bound = INF; } else { bound = NEG_INF; } } else if (Double.isNaN(value.doubleValue())) { bound = NAN; } } else if (value instanceof Float) { if (Float.isInfinite(value.floatValue())) { if (Float.compare(value.floatValue(), Float.POSITIVE_INFINITY) == 0) { bound = INF; } else { bound = NEG_INF; } } else if (Float.isNaN(value.floatValue())) { bound = NAN; } } if (bound == FINITE) { this.maxValue = this.minValue = value; this.maxType = this.minType = FINITE; this.maxOpen = this.minOpen = false; this.maxIncluded = this.minIncluded = true; } else { // point at +ve or -ve infinity this.maxValue = this.minValue = null; this.maxType = this.minType = bound; this.minOpen = this.maxOpen = true; /* * While it seems strange to have a point that doesn't * include itself, setting minIncluded and maxIncluded to * true would be inconsistent with treatment for proper * intervals (and perhaps illogical) */ this.maxIncluded = this.minIncluded = false; } } /** * Creates a copy of another Range instance. * * @param other the range to copy */ public Range(Range other) { this.minValue = other.minValue; this.minIncluded = other.minIncluded; this.minOpen = other.minOpen; this.minType = other.minType; this.maxValue = other.maxValue; this.maxIncluded = other.maxIncluded; this.maxOpen = other.maxOpen; this.maxType = other.maxType; this.isPoint = other.isPoint; } /** * Checks if this range is a point (degenerate) interval * @return {@code true} if this is a point; {@code false} otherwise */ public boolean isPoint() { return isPoint; } /** * Gets the minimum value for this range. The minimum value is * not necessarily included in the range (see {@linkplain #isMinIncluded()}). *

* Caution: If the range is lower-open * (ie. {@linkplain #isMinOpen()} returns true) this method will * return null. * * @return the minimum value or null if this range is lower-open */ public T getMin() { return minValue; } /** * Tests if the minimum value is positive infinity. If so, the range * must be a point (degenerate) interval. * * @return {@code true} if positive infinity; {@code false} otherwise */ public boolean isMinInf() { return minType == INF; } /** * Tests if the minimum value is negative infinity. This is equivalent * to {@linkplain #isMinOpen()} * * @return {@code true} if negative infinity; {@code false} otherwise */ public boolean isMinNegInf() { return minType == NEG_INF; } /** * Gets the maximum value of this range. The maximum value is * not necessarily included in the range (see {@linkplain #isMaxIncluded()}). *

* Caution: If the range is upper-open * (ie. {@linkplain #isMaxOpen()} returns true) this method will * return null. * * @return the maximum value or null if this range is upper-open */ public T getMax() { return maxValue; } /** * Tests if the maximum value is positive infinity. This is equivalent * to {@linkplain #isMaxOpen()} * * @return {@code true} if positive infinity; {@code false} otherwise */ public boolean isMaxInf() { return maxType == INF; } /** * Tests if the maximum value is negative infinity. If so, the range * must be a point (degenerate) interval. * * @return {@code true} if negative infinity; {@code false} otherwise */ public boolean isMaxNegInf() { return maxType == NEG_INF; } /** * Tests if the minimum value is included in the range. * This will return false if the range has an unbounded * minimum. * * @return {@code true} if minimum value is included; {@code false} otherwise * @see #isMinOpen() * @see #isMinClosed() */ public boolean isMinIncluded() { return minIncluded; } /** * Tests if the maximum range value is included in the range. * This will return false if the range has an unbounded * maximum. * * @return {@code true} if maximum value is included; {@code false} otherwise * @see #isMaxOpen * @see #isMaxClosed */ public boolean isMaxIncluded() { return maxIncluded; } /** * Tests if this range has an unbounded (open) minimum, ie. * the range extends to negative infinity. * * @return {@code true} if unbounded; {@code false} otherwise * @see #isMinIncluded() */ public boolean isMinOpen() { return minOpen; } /** * Tests if this range has a bounded (closed) minimum. * * @return {@code true} if bounded; {@code false} otherwise * @see #isMinIncluded() */ public boolean isMinClosed() { return !minOpen; } /** * Tests if this range has unbounded (open) upper end, ie the * range extends to positive infinity. * * @return {@code true} if unbounded; {@code false} otherwise * @see #isMaxIncluded() */ public boolean isMaxOpen() { return maxOpen; } /** * Tests if this range has a bounded (closed) maximum value. * * @return {@code true} if bounded; {@code false} otherwise * @see #isMaxIncluded() */ public boolean isMaxClosed() { return !maxOpen; } /** * Tests if this range contains the specified, non-null value. * * @param value the value * @return {@code true} if the value is within this range; {@code false} otherwise */ public boolean contains(T value) { if (value == null) { throw new UnsupportedOperationException("null values are not supported"); } if (isPoint) { if (minType == FINITE) { return NumberOperations.compare(minValue, value) == 0; } else if (minType == NAN) { if (value instanceof Double) { return Double.isNaN(value.doubleValue()); } else { return Float.isNaN(value.floatValue()); } } else { return false; } } else { // NaN values are always outside a proper interval if ((value instanceof Double && Double.isNaN(value.doubleValue())) || (value instanceof Float && Float.isNaN(value.floatValue()))) { return false; } int comp; if (minValue != null) { comp = NumberOperations.compare(value, minValue); if (comp < 0 || (!minIncluded && comp == 0)) { return false; } } if (maxValue != null) { comp = NumberOperations.compare(value, maxValue); if (comp > 0 || (!maxIncluded && comp == 0)) { return false; } } return true; } } /** * Tests if this range intersects another range. Two ranges intersect if * there is at least one value, x, for which contains(x) returns true * for both ranges * * @param other the range to check for intersection * @return {@code true} if the ranges intersect; {@code false} otherwise */ public boolean intersects(Range other) { RangeComparator.Result comp = this.compareTo(other); return RangeComparator.isIntersection(comp); } /** * Tests for equality with another range. Two ranges are equal if * their respective end-points are identical in both value and type * (included / excluded). * * @param obj the other range * * @return {@code true} if equal; {@code false} otherwise */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Range other = (Range) obj; // need this to guard against incorrect matching of infinite points // with the whole number line if (this.isPoint != other.isPoint) { return false; } if (this.minValue != other.minValue && (this.minValue == null || !this.minValue.equals(other.minValue))) { return false; } if (this.minIncluded != other.minIncluded) { return false; } if (this.maxValue != other.maxValue && (this.maxValue == null || !this.maxValue.equals(other.maxValue))) { return false; } if (this.maxIncluded != other.maxIncluded) { return false; } return true; } @Override public int hashCode() { int hash = 7; hash = 11 * hash + (this.minValue != null ? this.minValue.hashCode() : 0); hash = 11 * hash + (this.minIncluded ? 1 : 0); hash = 11 * hash + (this.maxValue != null ? this.maxValue.hashCode() : 0); hash = 11 * hash + (this.maxIncluded ? 1 : 0); return hash; } /** * Returns a string representation of this range. The common interval * notation of square brackets for closed boundaries and round brackets * for open boundaries is used. *

* Examples of output: *

* [10, 20]
* (-Inf, 42) *
* * @return the string */ @Override public String toString() { StringBuilder sb = new StringBuilder(); if (isMinClosed()) { sb.append(isMinIncluded() ? '[' : '('); sb.append(getMin()); } else { if (isPoint()) { sb.append(isMinInf() ? "(Inf" : "(-Inf"); } else { sb.append("(-Inf"); } } if (isPoint()) { sb.append(isMinIncluded() ? ']' : ')'); } else { sb.append(", "); if (isMaxClosed()) { sb.append(getMax()); sb.append(isMaxIncluded() ? ']' : ')'); } else { sb.append("Inf)"); } } return sb.toString(); } /** * Compares this Range to another Range. Range comparisons are more involved * than comparisons between point values. There are 18 distinct comparison * results as described by Hayes (2003). See {@linkplain RangeComparator} for * more details. * * @param other the other Range * @return a RangeComparator.Result enum identifying the relationship from the * point of view of this Range */ RangeComparator.Result compareTo(Range other) { RangeComparator rc = new RangeComparator(); return rc.compare(this, other); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy