web-dev-qa-db-fra.com

Utilisation de BigDecimal pour travailler avec des devises

J'essayais de créer ma propre classe pour les devises en utilisant des positions longues, mais apparemment, je devrais plutôt utiliser BigDecimal. Quelqu'un pourrait-il m'aider à démarrer? Quelle serait la meilleure façon d'utiliser BigDecimals pour les devises en dollars, comme le faire au moins mais pas plus de 2 décimales pour les cents, etc. L'API pour BigDecimal est énorme et je ne sais pas quelle méthode utiliser. De plus, BigDecimal a une meilleure précision, mais est-ce que tout n'est pas perdu s'il passe par double? Si je fais un nouveau BigDecimal(24.99), en quoi cela diffère-t-il de l’utilisation d’un double? Ou devrais-je utiliser le constructeur qui utilise une String à la place?

60
mk12

Voici quelques astuces:

  1. Utilisez BigDecimal pour les calculs si vous avez besoin de la précision qu'il offre (les valeurs Money en ont souvent besoin).
  2. Utilisez la classe NumberFormat pour l’affichage. Cette classe s’occupera des problèmes de localisation pour les montants en différentes devises. Cependant, il ne prendra que des primitives; Par conséquent, si vous pouvez accepter le petit changement d’exactitude dû à la transformation en une variable double, vous pouvez utiliser cette classe.
  3. Lorsque vous utilisez la classe NumberFormat, utilisez la méthode scale() sur l'instance BigDecimal pour définir la précision et la méthode d'arrondi.

PS: Au cas où vous vous le demanderiez, BigDecimal est toujours meilleur que double, lorsque vous devez représenter des valeurs monétaires dans Java .

PPS:

Création d'instances BigDecimal

C’est assez simple puisque BigDecimal fournit aux constructeurs des objets primitifs , et String. Vous pouvez utiliser ceux-ci, de préférence celui prenant l'objet String . Par exemple,

BigDecimal modelVal = new BigDecimal("24.455");
BigDecimal displayVal = modelVal.setScale(2, RoundingMode.HALF_EVEN);

Affichage de BigDecimal instances

Vous pouvez utiliser les appels de méthode setMinimumFractionDigits et setMaximumFractionDigits pour limiter la quantité de données affichées.

NumberFormat usdCostFormat = NumberFormat.getCurrencyInstance(Locale.US);
usdCostFormat.setMinimumFractionDigits( 1 );
usdCostFormat.setMaximumFractionDigits( 2 );
System.out.println( usdCostFormat.format(displayVal.doubleValue()) );
70
Vineet Reynolds

Je recommanderais un peu de recherche sur le modèle de l'argent. Martin Fowler dans son livre Analysis pattern a couvert cela plus en détail.

public class Money {

    private static final Currency USD = Currency.getInstance("USD");
    private static final RoundingMode DEFAULT_ROUNDING = RoundingMode.HALF_EVEN;

    private final BigDecimal amount;
    private final Currency currency;   

    public static Money dollars(BigDecimal amount) {
        return new Money(amount, USD);
    }

    Money(BigDecimal amount, Currency currency) {
        this(amount, currency, DEFAULT_ROUNDING);
    }

    Money(BigDecimal amount, Currency currency, RoundingMode rounding) {
        this.currency = currency;      
        this.amount = amount.setScale(currency.getDefaultFractionDigits(), rounding);
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public Currency getCurrency() {
        return currency;
    }

    @Override
    public String toString() {
        return getCurrency().getSymbol() + " " + getAmount();
    }

    public String toString(Locale locale) {
        return getCurrency().getSymbol(locale) + " " + getAmount();
    }   
}

Venir à l'utilisation:

Vous représenteriez toutes les sommes en utilisant un objet Money, par opposition à BigDecimal. Si vous représentez de l’argent sous forme de grande décimale, vous aurez la possibilité de formater l’argent partout où vous l’aurez. Imaginez si la norme d'affichage change. Vous devrez faire les modifications partout. Au lieu d'utiliser le modèle Money, vous centralisez le formatage de l'argent en un seul endroit.

Money price = Money.dollars(38.28);
System.out.println(price);
33
Brad

Ou attendez JSR-354 . L'API Java Money et Currency bientôt disponible!

6
Victor Grazi

1) Si vous êtes limité à la précision double, une des raisons d'utiliser BigDecimals est de réaliser des opérations avec les BigDecimals créées à partir de doubles.

2) La variable BigDecimal consiste en une valeur non calibrée d'entiers de précision arbitraire et une échelle d'entiers non négative de 32 bits, tandis que le double enveloppe une valeur du type primitif double dans un objet. Un objet de type Double contient un seul champ dont le type est double

3) Cela ne devrait faire aucune différence

Vous ne devriez avoir aucune difficulté avec le $ et la précision. Une façon de le faire est d'utiliser System.out.printf

1
Diego Dias

Les types numériques primitifs sont utiles pour stocker des valeurs uniques en mémoire. Toutefois, lorsqu’il s’agit de calculs utilisant des types double et float, il ya un problème d’arrondi. Cela se produit car la représentation de la mémoire ne correspond pas exactement à la valeur. Par exemple, une valeur double est supposée prendre 64 bits, mais Java n'utilise pas la totalité des 64 bits. Il ne stocke que ce qu'il pense des parties importantes du nombre. Ainsi, vous pouvez arriver à de mauvaises valeurs lorsque vous ajoutez des valeurs du type float ou double. 

S'il vous plaît voir un clip court https://youtu.be/EXxUSz9x7BM

1
Gregory Nozik

Utilisez BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP) lorsque vous souhaitez arrondir aux 2 décimales les centimes. Soyez conscient des erreurs d'arrondi lorsque vous effectuez des calculs. Vous devez être cohérent lorsque vous arrondissez la valeur monétaire. Arrondissez bien à la fin une fois tous les calculs ou appliquez l’arrondissement à chaque valeur avant d’effectuer un calcul. Lequel utiliser dépend des besoins de votre entreprise, mais en général, je pense que l'arrondir à la fin semble me sembler plus judicieux.

Utilisez un String lorsque vous construisez BigDecimal pour la valeur monétaire. Si vous utilisez double, il y aura une fin de virgule flottante à la fin. Cela est dû à l'architecture de l'ordinateur concernant la façon dont les valeurs double/float sont représentées au format binaire.

0
tim_wonil

Il existe un exemple détaillé de la procédure à suivre sur javapractices.com. Voir en particulier la classe Money , conçue pour simplifier les calculs monétaires plutôt que d'utiliser BigDecimal directement. 

La conception de cette classe Money a pour but de rendre les expressions plus naturelles. Par exemple:

if ( amount.lt(hundred) ) {
 cost = amount.times(price); 
}

L'outil WEB4J a une classe similaire, appelée Decimal , qui est un peu plus raffinée que la classe Money.

0
John O
NumberFormat.getNumberInstance(Java.util.Locale.US).format(num);
0
Heisenberg

Je serais radical. Pas de BigDecimal.

Voici un excellent article https://lemnik.wordpress.com/2011/03/25/bigdecimal-and-your-money/

Des idées d'ici.

import Java.math.BigDecimal;

public class Main {

    public static void main(String[] args) {
        testConstructors();
        testEqualsAndCompare();
        testArithmetic();
    }

    private static void testEqualsAndCompare() {
        final BigDecimal zero = new BigDecimal("0.0");
        final BigDecimal zerozero = new BigDecimal("0.00");

        boolean zerosAreEqual = zero.equals(zerozero);
        boolean zerosAreEqual2 = zerozero.equals(zero);

        System.out.println("zerosAreEqual: " + zerosAreEqual + " " + zerosAreEqual2);

        int zerosCompare = zero.compareTo(zerozero);
        int zerosCompare2 = zerozero.compareTo(zero);
        System.out.println("zerosCompare: " + zerosCompare + " " + zerosCompare2);
    }

    private static void testArithmetic() {
        try {
            BigDecimal value = new BigDecimal(1);
            value = value.divide(new BigDecimal(3));
            System.out.println(value);
        } catch (ArithmeticException e) {
            System.out.println("Failed to devide. " + e.getMessage());
        }
    }

    private static void testConstructors() {
        double doubleValue = 35.7;
        BigDecimal fromDouble = new BigDecimal(doubleValue);
        BigDecimal fromString = new BigDecimal("35.7");

        boolean decimalsEqual = fromDouble.equals(fromString);
        boolean decimalsEqual2 = fromString.equals(fromDouble);

        System.out.println("From double: " + fromDouble);
        System.out.println("decimalsEqual: " + decimalsEqual + " " + decimalsEqual2);
    }
}

Il imprime

From double: 35.7000000000000028421709430404007434844970703125
decimalsEqual: false false
zerosAreEqual: false false
zerosCompare: 0 0
Failed to devide. Non-terminating decimal expansion; no exact representable decimal result.

Que diriez-vous de stocker BigDecimal dans une base de données? Enfer, ça stocke aussi comme une double valeur ??? Au moins, si j'utilise mongoDb sans configuration avancée, il stockera BigDecimal.TEN sous le nom 1E1.

Solutions possibles?

Je suis venu avec une chaîne d’utilisation unique pour stocker BigDecimal en Java en tant que String dans la base de données. Vous disposez d'une validation, par exemple @NotNull, @Min(10), etc. Vous pouvez ensuite utiliser un déclencheur lors de la mise à jour ou de l'enregistrement pour vérifier si la chaîne actuelle est un nombre dont vous avez besoin. Il n'y a pas de déclencheur pour mongo cependant . Existe-t-il une méthode intégrée pour les appels de fonction de déclencheur Mongodb?

Il y a un inconvénient avec lequel je m'amuse - BigDecimal en tant que String dans la définition de Swagger

J'ai besoin de générer de la fanfaronnade, alors notre équipe frontale comprend que je leur passe un nombre présenté comme une chaîne. DateTime par exemple présenté sous forme de chaîne.

Il existe une autre solution intéressante que j'ai lue dans l'article ci-dessus ........ Utilisez long pour stocker des nombres précis.

Une valeur longue standard peut stocker la valeur actuelle de la dette nationale des États-Unis (en cents, et non en dollars) 6477 fois sans aucun débordement. Quoi de plus: c’est un type entier, pas un point flottant. Cela rend le travail plus facile et précis, et garantit un comportement. 


Mettre à jour

https://stackoverflow.com/a/27978223/4587961

Peut-être que dans le futur, MongoDb ajoutera un support pour BigDecimal . https://jira.mongodb.org/browse/SERVER-1393 3.3.8 semble l'avoir fait.

C'est un exemple de la deuxième approche. Utilisez la mise à l'échelle . http://www.technology-ebay.de/the-teams/mobile-de/blog/mapping-bigdecimals-with-morphia-for-mongodb.html

0
Yan Khonski