2010-10-05 13 views
9

J'ai hérité d'un projet dans lequel les montants monétaires utilisent le double type. Pire encore, le framework utilisé et les propres classes du framework utilisent le double pour l'argent.Double mon argent: mon framework utilise des doubles pour les montants monétaires

Le cadre ORM gère également la récupération des valeurs (et le stockage) dans la base de données. Dans la base de données, les valeurs monétaires sont de type number (19, 7), mais l'ORM du framework les mappe en double.

À court de contournement complet des classes de structure et de l'ORM, y a-t-il quelque chose que je puisse faire pour calculer précisément les valeurs monétaires?

Editer: Oui, je sais que BigDecimal devrait être utilisé. Le problème est que je suis étroitement lié à un cadre où, par exemple, la classe framework.commerce.pricing.ItemPriceInfo a deux membres mRawTotalPrice; et double mListPrice. Le code propre à l'application de mon entreprise étend, par exemple, cet ItemPriceInfoClass.

De façon réaliste, je ne peux pas dire à ma compagnie, « ferraille deux années de travail, et des centaines de milliers de dollars dépensés, en se basant sur le code de ce cadre, en raison d'erreurs d'arrondi »

+10

+1 pour le titre "Double mon argent" – Marko

+5

Pour comprendre votre situation. Mais: l'argent comptant et la comptabilité appropriée tend à être important aux sociétés. Alors faites des poursuites pour négligence. Vous êtes dans une situation difficile: d'une part, recommander que l'entreprise brûle des années de développement pour résoudre ce qui semblait être un problème. D'un autre côté, invitez de gros problèmes sur la route. Si j'étais vous, je dirais à ma direction de cette façon, et laissez-les faire l'appel. Vous avez un problème de responsabilité ici, et c'est ce que votre patron est là pour. Documenter toute décision prise. –

+2

À ce que @Michael Petrotta a dit, il est 100% correct. Selon ce que vous faites, ils pourraient être d'énormes problèmes de responsabilité venant à la baisse de cette décision. De plus, ce cadre - est-ce un cadre commercial? Pourriez-vous les convaincre de changer à BigDecimal à la place, ou y a-t-il une version qui le fait?Sinon, vous devrez vous diriger vers la refactorisation incrémentale, ce qui est une situation horrible où vous êtes - je ressens pour vous. – aperkins

Répondre

10

Si tolérable, traiter le type monétaire comme faisant partie intégrante. En d'autres termes, si vous travaillez aux États-Unis, suivez les cents au lieu de dollars, si cents fournit la granularité dont vous avez besoin. Les doublons peuvent représenter avec précision des nombres entiers jusqu'à a very large value (2^53) (aucune erreur d'arrondi jusqu'à cette valeur). Mais en réalité, la bonne chose à faire est de contourner entièrement le cadre et d'utiliser quelque chose de plus raisonnable. C'est une telle erreur d'amateur pour le cadre à faire - qui sait quoi d'autre se cache?

6

Je n'ai pas vu vous mentionnez le refactoring. Je pense que c'est votre meilleure option ici. Au lieu de jeter ensemble des hacks pour que les choses fonctionnent mieux pour le moment, pourquoi ne pas le réparer correctement?

Voici quelques informations sur double vs BigDecimal. Ce message suggère d'utiliser BigDecimal même si c'est plus lent.

+1

[Effective Java] (http://java.sun.com/docs/books/effective/) le suggère aussi (2e édition, article 48). –

+2

Je devrais espérer que cela suggère 'BigDecimal' ... la vitesse ne veut pas dire grand-chose si vos nombres et calculs sont incorrects à cause de' double '. – ColinD

+0

De l'OP, il semble qu'il * ne peut pas * refactoriser car le framework (dont je suppose qu'il n'a pas le code source pour) utilise l'arithmétique FP. – gustafc

1

Eh bien, vous n'avez pas que beaucoup d'options dans la réalité:

Vous pouvez refactoriser le projet d'utiliser par exemple BigDecimal (ou quelque chose qui correspond mieux à ses besoins) pour représenter l'argent. Soyez extrêmement prudent en cas de débordement/sous-débit et de perte de précision, ce qui signifie ajouter des tonnes de vérifications et refactoriser une proportion encore plus grande du système d'une manière inutile. Sans compter combien de recherches seraient nécessaires si vous le faites.

Gardez les choses telles qu'elles sont et espérez que personne ne les remarque (c'est une blague).

À mon humble avis, la meilleure solution serait de simplement refactoriser cela. Cela pourrait être un refactoring lourd, mais le mal est déjà fait et je crois que ce devrait être votre meilleure option.

Best, Vassil

post-scriptum Oh, et vous pouvez traiter l'argent comme des entiers (en comptant les centimes), mais cela ne semble pas être une bonne idée si vous allez avoir des conversions de devises, calculer les intérêts, etc

+0

Perte de précision peut-être, mais "débordement/débordement" êtes-vous sérieux? –

+0

débordements doubles pour les valeurs supérieures à 10^308 et sous-débits pour les valeurs inférieures à 10^-308. Quelles devises ont cet ordre de tri? –

+0

Ok, débordement dans hautement improbable, mais vous ne pouvez pas savoir quelles opérations seraient effectuées sur ces chiffres et sous-débit est une possibilité dans certains scénarios. – vstoyanov

2

Beaucoup de gens vont suggérer d'utiliser BigDecimal et si vous ne savez pas comment utiliser les arrondis dans votre projet, c'est ce que vous devriez faire.

Si vous savez comment utiliser l'arrondi décimal correctement, utilisez le double. Ses nombreux ordres de grandeur plus rapide, beaucoup plus clair et plus simple et donc moins d'erreur à mon humble avis. Si vous utilisez des dollars et des cents (ou avez besoin de deux décimales), vous pouvez obtenir un résultat précis pour des valeurs allant jusqu'à 70 trillions de dollars.

Fondamentalement, vous n'obtiendrez pas d'erreurs rondes si vous corrigez à l'aide d'arrondi approriate.

BTW: La pensée des erreurs d'arrondi frappe la terreur dans le cœur de nombreux développeurs, mais elles ne sont pas des erreurs aléatoires et vous pouvez les gérer assez facilement.

EDIT: considérez cet exemple simple d'une erreur d'arrondi.

double a = 100000000.01; 
    double b = 100000000.09; 
    System.out.println(a+b); // prints 2.0000000010000002E8 

Il existe un certain nombre de stratégies d'arrondi possibles. Vous pouvez arrondir le résultat lors de l'impression/l'affichage. par exemple.

System.out.printf("%.2f%n", a+b); // prints 200000000.10 

ou arrondir le résultat mathématiquement

double c = a + b; 
    double r= (double)((long)(c * 100 + 0.5))/100; 
    System.out.println(r); // prints 2.000000001E8 

Dans mon cas, j'Arrondir le résultat lors de l'envoi du serveur (écrire à une prise et un fichier), mais utiliser ma propre routine pour éviter tout création d'objet

Une fonction ronde plus générale est la suivante, mais si vous pouvez utiliser printf ou DecimalFormat, peut être plus simple.

private static long TENS[] = new long[19]; static { 
    TENS[0] = 1; 
    for (int i = 1; i < TENS.length; i++) TENS[i] = 10 * TENS[i - 1]; 
} 

public static double round(double v, int precision) { 
    assert precision >= 0 && precision < TENS.length; 
    double unscaled = v * TENS[precision]; 
    assert unscaled > Long.MIN_VALUE && unscaled < Long.MAX_VALUE; 
    long unscaledLong = (long) (unscaled + (v < 0 ? -0.5 : 0.5)); 
    return (double) unscaledLong/TENS[precision]; 
} 

note: vous pouvez utiliser BigDecimal pour effectuer l'arrondissement final. esp si vous avez besoin d'une méthode ronde spécifique.

+2

Je n'ai pas fait de downvote, mais sans donner de conseils sur la façon de gérer ces erreurs d'arrondi, la réponse n'est pas utile. – Yishai

+0

Si vous avez affaire à de grosses sommes d'argent, ce n'est pas pratique (pensez à des millions et des milliards de dollars qui sont ajoutés/soustraits, etc.). De plus, en fonction du matériel, les erreurs d'arrondi pourraient être différentes, ce qui donnerait des points de douleur potentiels futurs et pourrait certainement entraîner un comportement imprévisible. Si la précision est nécessaire, vous n'utilisez pas de structures de données estimées à virgule flottante (double/float) et vous vous concentrez plutôt sur la précision. – aperkins

+0

Il est à noter que je n'ai pas baissé le ton non plus. – vstoyanov

0

Je pense que cette situation est au moins récupérable au minimum pour votre code. Vous obtenez la valeur en double via le framework ORM. Vous pouvez ensuite le convertir en BigDecimal en utilisant la méthode statique valueOf (voir here pour la raison) avant d'effectuer des calculs/calculs, puis le convertir en double uniquement pour le stocker. Puisque vous étendez quand même ces classes, vous pouvez ajouter des getters pour votre double valeur qui les obtient comme BigDecimal quand vous en avez besoin.

Cela peut ne pas couvrir 100% des cas (je serais particulièrement inquiet de ce que le pilote ORM ou JDBC est en train de faire pour convertir le double en type numérique), mais c'est tellement mieux que de faire les calculs sur les doubles bruts.

Cependant, je suis loin d'être convaincu que cette approche soit effectivement moins chère pour l'entreprise à long terme.