001package com.hfg.math; 002 003 004import java.util.ArrayList; 005import java.util.List; 006 007import com.hfg.exception.InvalidValueException; 008 009 010//------------------------------------------------------------------------------ 011/** 012 Roman numeral representation. The current implementation works for numbers up to 4000. 013 <div class='example'> 014 Ex: given <code style='font-style:italic'> RomanNumeral num = new RomanNumeral(1944);</code> 015 <p> 016 <code style='font-style:italic'>num.toString()</code> will be equal to "MCMXLIV". 017 </p> 018 </div> 019 <div> 020 See <a href='http://en.wikipedia.org/wiki/Roman_numerals'>http://en.wikipedia.org/wiki/Roman_numerals</a> 021 </div> 022 @author J. Alex Taylor, hairyfatguy.com 023 */ 024//------------------------------------------------------------------------------ 025// com.hfg XML/HTML Coding Library 026// 027// This library is free software; you can redistribute it and/or 028// modify it under the terms of the GNU Lesser General Public 029// License as published by the Free Software Foundation; either 030// version 2.1 of the License, or (at your option) any later version. 031// 032// This library is distributed in the hope that it will be useful, 033// but WITHOUT ANY WARRANTY; without even the implied warranty of 034// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 035// Lesser General Public License for more details. 036// 037// You should have received a copy of the GNU Lesser General Public 038// License along with this library; if not, write to the Free Software 039// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 040// 041// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 042// jataylor@hairyfatguy.com 043//------------------------------------------------------------------------------ 044// TODO: Add support for values 4000+ that need a macron (bar above the letter) for display. 045// The macron indicates that the number is multiplied by 1000. 046 047public class RomanNumeral extends Number implements Comparable<RomanNumeral> 048{ 049 private String mString; 050 private int mValue; 051 052 private static List<RomanNumeralSymbol> sHiToLowSymbols = new ArrayList<RomanNumeralSymbol>(7); 053 054 static 055 { 056 sHiToLowSymbols.add(RomanNumeralSymbol.M); 057 sHiToLowSymbols.add(RomanNumeralSymbol.D); 058 sHiToLowSymbols.add(RomanNumeralSymbol.C); 059 sHiToLowSymbols.add(RomanNumeralSymbol.L); 060 sHiToLowSymbols.add(RomanNumeralSymbol.X); 061 sHiToLowSymbols.add(RomanNumeralSymbol.V); 062 sHiToLowSymbols.add(RomanNumeralSymbol.I); 063 } 064 065 //************************************************************************** 066 // CONSTRUCTORS 067 //************************************************************************** 068 069 //-------------------------------------------------------------------------- 070 /** 071 Create a RomanNumeral object from an integer value. 072 */ 073 public RomanNumeral(int inValue) 074 { 075 super(); 076 calculateString(inValue); 077 } 078 079 //-------------------------------------------------------------------------- 080 /** 081 Create a RomanNumeral object from a Roman numeral string such as "MCMXLIV". 082 */ 083 public RomanNumeral(String inValue) 084 { 085 super(); 086 parseString(inValue); 087 } 088 089 090 //************************************************************************** 091 // PUBLIC METHODS 092 //************************************************************************** 093 094 //-------------------------------------------------------------------------- 095 /** 096 Returns the Roman numeral string value of the Roman numeral. 097 */ 098 @Override 099 public String toString() 100 { 101 return mString; 102 } 103 104 //-------------------------------------------------------------------------- 105 /** 106 Returns the integer value of the Roman numeral. 107 @deprecated use intValue() 108 */ 109 public int getIntValue() 110 { 111 return mValue; 112 } 113 //-------------------------------------------------------------------------- 114 @Override 115 public int intValue() 116 { 117 return mValue; 118 } 119 120 //-------------------------------------------------------------------------- 121 @Override 122 public long longValue() 123 { 124 return mValue; 125 } 126 127 //-------------------------------------------------------------------------- 128 @Override 129 public float floatValue() 130 { 131 return mValue; 132 } 133 134 //-------------------------------------------------------------------------- 135 @Override 136 public double doubleValue() 137 { 138 return mValue; 139 } 140 141 //-------------------------------------------------------------------------- 142 /** 143 Numerically compares two RomanNumeral objects. 144 */ 145 public int compareTo(RomanNumeral inObj) 146 { 147 return NumUtil.compare(intValue(), inObj.intValue()); 148 } 149 150 //************************************************************************** 151 // PRIVATE METHODS 152 //************************************************************************** 153 154 //-------------------------------------------------------------------------- 155 /* 156 From wikipedia: 157 <br /><br /> 158 Subtractive principle 159 <br /><br /> 160 Generally, Roman numerals are written in descending order from left to right, 161 and are added sequentially, for example MMVI (2006) is interpreted as 1000 + 1000 + 5 + 1. 162 <br /><br /> 163 Certain combinations employ a subtractive principle, which specifies that where a symbol 164 of smaller value precedes a symbol of larger value, the smaller value is subtracted from 165 the larger value, and the result is added to the total. For example, in MCMXLIV (1944), t 166 he symbols C, X and I each precede a symbol of higher value, and the result is interpreted as 167 1000 plus (1000 minus 100) plus (50 minus 10) plus (5 minus 1). 168 <br /><br /> 169 A numeral for 10n (I, X, or C) may not precede a numeral larger than 10n+1, where n is an integer. 170 That is, I may precede V and X, but not L or C; X may precede L or C, but not D or M. 171 The numerals 5?10n (V, L, or D) may not be followed by a numeral of greater or equal value. 172 Any symbol that appears more than once consecutively may not be followed by a symbol of larger value. 173 */ 174 private void calculateString(int inValue) 175 { 176 if (inValue < 1) 177 { 178 throw new InvalidValueException("Roman numerals cannot be less than or equal to zero!"); 179 } 180 else if (inValue > 3999) 181 { 182 throw new InvalidValueException("Roman numerals greater than 3999 are not currently supported!"); 183 } 184 185 StringBuilder buffer = new StringBuilder(); 186 int remainingValue = inValue; 187 while (remainingValue > 0) 188 { 189 for (int i = 0; i < sHiToLowSymbols.size(); i++) 190 { 191 RomanNumeralSymbol symbol = sHiToLowSymbols.get(i); 192 193 if (symbol.getIntValue() > remainingValue) 194 { 195 // Is a subtractive combination appropriate? 196 for (int j = sHiToLowSymbols.size() - 1; j > i; j--) 197 { 198 RomanNumeralSymbol lowerSymbol = sHiToLowSymbols.get(j); 199 200 if (lowerSymbol != null 201 && symbol.getIntValue() - lowerSymbol.getIntValue() <= remainingValue 202 && lowerSymbol.canPrecede(symbol)) 203 { 204 buffer.append(lowerSymbol.getLetter()); 205 buffer.append(symbol.getLetter()); 206 remainingValue -= (symbol.getIntValue() - lowerSymbol.getIntValue()); 207 break; 208 } 209 } 210 } 211 else 212 { 213 while (remainingValue >= symbol.getIntValue()) 214 { 215 buffer.append(symbol.getLetter()); 216 remainingValue -= symbol.getIntValue(); 217 } 218 219 if (remainingValue > 0) i--; 220 } 221 } 222 } 223 224 mString = buffer.toString(); 225 mValue = inValue; 226 } 227 228 //-------------------------------------------------------------------------- 229 private void parseString(String inString) 230 { 231 RomanNumeralSymbol previousSymbol = null; 232 mValue = 0; 233 for (char theChar : inString.toCharArray()) 234 { 235 RomanNumeralSymbol symbol = RomanNumeralSymbol.valueOf(theChar); 236 if (null == symbol) 237 { 238 throw new InvalidValueException("'" + theChar + "' is not a recognized Roman numeral symbol!"); 239 } 240 241 mValue += symbol.getIntValue(); 242 if (previousSymbol != null 243 && symbol.getIntValue() > previousSymbol.getIntValue()) 244 { 245 mValue -= 2 * previousSymbol.getIntValue(); 246 } 247 248 previousSymbol = symbol; 249 } 250 251 mString = inString; 252 } 253}