001 package edu.nrao.sss.math; 002 003 import java.math.BigDecimal; 004 005 /** 006 * An interval on the number line from one number to another. 007 * Whether or not the endpoints are part of an interval is 008 * determined by its {@link IntervalType type}. 009 * <p> 010 * <b>Version Info:</b> 011 * <table style="margin-left:2em"> 012 * <tr><td>$Revision: 567 $</td></tr> 013 * <tr><td>$Date: 2007-04-26 16:35:46 -0600 (Thu, 26 Apr 2007) $</td></tr> 014 * <tr><td>$Author: dharland $</td></tr> 015 * </table></p> 016 * 017 * @author David M. Harland 018 * @since 2007-04-25 019 */ 020 public class NumberInterval 021 { 022 private IntervalType intervalType; 023 private BigDecimal low, high; 024 025 /** 026 * Creates a new interval with the given properties. 027 * 028 * @param type determines which endpoints, if either, are part of this 029 * interval. 030 * @param a one of the endpoints. 031 * @param b the other endpoint. 032 */ 033 public NumberInterval(IntervalType type, Number a, Number b) 034 { 035 set(a,b); 036 set(type); 037 } 038 039 /** Used by constructor & public set method. */ 040 private void set(Number a, Number b) 041 { 042 BigDecimal bdA = numberToBigDecimal(a); 043 BigDecimal bdB = numberToBigDecimal(b); 044 045 if (bdA.compareTo(bdB) <= 0) 046 { 047 low = bdA; 048 high = bdB; 049 } 050 else 051 { 052 low = bdB; 053 high = bdA; 054 } 055 } 056 057 /** Used by constructor & public set method. */ 058 private void set(IntervalType type) 059 { 060 intervalType = (type == null) ? IntervalType.LOW_CLOSED_HIGH_OPEN : type; 061 } 062 063 /** Converts a Number to a BigDecimal. */ 064 private BigDecimal numberToBigDecimal(Number n) 065 { 066 BigDecimal bd = null; 067 068 try 069 { 070 bd = new BigDecimal(n.toString()); 071 } 072 catch (NumberFormatException ex) 073 { 074 //TODO BigD does not handle NaN or infinity. 075 // Might be nice to handle infinity. 076 bd = new BigDecimal(Double.toString(n.doubleValue())); 077 } 078 079 return bd; 080 } 081 082 /** 083 * Sets the endpoints of this interval. 084 * Clients do not need to present the endpoints in any particular order. 085 * This method will compare the two endpoints to determine which 086 * is higher and which is lower. 087 * 088 * @param a one of the endpoints. 089 * @param b the other endpoint. 090 */ 091 public void setInterval(Number a, Number b) { set(a,b); } 092 093 /** 094 * Sets the value that determines which endpoints, if either, are in 095 * this interval. 096 * @param newType the type of this interval (open, closed, etc.). 097 */ 098 public void setType(IntervalType newType) { set(newType); } 099 100 /** 101 * Returns <i>true</i> if this interval contains {@code candidate}. 102 * @param candidate a number that might be contained in this interval. 103 * @return <i>true</i> if this interval contains {@code candidate}. 104 */ 105 public boolean contains(Number candidate) 106 { 107 BigDecimal param = numberToBigDecimal(candidate); 108 109 //Though we usually avoid multiple returns in a method, the process 110 //of elimination leads to easier-to-read conditionals. 111 112 int paramCompareToLow = param.compareTo(low); 113 int paramCompareToHigh = param.compareTo(high); 114 115 //param is clearly in the range 116 if ((paramCompareToLow > 0) && (paramCompareToHigh < 0)) 117 return true; 118 119 //param equals low end of range, and low end is part of interval 120 if ((paramCompareToLow == 0) && 121 (intervalType.equals(IntervalType.CLOSED)) || 122 intervalType.equals(IntervalType.LOW_CLOSED_HIGH_OPEN)) 123 return true; 124 125 //param equals high end of range, and high end is part of interval 126 if ((paramCompareToHigh == 0) && 127 (intervalType.equals(IntervalType.CLOSED)) || 128 intervalType.equals(IntervalType.LOW_OPEN_HIGH_CLOSED)) 129 return true; 130 131 //param is either outside the range or equal to an endpoint that 132 //is not part of the range 133 return false; 134 } 135 136 /** 137 * Returns a string of form <tt>[low..high)</tt>, where a square bracket 138 * indicates that the interval is closed on that end and a rounded bracket 139 * that it is open. 140 */ 141 @Override 142 public String toString() 143 { 144 char startChar = 145 (intervalType.equals(IntervalType.CLOSED) || 146 intervalType.equals(IntervalType.LOW_CLOSED_HIGH_OPEN)) ? '[' : '('; 147 148 char endChar = 149 (intervalType.equals(IntervalType.CLOSED) || 150 intervalType.equals(IntervalType.LOW_OPEN_HIGH_CLOSED)) ? ']' : ')'; 151 152 StringBuilder buff = new StringBuilder(); 153 154 buff.append(startChar).append(low.toString()); 155 buff.append(".."); 156 buff.append(high.toString()).append(endChar); 157 158 return buff.toString(); 159 } 160 161 /** 162 * Indicates whether or not the endpoints are inside or outside an interval. 163 */ 164 public static enum IntervalType 165 { 166 /** 167 * Neither endpoint is contained in the interval. 168 */ 169 OPEN, 170 171 /** 172 * Both endpoints are contained in the interval. 173 */ 174 CLOSED, 175 176 /** 177 * The low endpoint is in the interval; the high endpoint is not. 178 * This is the usual definition of a half-open interval. 179 */ 180 LOW_CLOSED_HIGH_OPEN, 181 182 /** 183 * The low endpoint is not in the interval; the high endpoint is. 184 */ 185 LOW_OPEN_HIGH_CLOSED; 186 } 187 }