com.hfg.math.RomanNumeral Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.math;
import java.util.ArrayList;
import java.util.List;
import com.hfg.exception.InvalidValueException;
//------------------------------------------------------------------------------
/**
Roman numeral representation. The current implementation works for numbers up to 4000.
Ex: given RomanNumeral num = new RomanNumeral(1944);
num.toString()
will be equal to "MCMXLIV".
@author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
// TODO: Add support for values 4000+ that need a macron (bar above the letter) for display.
// The macron indicates that the number is multiplied by 1000.
public class RomanNumeral extends Number implements Comparable
{
private String mString;
private int mValue;
private static List sHiToLowSymbols = new ArrayList(7);
static
{
sHiToLowSymbols.add(RomanNumeralSymbol.M);
sHiToLowSymbols.add(RomanNumeralSymbol.D);
sHiToLowSymbols.add(RomanNumeralSymbol.C);
sHiToLowSymbols.add(RomanNumeralSymbol.L);
sHiToLowSymbols.add(RomanNumeralSymbol.X);
sHiToLowSymbols.add(RomanNumeralSymbol.V);
sHiToLowSymbols.add(RomanNumeralSymbol.I);
}
//**************************************************************************
// CONSTRUCTORS
//**************************************************************************
//--------------------------------------------------------------------------
/**
Create a RomanNumeral object from an integer value.
*/
public RomanNumeral(int inValue)
{
super();
calculateString(inValue);
}
//--------------------------------------------------------------------------
/**
Create a RomanNumeral object from a Roman numeral string such as "MCMXLIV".
*/
public RomanNumeral(String inValue)
{
super();
parseString(inValue);
}
//**************************************************************************
// PUBLIC METHODS
//**************************************************************************
//--------------------------------------------------------------------------
/**
Returns the Roman numeral string value of the Roman numeral.
*/
@Override
public String toString()
{
return mString;
}
//--------------------------------------------------------------------------
/**
Returns the integer value of the Roman numeral.
@deprecated use intValue()
*/
public int getIntValue()
{
return mValue;
}
//--------------------------------------------------------------------------
@Override
public int intValue()
{
return mValue;
}
//--------------------------------------------------------------------------
@Override
public long longValue()
{
return mValue;
}
//--------------------------------------------------------------------------
@Override
public float floatValue()
{
return mValue;
}
//--------------------------------------------------------------------------
@Override
public double doubleValue()
{
return mValue;
}
//--------------------------------------------------------------------------
/**
Numerically compares two RomanNumeral objects.
*/
public int compareTo(RomanNumeral inObj)
{
return NumUtil.compare(intValue(), inObj.intValue());
}
//**************************************************************************
// PRIVATE METHODS
//**************************************************************************
//--------------------------------------------------------------------------
/*
From wikipedia:
Subtractive principle
Generally, Roman numerals are written in descending order from left to right,
and are added sequentially, for example MMVI (2006) is interpreted as 1000 + 1000 + 5 + 1.
Certain combinations employ a subtractive principle, which specifies that where a symbol
of smaller value precedes a symbol of larger value, the smaller value is subtracted from
the larger value, and the result is added to the total. For example, in MCMXLIV (1944), t
he symbols C, X and I each precede a symbol of higher value, and the result is interpreted as
1000 plus (1000 minus 100) plus (50 minus 10) plus (5 minus 1).
A numeral for 10n (I, X, or C) may not precede a numeral larger than 10n+1, where n is an integer.
That is, I may precede V and X, but not L or C; X may precede L or C, but not D or M.
The numerals 5?10n (V, L, or D) may not be followed by a numeral of greater or equal value.
Any symbol that appears more than once consecutively may not be followed by a symbol of larger value.
*/
private void calculateString(int inValue)
{
if (inValue < 1)
{
throw new InvalidValueException("Roman numerals cannot be less than or equal to zero!");
}
else if (inValue > 3999)
{
throw new InvalidValueException("Roman numerals greater than 3999 are not currently supported!");
}
StringBuilder buffer = new StringBuilder();
int remainingValue = inValue;
while (remainingValue > 0)
{
for (int i = 0; i < sHiToLowSymbols.size(); i++)
{
RomanNumeralSymbol symbol = sHiToLowSymbols.get(i);
if (symbol.getIntValue() > remainingValue)
{
// Is a subtractive combination appropriate?
for (int j = sHiToLowSymbols.size() - 1; j > i; j--)
{
RomanNumeralSymbol lowerSymbol = sHiToLowSymbols.get(j);
if (lowerSymbol != null
&& symbol.getIntValue() - lowerSymbol.getIntValue() <= remainingValue
&& lowerSymbol.canPrecede(symbol))
{
buffer.append(lowerSymbol.getLetter());
buffer.append(symbol.getLetter());
remainingValue -= (symbol.getIntValue() - lowerSymbol.getIntValue());
break;
}
}
}
else
{
while (remainingValue >= symbol.getIntValue())
{
buffer.append(symbol.getLetter());
remainingValue -= symbol.getIntValue();
}
if (remainingValue > 0) i--;
}
}
}
mString = buffer.toString();
mValue = inValue;
}
//--------------------------------------------------------------------------
private void parseString(String inString)
{
RomanNumeralSymbol previousSymbol = null;
mValue = 0;
for (char theChar : inString.toCharArray())
{
RomanNumeralSymbol symbol = RomanNumeralSymbol.valueOf(theChar);
if (null == symbol)
{
throw new InvalidValueException("'" + theChar + "' is not a recognized Roman numeral symbol!");
}
mValue += symbol.getIntValue();
if (previousSymbol != null
&& symbol.getIntValue() > previousSymbol.getIntValue())
{
mValue -= 2 * previousSymbol.getIntValue();
}
previousSymbol = symbol;
}
mString = inString;
}
}