001 /*--------------------------------------------------------------------------+ 002 $Id: Range.java 26268 2010-02-18 10:44:30Z juergens $ 003 | | 004 | Copyright 2005-2010 Technische Universitaet Muenchen | 005 | | 006 | Licensed under the Apache License, Version 2.0 (the "License"); | 007 | you may not use this file except in compliance with the License. | 008 | You may obtain a copy of the License at | 009 | | 010 | http://www.apache.org/licenses/LICENSE-2.0 | 011 | | 012 | Unless required by applicable law or agreed to in writing, software | 013 | distributed under the License is distributed on an "AS IS" BASIS, | 014 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 015 | See the License for the specific language governing permissions and | 016 | limitations under the License. | 017 +--------------------------------------------------------------------------*/ 018 package edu.tum.cs.commons.math; 019 020 import java.text.NumberFormat; 021 022 import edu.tum.cs.commons.assertion.CCSMPre; 023 import edu.tum.cs.commons.string.StringUtils; 024 025 /** 026 * A class that represents ranges that may include or exclude the upper and 027 * lower bounds. This class is immutable. 028 * <p> 029 * Note: If a range is constructed where the upper and lower bounds are equal 030 * and one of them is exclusive, this range is considered empty, i.e. no number 031 * can be contained in it. 032 * 033 * 034 * @author deissenb 035 * @author $Author: juergens $ 036 * @version $Rev: 26268 $ 037 * @levd.rating GREEN Hash: 71F5CBF843D6B94E6071CAF614C19E6B 038 */ 039 public class Range { 040 /** The lower bound. */ 041 private final double lower; 042 043 /** The upper bound. */ 044 private final double upper; 045 046 /** Flag that indicates if lower bound is inclusive or not. */ 047 private final boolean lowerIsInclusive; 048 049 /** Flag that indicates if upper bound is inclusive or not. */ 050 private final boolean upperIsInclusive; 051 052 /** Create range where both bounds are inclusive. */ 053 public Range(double lower, double upper) { 054 this(lower, true, upper, true); 055 } 056 057 /** 058 * Create range. 059 * 060 * @param lower 061 * lower bound 062 * @param lowerIsInclusive 063 * flag that indicates if lower bound is inclusive or not 064 * @param upper 065 * upper bound 066 * @param upperIsInclusive 067 * flag that indicates if upper bound is inclusive or not 068 */ 069 public Range(double lower, boolean lowerIsInclusive, double upper, 070 boolean upperIsInclusive) { 071 CCSMPre.isFalse(Double.isNaN(lower), "Lower bound must not be NaN"); 072 CCSMPre.isFalse(Double.isNaN(upper), "Upper bound must not be NaN"); 073 this.lower = lower; 074 this.lowerIsInclusive = lowerIsInclusive; 075 this.upper = upper; 076 this.upperIsInclusive = upperIsInclusive; 077 } 078 079 /** Checks is a number is contained in the range. */ 080 public boolean contains(double number) { 081 if (lowerIsInclusive) { 082 if (number < lower) { 083 return false; 084 } 085 } else { 086 if (number <= lower) { 087 return false; 088 } 089 } 090 091 if (upperIsInclusive) { 092 if (number > upper) { 093 return false; 094 } 095 } else { 096 if (number >= upper) { 097 return false; 098 } 099 } 100 101 return true; 102 } 103 104 /** 105 * Hash code includes bound and the flags that indicate if the bounds are 106 * inclusive or not. 107 */ 108 @Override 109 public int hashCode() { 110 if (isEmpty()) { 111 return 0; 112 } 113 114 int result = hash(upper) + 37 * hash(lower); 115 116 // indicate bounds by bit flips; which bit is used is not really 117 // relevant 118 if (lowerIsInclusive) { 119 result ^= 0x100000; 120 } 121 if (upperIsInclusive) { 122 result ^= 0x4000; 123 } 124 125 return result; 126 } 127 128 /** Code for hashing a double is copied from {@link Double#hashCode()}. */ 129 private int hash(double number) { 130 long bits = Double.doubleToLongBits(number); 131 return (int) (bits ^ (bits >>> 32)); 132 } 133 134 /** 135 * Two ranges are equal if their bounds are equal and the flags that 136 * indicate if the bounds are inclusive or not are equal, too. Empty ranges 137 * are considered equal regardless for there specific bounds. 138 */ 139 @Override 140 public boolean equals(Object obj) { 141 if (obj == this) { 142 return true; 143 } 144 if (!(obj instanceof Range)) { 145 return false; 146 } 147 148 Range other = (Range) obj; 149 150 if (isEmpty() && other.isEmpty()) { 151 return true; 152 } 153 154 return lowerIsInclusive == other.lowerIsInclusive 155 && upperIsInclusive == other.upperIsInclusive 156 && lower == other.lower && upper == other.upper; 157 } 158 159 /** Get lower bound. */ 160 public double getLower() { 161 return lower; 162 } 163 164 /** Get upper bound. */ 165 public double getUpper() { 166 return upper; 167 } 168 169 /** Get flag that indicates if the lower bound is inclusive. */ 170 public boolean isLowerInclusive() { 171 return lowerIsInclusive; 172 } 173 174 /** Get flag that indicates if the upper bound is inclusive. */ 175 public boolean isUpperInclusive() { 176 return upperIsInclusive; 177 } 178 179 /** Checks if a range is empty. */ 180 public boolean isEmpty() { 181 if (lowerIsInclusive && upperIsInclusive) { 182 return lower > upper; 183 } 184 return lower >= upper; 185 } 186 187 /** 188 * Returns the size of the range. In case of empty ranges this returns 0. 189 * Note that inclusiveness of bound is not relevant for this method. 190 */ 191 public double size() { 192 return Math.max(0, upper - lower); 193 } 194 195 /** This forwards to <code>format(null);</code>. */ 196 @Override 197 public String toString() { 198 return format(null); 199 } 200 201 /** 202 * String representation contains the bounds and brackets that indicate if 203 * the bounds are inclusive or exclusive. 204 * 205 * @param numberFormat 206 * number format used for formatting the numbers. If this is 207 * <code>null</code>, no special formatting is applied. 208 */ 209 public String format(NumberFormat numberFormat) { 210 StringBuilder result = new StringBuilder(); 211 if (lowerIsInclusive) { 212 result.append("["); 213 } else { 214 result.append("]"); 215 } 216 result.append(StringUtils.format(lower, numberFormat) + ";" 217 + StringUtils.format(upper, numberFormat)); 218 if (upperIsInclusive) { 219 result.append("]"); 220 } else { 221 result.append("["); 222 } 223 return result.toString(); 224 } 225 }