001package com.hfg.math;
002
003import java.util.Map;
004
005import com.hfg.util.collection.OrderedMap;
006
007
008//------------------------------------------------------------------------------
009/**
010 Individual Roman numeral symbol representation. Used by the RomanNumeral class,
011 it encapsulates the logic of which symbols can precede a given symbol.
012 <p>
013 See <a href='http://en.wikipedia.org/wiki/Roman_numerals'>http://en.wikipedia.org/wiki/Roman_numerals</a>
014 </p>
015 @author J. Alex Taylor, hairyfatguy.com
016 */
017//------------------------------------------------------------------------------
018// com.hfg XML/HTML Coding Library
019//
020// This library is free software; you can redistribute it and/or
021// modify it under the terms of the GNU Lesser General Public
022// License as published by the Free Software Foundation; either
023// version 2.1 of the License, or (at your option) any later version.
024//
025// This library is distributed in the hope that it will be useful,
026// but WITHOUT ANY WARRANTY; without even the implied warranty of
027// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
028// Lesser General Public License for more details.
029//
030// You should have received a copy of the GNU Lesser General Public
031// License along with this library; if not, write to the Free Software
032// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
033//
034// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
035// jataylor@hairyfatguy.com
036//------------------------------------------------------------------------------
037   
038public class RomanNumeralSymbol implements Comparable<RomanNumeralSymbol>
039{
040   //**************************************************************************
041   // PRIVATE FIELDS
042   //**************************************************************************
043
044   char   mLetter;
045   int    mValue;
046
047
048   private static Map<Character, RomanNumeralSymbol> sValueMap = new OrderedMap<Character, RomanNumeralSymbol>(10);
049
050   //**************************************************************************
051   // PUBLIC FIELDS
052   //**************************************************************************
053
054   /** The Roman numeral for the integer 1. */
055   public static RomanNumeralSymbol I = new RomanNumeralSymbol('I', 1);
056   /** The Roman numeral for the integer 5. */
057   public static RomanNumeralSymbol V = new RomanNumeralSymbol('V', 5);
058   /** The Roman numeral for the integer 10. */
059   public static RomanNumeralSymbol X = new RomanNumeralSymbol('X', 10);
060   /** The Roman numeral for the integer 50. */
061   public static RomanNumeralSymbol L = new RomanNumeralSymbol('L', 50);
062   /** The Roman numeral for the integer 100. */
063   public static RomanNumeralSymbol C = new RomanNumeralSymbol('C', 100);
064   /** The Roman numeral for the integer 500. */
065   public static RomanNumeralSymbol D = new RomanNumeralSymbol('D', 500);
066   /** The Roman numeral for the integer 1000. */
067   public static RomanNumeralSymbol M = new RomanNumeralSymbol('M', 1000);
068
069   //**************************************************************************
070   // CONSTRUCTORS
071   //**************************************************************************
072
073   //--------------------------------------------------------------------------
074   private RomanNumeralSymbol(char inLetter, int inValue)
075   {
076      mLetter = inLetter;
077      mValue  = inValue;
078
079      sValueMap.put(mLetter, this);
080   }
081
082   //**************************************************************************
083   // PUBLIC METHODS
084   //**************************************************************************
085
086   //--------------------------------------------------------------------------
087   /**
088    Returns the RomanNumeralSymbol value for the specified character.
089    */
090   public static RomanNumeralSymbol valueOf(char inLetter)
091   {
092      return sValueMap.get(Character.toUpperCase(inLetter));
093   }
094
095   //--------------------------------------------------------------------------
096   /**
097    Returns an array of all the RomanNumeralSymbol values.
098    */
099   public static RomanNumeralSymbol[] values()
100   {
101      return sValueMap.values().toArray(new RomanNumeralSymbol[1]);
102   }
103
104   //--------------------------------------------------------------------------
105   /**
106    Returns the character value of the Roman numeral symbol.
107    */
108   public char getLetter()
109   {
110      return mLetter;
111   }
112
113   //--------------------------------------------------------------------------
114   /**
115    Returns the integer value of the Roman numeral symbol.
116    */
117   public int getIntValue()
118   {
119      return mValue;
120   }
121
122   //--------------------------------------------------------------------------
123   /**
124    Returns the Roman numeral string value of the Roman numeral symbol.
125    */
126   @Override
127   public String toString()
128   {
129      return mLetter + "";
130   }
131
132   //--------------------------------------------------------------------------
133   /**
134    Numerically compares two RomanNumeralSymbol objects.
135    */
136   public int compareTo(RomanNumeralSymbol inObj)
137   {
138      return NumUtil.compare(mValue, inObj.mValue);
139   }
140
141   //--------------------------------------------------------------------------
142   /**
143    Returns whether this symbol can legally precede the specified symbol given
144    the subtractive principle.
145    */
146   /*
147   From wikipedia:
148
149   Subtractive principle
150
151   Generally, Roman numerals are written in descending order from left to right,
152   and are added sequentially, for example MMVI (2006) is interpreted as 1000 + 1000 + 5 + 1.
153
154   Certain combinations employ a subtractive principle, which specifies that where a symbol
155   of smaller value precedes a symbol of larger value, the smaller value is subtracted from
156   the larger value, and the result is added to the total. For example, in MCMXLIV (1944), t
157   he symbols C, X and I each precede a symbol of higher value, and the result is interpreted as
158   1000 plus (1000 minus 100) plus (50 minus 10) plus (5 minus 1).
159
160   A numeral for 10n (I, X, or C) may not precede a numeral larger than 10n+1, where n is an integer.
161   That is, I may precede V and X, but not L or C; X may precede L or C, but not D or M.
162   The numerals 5?10n (V, L, or D) may not be followed by a numeral of greater or equal value.
163   Any symbol that appears more than once consecutively may not be followed by a symbol of larger value.
164    */
165   public boolean canPrecede(RomanNumeralSymbol inSymbol)
166   {
167      boolean result;
168      if (inSymbol.getIntValue() < getIntValue())
169      {
170         result = true;
171      }
172      else
173      {
174         double predecessorLog10 = Math.log10((double)getIntValue());
175
176         result = (predecessorLog10%1 == 0
177                   && (inSymbol.getIntValue() <= Math.pow(10, (predecessorLog10 + 1))));
178      }
179
180      return result;
181   }
182}