2010-11-10 7 views
13

Kann jemand empfehlen, eine Klassenbibliothek oder ein Entwurfsmuster dass Modelle Mengen an Geld modellieren?Wie eine Menge Geld in Java

Ich denke, es unterstützen sollte: (. Bestimmten Rundungs ​​Konventionen respektieren (cfr dh Banker's Rounding))

  • mehrere Währungen
  • ein Mechanismus die Anzahl der Dezimalstellen, um anzuzeigen
  • math
  • Serialisierung und aus einer String-Darstellung
  • ...?

I.e. 19,99 USD könnte in „USD-2-00000001999
(Die 2 gibt die Anzahl der Nachkommastellen) serialisiert werden

Antwort

8

ich würde überprüfen sie die monetary Modul aus das Projekt JScience (von Jean Marie Dautelle).

Abhängig von Ihren genauen Bedürfnissen, Stephen Colebourne begann Joda-Money ("ein konzentrierter Projekt" [als JScience]) vor einiger Zeit. Aber es gibt noch keine vollständige Version (Version 0.5 wurde vor einem Jahr veröffentlicht).

+0

Joda-Money sieht gut aus, aber die Entwicklung scheint seit 2009 gestoppt zu sein! .... Zeit, um an Bord zu springen und dem Entwickler zu helfen. – Elister

+3

Joda-Money ist ziemlich stabil, braucht also nicht viel Entwicklung. Ich würde mich freuen, Feedback, Bewertungen und kleine Verbesserungsvorschläge (zB GitHub Gabeln) – JodaStephen

+0

Release 0.10.0 ist jetzt da draußen. Für mich ist es gut getestet und ziemlich stabil. Diese Bibliothek löst all unser Money-Computing. – mahesh

0

dies wie könnte es helfen soll, aber ich habe keine Erfahrung mit ihm: http://quantlib.org/index.shtml

+0

Es scheint eine Java-Port zu sein: ** JQuantlib ** (http://www.jquantlib.org). Wenn ich mir die APIs anschaue, sehe ich jedoch nicht sofort eine Amount-Klasse ... In einer Klasse wie Coupon werden die Beträge als doppelt modelliert. Ich könnte mich irren. Thx sowieso! – Jan

4

Hinzufügen zu dem, was Paul sagte, gibt es Martin Fowlers Money Pattern.

4

Es gibt JSR 354 JavaMoney, das ein Teil von Java 9 werden sollte. Blick in presentation für weitere Details.

Dieser JSR sollte ein Ersatz für Joda Money sein, aber derzeit ist nur Joda Money stabil und in der Produktion getestet.

Sie können auch einen Blick auf diese Bibliotheken nehmen:

1

Dies ist ein vollständiges Beispiel der Java-Geldklasse mit Martin Fowlers Analyse patte rn:

package com.console.utils.value; 

import com.console.core.exceptions.UnknownCurrencyCodeException; 
import java.io.Serializable; 
import java.math.BigDecimal; 
import java.math.MathContext; 
import java.math.RoundingMode; 
import java.text.NumberFormat; 
import java.util.Currency; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
import org.junit.Assert; 
import static java.math.RoundingMode.HALF_UP; 

/** 
* 
* @author farouka 
*/ 
public class Money implements Serializable { 

    /** 
    * Why me 
    */ 
    private static final int[] cents = new int[]{1, 10, 100, 1000}; 

    private BigDecimal amount; 

    private Currency currency; 

    //private MathContext DEFAULT_CONTEXT = new MathContext(2, HALF_UP); 

    private MathContext DEFAULT_CONTEXT = new MathContext(10, RoundingMode.HALF_DOWN); 

    public Money(long amount, Currency currency) { 
    this.currency = currency; 
    this.amount = BigDecimal.valueOf(amount, currency.getDefaultFractionDigits()); 
    } 

    /** 
    * Creates a currency object from the long value provided assuming the long value 
    * represents the base currency in the least monetary unit. For eg, new Money(500, "GBP") 
    * is assumed to mean 5.00 great british pounds 
    * @param amount in base monetary unit 
    * @param currCode 
    * @throws com.console.core.exceptions.UnknownCurrencyCodeException 
    */ 
    public Money(long amount, String currCode) throws UnknownCurrencyCodeException { 
    this(amount, Currency.getInstance(currCode)); 
    } 

    /** 
    * Construct an IMMUTABLE money object from a double. It is assumed that 
    * the whole part of the double is the Money with the fractional part representing 
    * lowest denominator of the currency. For eg, new Money (50.99, "GBP") is assumed 
    * to be 50 pounds and 99 pence. 
    * PS. 89.788 will be truncated to 89.78 based on the defaultcurrencydigit of the currency 
    * @param amount 
    * @param curr 
    */ 
    public Money(double amount, Currency curr) { 
    this.currency = curr; 
    BigDecimal bd = BigDecimal.valueOf(amount); 
    this.amount = bd.setScale(centFactor(), HALF_UP); 
    } 

    private Money() { 
    } 

    /** 
    * Construct an IMMUTABLE money object from a double. It is assumed that 
    * the whole part of the double is the Money with the fractional part representing 
    * lowest denominator of the currency. For eg, new Money (50.99, "GBP") is assumed 
    * to be 50 pounds and 99 pence. 
    * PS. 89.788 will be truncated to 89.78 based on the defaultcurrencydigit of the currency 
    * code supplied 
    * @param amount 
    * @param currCode iso 4217 currency code 
    * @throws com.console.core.exceptions.UnknownCurrencyCodeException 
    */ 
    public Money(double amount, String currCode) throws UnknownCurrencyCodeException { 
    this.currency = Currency.getInstance(currCode); 
    BigDecimal bd = BigDecimal.valueOf(amount); 
    this.amount = bd.setScale(currency.getDefaultFractionDigits(), HALF_UP); 
    } 

    /** 
    * Constructs an IMMUTABLE money from a BigDecimal. the BigDecimal provided is only scaled 
    * to used the default digits in currency object represented by the sting parameter 
    * @param bigDecimal 
    * @param currCode ISO 4217 cuurency code 
    * @throws com.console.core.exceptions.UnknownCurrencyCodeException 
    */ 
    public Money(BigDecimal bigDecimal, String currCode) throws UnknownCurrencyCodeException {  
    this.currency = Currency.getInstance(currCode); 
    this.amount = bigDecimal.setScale(currency.getDefaultFractionDigits(), HALF_UP); 
    } 

    /** 
    * Constructs an IMMUTABLE money from a BigDecimal. the BigDecimal provided is only scaled 
    * to used the default digits in currency object represented by the sting parameter 
    * @param multiply 
    * @param currency 
    */ 
    public Money(BigDecimal bigDecimal, Currency currency) { 
    this.currency = currency; 
    this.amount = bigDecimal.setScale(currency.getDefaultFractionDigits(), HALF_UP); 
    } 

// public boolean assertSameCurrencyAs(Money arg) { 
// return this.currency.getCurrencyCode().equals(arg.currency.getCurrencyCode()); 
// } 
// 
    public boolean assertSameCurrencyAs(Money money) throws IncompatibleCurrencyException{ 
    if (this.currency == null) { 
    throw new IncompatibleCurrencyException("currency.invalid"); 
    } 
    if (money == null) { 
    throw new IncompatibleCurrencyException("currency.invalid"); 
    } 
    Assert.assertEquals("money math mismatch", currency, money.currency); 
    return true; 
    } 

    private int centFactor() { 
    return cents[ getCurrency().getDefaultFractionDigits() ]; 
    } 

    public BigDecimal amount() { 
    return amount; 
    } 

    public long amountAsLong(){ 
    return amount.unscaledValue().longValue(); 
    } 

    public Currency getCurrency() { 
    return currency; 
    } 
// common currencies 
    public static Money dollars(double amount) { 
    Money result = null; 
    try { 
     result = new Money(amount, "USD"); 
    } catch (UnknownCurrencyCodeException ex) { 
     Logger.getLogger(Money.class.getName()).log(Level.SEVERE, null, ex); 
    } 
    return result; 
    } 

    public static Money dollars(long amount) { 
    Money result = null; 
    try { 
     result = new Money(amount, "USD"); 
    } catch (UnknownCurrencyCodeException ex) { 
     Logger.getLogger(Money.class.getName()).log(Level.SEVERE, null, ex); 
    } 
    return result; 
    } 

    public static Money pounds(double amount) { 
    Money result = null; 
    try { 
     result = new Money(amount, "GBP"); 
    } catch (UnknownCurrencyCodeException ex) { 
     Logger.getLogger(Money.class.getName()).log(Level.SEVERE, null, ex); 
    } 
    return result; 
    } 

    public static Money pounds(long amount) { 
    Money result = null; 
    try { 
     result = new Money(amount, "GBP"); 
    } catch (UnknownCurrencyCodeException ex) { 
     Logger.getLogger(Money.class.getName()).log(Level.SEVERE, null, ex); 
    } 
    return result; 
    } 

    public static Money pounds(BigDecimal amount) { 
    Money result = null; 
    try { 
     result = new Money(amount, "GBP"); 
    } catch (UnknownCurrencyCodeException ex) { 
     Logger.getLogger(Money.class.getName()).log(Level.SEVERE, null, ex); 
    } 
    return result; 
    } 


    @Override 
    public int hashCode() { 
    int hash = (int) (amount.hashCode()^(amount.hashCode() >>> 32)); 
    return hash; 
    } 

    @Override 
    public boolean equals(Object other) { 
    return (other instanceof Money && equals((Money) other)); 
    } 

    public boolean equals(Money other) { 
    return (currency.equals(other.currency) && (amount.equals(other.amount))); 
    } 

    public Money add(Money other) throws Exception{ 
    assertSameCurrencyAs(other); 
    return newMoney(amount.add(other.amount, DEFAULT_CONTEXT)); 
    } 

    private int compareTo(Money money) throws Exception { 
    assertSameCurrencyAs(money); 
    return amount.compareTo(money.amount); 
    } 

    public Money multiply(BigDecimal amount) { 
    return new Money(this.amount().multiply(amount, DEFAULT_CONTEXT), currency); 
    } 

    public Money multiply(BigDecimal amount, RoundingMode roundingMode) { 
    MathContext ct = new MathContext(currency.getDefaultFractionDigits(), roundingMode); 
    return new Money(amount().multiply(amount, ct), currency); 
    } 

    private Money newMoney(BigDecimal amount) { 
    return new Money(amount, this.currency); 
    } 

    public Money multiply(double amount) { 
    return multiply(new BigDecimal(amount)); 
    } 

    public Money subtract(Money other) throws Exception { 
    assertSameCurrencyAs(other); 
    return newMoney(amount.subtract(other.amount, DEFAULT_CONTEXT)); 
    } 

    public int compareTo(Object other) throws Exception { 
    return compareTo((Money) other); 
    } 

    public boolean greaterThan(Money other)throws Exception { 
    return (compareTo(other) > 0); 
    } 

// public Money[] allocate(int n){ 
// Money lowResult = newMoney(amount.unscaledValue().longValue()/n); 
// Money highResult = newMoney(lowResult.amount + 1); 
// Money[] results = new Money[n]; 
// int remainder = (int) amount % n; 
//  
// for(int i = 0; i < remainder; i++)results[i] = highResult; 
// for(int i = 0; i < n; i++) results[i] = lowResult; 
//  
// return results; 
// } 
// 
// public Money[]allocate(long[] ratios){ 
// long total = 0; 
// for (int i = 0; i < ratios.length; i++) { 
//  total += ratios[i]; 
// } 
// long remainder = amount; 
// Money[] results = new Money[ratios.length]; 
// for (int i = 0; i < results.length; i++) { 
//  results[i] = newMoney(amount * ratios[i]/total); 
//  remainder -= results[i].amount; 
// } 
// for (int i = 0; i < remainder; i++) { 
//  results[i].amount++; 
// } 
// return results; 
// 
// } 

    public Money divideByNumber(double divisor){ 
    BigDecimal div = BigDecimal.valueOf(divisor); 
    BigDecimal ans = this.amount.divide(div, DEFAULT_CONTEXT); 
    return new Money(ans, this.currency); 
    } 

    public int getQuotient(Money divisor){ 
    BigDecimal ans = this.amount.divide(divisor.amount, RoundingMode.DOWN); 
    return ans.intValue(); 
    } 

    /** 
    * divides toe moneys and return the quotient and Remainder this method has been customised, 
    * for my money transfer needs...sorry 
    * @param divisor 
    * @return 
    */ 
    public int[] getQuotientandRemainder(Money divisor){ 
    int[] ans = new int[2]; 
    BigDecimal[] bdArr = this.amount.divideAndRemainder(divisor.amount, DEFAULT_CONTEXT); 
    BigDecimal quo = bdArr[0]; 
    BigDecimal rem = bdArr[1]; 
    ans[0] = quo.intValue(); 
    if(rem.compareTo(BigDecimal.ZERO) == 0){ 
     ans[1] =0; 
    }else{ 
     ans[1] = 1; 
    } 
    return ans; 
    } 

public String toFormattedString() { 
    NumberFormat nf = NumberFormat.getCurrencyInstance(); 
    nf.setCurrency(currency); 
    nf.setGroupingUsed(true); 
    nf.setMaximumFractionDigits(currency.getDefaultFractionDigits()); 
    return nf.format(this.amount.doubleValue()); 
} 

/** 
    * Returns the ISO-4217 currency code of the currency 
    * attached to this money. 
    * 
    * @return The ISO-4217 currency code. 
    */ 
public String getCurrencyCode() { 
    return currency.getCurrencyCode(); 
} 

    @Override 
    public String toString() { 
     return amount.toString(); 
    } 

/** 
    * Returns the precision for this money. The precision is the total number 
    * of digits that the value can represent. This includes the integer part. 
    * So, 18 would be able to represent: 
    * 

* 1234567890.12345678 
    * 

* 123456789.78 
    * 

* 123456789
    * 

* 0.123456789
    * 
    * @return The precision. 
    */ 
public int precision() { 
    return amount.precision(); 
} 

/** 
    * Returns the 'scale' for this money. The scale is the number of 
    * digits that are moved to the fractional part, assuming that all 
    * digits are represented by a single integer value. For example: 
    * 

* If: 123456789has scaling 2, it would be : 
    * 

* 123456789.78 
    * 
    * @return The scale value. 
    */ 
public int scale() { 
    return amount.scale(); 
} 

/** 
    * Returns the sign for the money (negative or positive). 
    * -1 if negative, 0 if 0.00 (zero), 1 if positive. 
    * 
    * @return The sign of the money. 
    */ 
public int signum() { 
    return amount.signum(); 
} 
} 


And here is the UnknownCurrencyCodeException class 
package com.console.lib.utils; 

/** 
* An exception which is raised when an unrecognised currency code is passed to the 
* Currency class. 
* 
* @author Farouk Alhassan 
* @see Currency 
*/ 
public class UnknownCurrencyCodeException extends Exception { 

    // Reason for exception 
    private String reason = null; 

    /** 
    * Create a new unknown currency code exception. 
    * 
    * @param reason for the exception 
    */ 
    public UnknownCurrencyCodeException(String reason) { 
     this.reason = reason; 
    } 

    /** 
    * Return the reason this exception was raised. 
    * 
    * @return the reason why the string isn't a valid currency code 
    */ 
    public String getReason() { 
     return reason; 
    } 

    /** 
    * Convert the exception to a string 
    * 
    * @return string version of the exception 
    */ 
    public String toString() { 
return getReason(); 
    } 
} 

Dank farouka bei http://cameotutorials.blogspot.com/2009/06/money-class-for-use-in-currency.html