2010-11-10 10 views
13

chiunque può consigliare una libreria di classi o un motivo disegno che modelli somme di denaro?Come modellare una somma di denaro in Java

Credo che dovrebbe sostenere: (. Rispettando alcune convenzioni di arrotondamento (cfr ad esempio Banker's Rounding))

  • più valute
  • un meccanismo per indicare il numero di decimali
  • matematica
  • serializzazione e da una rappresentazione String
  • ...?

I.e. 19.99 USD potrebbero essere serializzati in "USD-2-00000001999"
(mentre 2 indica il numero di cifre decimali)

risposta

8

vorrei controllare il modulo monetary da il progetto JScience (di Jean Marie Dautelle).

A seconda delle vostre esigenze, Stephen Colebourne iniziato Joda-Money ("un progetto più mirato" [di JScience]) qualche tempo fa. Ma non è ancora disponibile la versione completa (la versione 0.5 è stata rilasciata un anno fa).

+0

Joda-Money sembra fantastico! Ma lo sviluppo sembra essersi fermato dal 2009! .... tempo di saltare a bordo e aiutare lo sviluppatore. – Elister

+3

Joda-Money è abbastanza stabile, quindi non ha bisogno di molto sviluppo. Detto questo, apprezzerei feedback, recensioni e piccole proposte di miglioramento (ad es. Forchette GitHub) – JodaStephen

+0

Release 0.10.0 è disponibile ora. Per me è ben testato e abbastanza stabile. Questa libreria risolve tutti i nostri metodi di calcolo del denaro. – mahesh

0

questo sembra che potrebbe aiutare, ma non ho alcuna esperienza con esso: http://quantlib.org/index.shtml

+0

Sembra che ci sia una porta Java: ** ** JQuantlib (http://www.jquantlib.org). Osservando le API, non vedo immediatamente una classe Amount ... In una classe come Coupon, gli importi sono modellati come double. Potrei sbagliarmi. Thx comunque! – Jan

4

In aggiunta a ciò che Paul ha detto, c'è lo Money Pattern di Martin Fowler.

4

C'è JSR 354 JavaMoney che dovrebbe diventare una parte di Java 9. dare un'occhiata a presentation per ulteriori dettagli.

Questo JSR dovrebbe essere un sostituto di Joda Money, ma attualmente solo Joda Money è stabile e testato in produzione.

Inoltre è possibile dare uno sguardo su questo biblioteche:

1

Questo è un esempio completo della classe java money con Analysis patte di Martin Fowler 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(); 
    } 
} 

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