/*
-----BEGIN PGP SIGNED MESSAGE-----
*/
import java.text.*; // Release level 1.1 or above
import java.util.*; // Locales
import java.math.*; // BigDEcimal, BigInteger
import java.io.*; // for test code in main() only
/** A class of useful methods for financial applications that use BigIntegers
internally but also have to cope with floating point numbers and strings.
The idea is that 100 per cent of the rounding, formatting and parsing for
an entire bookkeeping or similar application should occur in one class
like this one. The code is internationalized to accept and produce decimal
and currency strings based on the default or specified locale of whatever
application instantiates this class.
The
BDFormat class must be in the same directory as this class. BDFormat
does BigDecimal formatting and parsing. There is a limit of 19 digits for
the integer portion of your application's numbers. Revisions to BDFormat
in the future may change this. Meanwhile you'll get a message (in English)
returned if you try to use too big of a BigDecimal.
The two-parameter morphs of displayAsDecimal() and displayAsCurrency() allow
right-justified output. Just specify the field size.
When needed, rounding uses the method favored by bankers in which discarded
fractions > .5 round the last remaining digit away from zero, and
discarded fractions == .5 round the last remaining digit up
or down towards the nearest *even* digit.
Right-justified output is improved so the field size you specify is the exact
length of the result string (if the result fits in the field size). With
multiple instances of BigCurrencyDecimal you can display (and align decimals)
accross multiple currencies at the same time. Suggestions and criticism
are welcome, but use at your own risk.
@author Tony Dahlman
@version 1.1 rev. 98/10/20
*/
public class BigCurrencyDecimal {
/** The number of decimal digits required by this locale's currency. */
private int digits;
/** The default rounding mode used by BigDecimal conversions. */
private static final int DEFAULT_RMODE = BigDecimal.ROUND_HALF_EVEN;
/** The rounding mode actually in effect. Set it with setRoundingMode(int). */
private int rmode = DEFAULT_RMODE;
/** The BDFormat class does BigDecimal formatting and parsing */
private BDFormat bdnf;
/** The default constructor sets up formatting objects for the default
locale. Locale-specific constants and limits are set. The
FieldPosition object can find the decimal point in the decimal strings
produced by NumberFormat.format()
*/
public BigCurrencyDecimal() {
this( Locale.getDefault() );
}
/** The locale-specific constructor sets up formatting objects for the
requested locale. Use java.util.Locale class to create a Locale object.
A list of 2-letter language codes
is available here, and a list of 2-letter country codes
is available here. Locale-specific constants are set
by this constructor.
*/
public BigCurrencyDecimal(Locale loc) {
NumberFormat cf = NumberFormat.getCurrencyInstance(loc);
digits = cf.getMaximumFractionDigits();
bdnf = new BDFormat(loc);
}
/** Although the calling application will use only BigIntegers internally,
calculating averages or doing currency conversions will occasionally yield
floating point numbers. This method gets rid of them by
converting back to an internal BigInteger object. This implements
bankers rounding (as described above).
*/
BigInteger roundInternalBigDecimal( BigDecimal bd ) {
bd = bd.setScale(0, rmode);
return bd.toBigInteger();
}
/** When the user or a spreadsheet supplies your application
with floating point input, this method converts floats and doubles
to internal BigInteger objects.
*/
BigInteger importExternalDouble( double dval ) {
BigDecimal bd = new BigDecimal(dval);
bd = bd.setScale(digits, rmode);
bd = bd.movePointRight(digits);
return bd.toBigInteger();
}
/** When the user or a spreadsheet supplies your application
with floating point input, this method converts BigDecimal objects
to internal BigInteger objects.
*/
BigInteger importExternalBigDecimal( BigDecimal bd ) {
bd = bd.setScale(digits, rmode);
bd = bd.movePointRight(digits);
return bd.toBigInteger();
}
/** Makes a string, formatted as currency, for output or
display, as in a report, journal, etc. For example, you have the
BigInteger value 123456. displayAsCurency() yields the strings,
$1,234.56 (en_US locale) or kr 1.234,56 (da_DK locale).
In Italy where they've done away with "pennies", 123456
is dislpayed as L. 123.456 .
*/
String displayAsCurrency( BigInteger bi ) {
BigDecimal bd = new BigDecimal(bi, digits);
return bdnf.curformat(bd);
}
/** Makes a right-justified string, formatted as currency, for output
or display as in a report, journal, etc. In this morph of the
displayAsCurrency() function, you supply a BigInteger and
a field size as parameters. The output will be right-justified
(if possible) with decimal points aligned. We assume a monospaced
font. In JDK 1.2 java.awt.font.TextLayout will open up decent,
optimized, i18n methods for using prettier fonts.
@returns a currency-formatted string of length fieldsize.
*/
String displayAsCurrency( BigInteger bi, int fieldsize ) {
BigDecimal bd = new BigDecimal(bi, digits);
return bdnf.curformat(bd, fieldsize);
}
/** Makes a string, formatted as a currency-like decimal for
output or display, as in a report, journal, ledger, etc. For
example you have the BigInteger -123456. displayAsDecimal() yields
the strings, -1,234.56 (en_US locale) or -1.234,56 (da_DK locale).
In Italy where they've done away with "pennies", -123456 is
displayed as -123.456 .
*/
String displayAsDecimal( BigInteger bi ) {
BigDecimal bd = new BigDecimal( bi, digits );
return bdnf.format(bd);
}
/** Makes a right-justified string, formatted as a currency-like decimal
for output or display, as in a report, journal, ledger, etc. In this
morph of the displayAsDecimal() function, you provide a a BigInteger
and a field size as parameters. The output will be right-justified
(if possible) with decimals aligned. We assume a monospaced
font. In JDK 1.2 java.awt.font.TextLayout will open up decent,
optimized, i18n methods for using prettier fonts.
@returns a currency-like decimal string of length fieldsize.
*/
String displayAsDecimal( BigInteger bi, int fieldsize ) {
BigDecimal bd = new BigDecimal( bi, digits );
return bdnf.format(bd, fieldsize);
}
/** Numeric user input, in decimal format, is converted
to a double, using banker's rounding (described above) if
required to achieve the locale-specified number of decimal
places. Returns BigDecimal 0.0 if parsing fails.
*/
BigDecimal decimalStringToDecimal( String input ) {
return bdnf.parse( input );
}
/** Numeric user input, in currency format, is converted
to a double, using banker's rounding (described above) if
required to achieve the locale-specified number of decimal
places. Returns BigDecimal 0.0 if parsing fails.
*/
BigDecimal currencyStringToDecimal( String input ) {
return bdnf.parse ( input );
}
/** The BigDecimal rounding mode (as an int) currently in effect. */
int getRoundingMode() {
return rmode;
}
/** Change to a different BigDecimal rounding/truncating mode */
void setRoundingMode( int i ) {
if (i>=0 && i<7) {
rmode = i;
}
}
/** Command-line test code: you can specify two arguments, the two-letter
codes for language (lower case) and COUNTRY (upper case) in order to
exercise this class's international features.
If you specify foreign locales, be sure to use its correct decimal
separator and sign convention (e.g. parentheses for negative currency values
in the en_US locale) when supplying input. A list of 2-letter language codes
is available here, and a list of 2-letter country codes
is available here. Even if your operating system implements
unicode, all the fonts for all the countries and languages may
not be available.
The prompts and text print-out here are all in English, although the
numbers, currencies, sign conventions, etc. should fully implement "i18n".
*/
public static void main(String[] args)
throws IOException, NumberFormatException {
String ls = System.getProperty("line.separator","\n");
Locale defLoc = Locale.getDefault();
// set Locale from args...
Locale loc;
if (args.length == 2) {
loc = new Locale( args[0], args[1] );
Locale.setDefault(loc);
} else {
loc = defLoc;
System.out.println(
"To try a non-default locale, supply the 2-letter language and " + ls
+" country codes as two command-line args.");
}
System.out.println("Setting language to "
+ loc.getDisplayLanguage()
+" and country to "
+ loc.getDisplayCountry() + ls);
BigCurrencyDecimal cd = new BigCurrencyDecimal(loc);
BufferedReader ins =
new BufferedReader(new InputStreamReader(System.in));
String input;
int fieldsize = 40;
System.out.print("Enter a field size for right-aligned output: ");
try {
fieldsize = new Double( ins.readLine() ).intValue();
} catch ( NumberFormatException e ) {
System.out.println("Let's just set the fieldsize to 40...");
}
while (true) {
System.out.print("Enter an amount: ");
input = ins.readLine().trim();
if (input.length() < 2 || input.charAt(0) == 'q')
System.exit(0);
BigDecimal bdec = cd.decimalStringToDecimal(input);
BigDecimal bcur = cd.currencyStringToDecimal(input);
if (bdec.doubleValue() == 0.0) bdec = bcur;
BigInteger bi = cd.importExternalBigDecimal(bdec);
System.out.println(
"Importing that as external or rounding it as internal yields a BigInteger:" + ls
+ " importExternalBigDecimal(bdec) => "
+ bi.toString() + ls
+ " roundInternalBigDecimal(bdec) => "
+ cd.roundInternalBigDecimal(bdec).toString() + ls
+ " importExternalDouble(double) => "
+ cd.importExternalDouble( bdec.doubleValue() ) + ls
+ "Using " + bi.toString() + " to make decimal & currency strings:" + ls
+ " displayAsDecimal(BigInteger) => "
+ cd.displayAsDecimal(bi) + ls
+ " displayAsCurrency(BigInteger) => "
+ cd.displayAsCurrency(bi) + ls
+ " displayAsDecimal(BigInteger,int) and displayAsCurrency(BigInteger,int) =>" + ls
+ cd.displayAsDecimal(bi,fieldsize) + ls
+ cd.displayAsCurrency(bi,fieldsize) );
if (defLoc != loc) {
BigCurrencyDecimal cd2 = new BigCurrencyDecimal(defLoc);
BigInteger bi2 = cd2.importExternalBigDecimal(bdec);
System.out.println(
" The default locale would have displayed:" + ls
+ cd2.displayAsDecimal(bi2,fieldsize) + ls
+ cd2.displayAsCurrency(bi2,fieldsize) );
}
System.out.println(); System.out.flush();
} // end while()
} // main()
}
/*
-----BEGIN PGP SIGNATURE-----
Version: 2.6.2
iQCVAwUBNn2ibGbsFmrW0oYFAQHYYgP/Rrf3QI2o0ga5sLS7p10Lne/it6Z01C71
j3Kcy4aFz6d+IO09rrqLFeI7Io67QP1M8jnsFF0woK1jkOoJW0UzLm5qZSNWK49I
RUuAwv/PW/eoqTfv8cyaC/sako+7lvTPZyqZMVBLDN/mjdp0pJlFRaSutgZh7Rye
k7pJQrInm8I=
=c35+
-----END PGP SIGNATURE-----
*/