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

org.conqat.lib.commons.math.Range Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) CQSE GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.conqat.lib.commons.math;

import java.io.Serializable;
import java.text.NumberFormat;

import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.string.StringUtils;
import org.conqat.lib.commons.test.IndexValueClass;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * A class that represents ranges that may include or exclude the upper and lower bounds. This class
 * is immutable.
 * 

* Note: If a range is constructed where the upper and lower bounds are equal and one of them is * exclusive, this range is considered empty, i.e. no number can be contained in it. */ @IndexValueClass public class Range implements Comparable, Serializable { /** Version for serialization. */ private static final long serialVersionUID = 1; /** The name of the JSON property name for {@link #lower}. */ private static final String LOWER_PROPERTY = "lower"; /** The name of the JSON property name for {@link #upper}. */ private static final String UPPER_PROPERTY = "upper"; /** The lower bound. */ @JsonProperty(LOWER_PROPERTY) private final double lower; /** The upper bound. */ @JsonProperty(UPPER_PROPERTY) private final double upper; /** Flag that indicates if lower bound is inclusive or not. */ @JsonProperty("lowerIsInclusive") private final boolean lowerIsInclusive; /** Flag that indicates if upper bound is inclusive or not. */ @JsonProperty("upperIsInclusive") private final boolean upperIsInclusive; /** Create range where both bounds are inclusive. */ @JsonCreator public Range(@JsonProperty(LOWER_PROPERTY) double lower, @JsonProperty(UPPER_PROPERTY) double upper) { this(lower, true, upper, true); } /** * Create range. * * @param lowerIsInclusive * flag that indicates if lower bound is inclusive or not * @param upperIsInclusive * flag that indicates if upper bound is inclusive or not */ public Range(double lowerBound, boolean lowerIsInclusive, double upperBound, boolean upperIsInclusive) { CCSMAssert.isFalse(Double.isNaN(lowerBound), "Lower bound must not be NaN"); CCSMAssert.isFalse(Double.isNaN(upperBound), "Upper bound must not be NaN"); lower = lowerBound; this.lowerIsInclusive = lowerIsInclusive; upper = upperBound; this.upperIsInclusive = upperIsInclusive; } /** Checks is a number is contained in the range. */ public boolean contains(double number) { if (lowerIsInclusive) { if (number < lower) { return false; } } else { if (number <= lower) { return false; } } if (upperIsInclusive) { return !(number > upper); } else { return !(number >= upper); } } /** * Hash code includes bound and the flags that indicate if the bounds are inclusive or not. */ @Override public int hashCode() { if (isEmpty()) { return 0; } int result = hash(upper) + 37 * hash(lower); // indicate bounds by bit flips; which bit is used is not really // relevant if (lowerIsInclusive) { result ^= 0x100000; } if (upperIsInclusive) { result ^= 0x4000; } return result; } /** Code for hashing a double is copied from {@link Double#hashCode()}. */ private static int hash(double number) { long bits = Double.doubleToLongBits(number); return (int) (bits ^ (bits >>> 32)); } /** * Two ranges are equal if their bounds are equal and the flags that indicate if the bounds are * inclusive or not are equal, too. Empty ranges are considered equal regardless for there specific * bounds. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Range)) { return false; } Range other = (Range) obj; if (isEmpty() && other.isEmpty()) { return true; } return lowerIsInclusive == other.lowerIsInclusive && upperIsInclusive == other.upperIsInclusive && lower == other.lower && upper == other.upper; } /** Get lower bound. */ public double getLower() { return lower; } /** Get upper bound. */ public double getUpper() { return upper; } /** Get flag that indicates if the lower bound is inclusive. */ public boolean isLowerInclusive() { return lowerIsInclusive; } /** Get flag that indicates if the upper bound is inclusive. */ public boolean isUpperInclusive() { return upperIsInclusive; } /** Checks if a range is empty. */ public boolean isEmpty() { if (lowerIsInclusive && upperIsInclusive) { return lower > upper; } return lower >= upper; } /** * Returns whether this range is a singleton (i.e. contains of a single point/number). */ public boolean isSingleton() { return lower == upper && lowerIsInclusive && upperIsInclusive; } /** * Returns the size of the range. In case of empty ranges this returns 0. Note that inclusiveness of * bound is not relevant for this method. */ public double size() { return Math.max(0, upper - lower); } /** This forwards to format(null);. */ @Override public String toString() { return format(null); } /** * String representation contains the bounds and brackets that indicate if the bounds are inclusive * or exclusive. * * @param numberFormat * number format used for formatting the numbers. If this is null, no * special formatting is applied. */ public String format(NumberFormat numberFormat) { StringBuilder result = new StringBuilder(); if (lowerIsInclusive) { result.append("["); } else { result.append("]"); } result.append(StringUtils.format(lower, numberFormat) + ";" + StringUtils.format(upper, numberFormat)); if (upperIsInclusive) { result.append("]"); } else { result.append("["); } return result.toString(); } /** * Returns whether this range has a non-empty intersection with another range. */ public boolean overlaps(Range other) { if (isEmpty() || other.isEmpty()) { return false; } if (lower == other.lower) { if (isSingleton()) { return other.lowerIsInclusive; } if (other.isSingleton()) { return lowerIsInclusive; } return true; } if (lower < other.lower) { if (upperIsInclusive && other.lowerIsInclusive) { return other.lower <= upper; } return other.lower < upper; } return other.overlaps(this); } /** * {@inheritDoc} *

* Compares by lower values. */ @Override public int compareTo(Range other) { int result = Double.compare(lower, other.lower); if (result != 0) { return result; } if (lowerIsInclusive && !other.lowerIsInclusive) { return -1; } if (!lowerIsInclusive && other.lowerIsInclusive) { return 1; } return 0; } /** Determines whether the other range is fully contained in this range. */ public boolean contains(Range other) { boolean sameLowerBound = isLowerInclusive() == other.isLowerInclusive() && getLower() == other.getLower(); boolean containsLower = contains(other.getLower()) || sameLowerBound; boolean sameUpperBound = isUpperInclusive() == other.isUpperInclusive() && getUpper() == other.getUpper(); boolean containsUpper = contains(other.getUpper()) || sameUpperBound; return containsLower && containsUpper; } /** * Calculates the union of this range with another range. The union is defined by the smaller lower * bound and the higher upper bound of the two ranges. */ public Range union(Range other) { double lowerBoundOfOtherRange = other.getLower(); double minLower; boolean lowerInclusive; if (lower == lowerBoundOfOtherRange) { minLower = lower; lowerInclusive = isLowerInclusive() || other.isLowerInclusive(); } else if (lower < lowerBoundOfOtherRange) { minLower = lower; lowerInclusive = isLowerInclusive(); } else { minLower = lowerBoundOfOtherRange; lowerInclusive = other.isLowerInclusive(); } double upperBoundOfOtherRange = other.getUpper(); double maxUpper; boolean upperInclusive; if (upper == upperBoundOfOtherRange) { maxUpper = upper; upperInclusive = isUpperInclusive() || other.isUpperInclusive(); } else if (upper < upperBoundOfOtherRange) { maxUpper = upperBoundOfOtherRange; upperInclusive = other.isUpperInclusive(); } else { maxUpper = upper; upperInclusive = isUpperInclusive(); } return new Range(minLower, lowerInclusive, maxUpper, upperInclusive); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy