2010-11-10 13 views
13

¿Alguien puede recomendar una biblioteca de clases o un patrón de diseño que modelos cantidades de dinero?Cómo modelar una cantidad de dinero en Java

supongo que debe apoyar: (. Respeten determinadas convenciones de redondeo (cfr es decir Banker's Rounding))

  • múltiples monedas
  • un mecanismo para indicar el número de decimales
  • matemáticas
  • serialización de y de una representación de cadena
  • ...?

I.e. 19,99 USD podrían ser serializados en "USD-2-00000001999"
(2 Mientras que indica el número de decimales)

Respuesta

8

me gustaría comprobar el módulo de monetary el proyecto JScience (por Jean Marie Dautelle).

Dependiendo de sus necesidades exactas, Stephen Colebourne comenzó Joda-Money ("un proyecto más centrado" [que JScience]) hace algún tiempo. Pero todavía no hay una versión completa disponible (la versión 0.5 fue lanzada hace un año).

+0

¡Joda-Money se ve genial! ¡Pero el desarrollo parece haberse detenido desde 2009! .... tiempo para saltar a bordo y ayudar al desarrollador. – Elister

+3

Joda-Money es bastante estable, por lo que no necesita mucho desarrollo. Dicho esto, agradecería los comentarios, las revisiones y las pequeñas propuestas de mejoras (p. Ej., Las horquillas GitHub) – JodaStephen

+0

La versión 0.10.0 está disponible ahora. Para mí está bien probado y es bastante estable. Esta biblioteca resuelve toda nuestra informática de dinero. – mahesh

0

esto parece que podría ayudar, pero no tengo ninguna experiencia con ella: http://quantlib.org/index.shtml

+0

Parece que hay un puerto de Java: ** ** JQuantLib (http://www.jquantlib.org). Sin embargo, al mirar las API, no veo inmediatamente una clase Amount ... En una clase como Coupon, las cantidades se modelan como dobles. Podría estar equivocado ¡De todos modos, Thx! – Jan

4

Agregando a lo que dijo Paul, está Martin Fowler's Money Pattern.

4

Hay JavaMoney JSR 354 que debe convertirse en una parte de Java 9. eche un vistazo a presentation para obtener más detalles.

Este JSR debería ser un reemplazo para Joda Money, pero actualmente solo Joda Money es estable y probado en producción.

También se puede echar un vistazo en esta bibliotecas:

1

Este es un ejemplo completo de la clase de dinero java con patte de análisis de 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(); 
    } 
} 

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

Cuestiones relacionadas