web-dev-qa-db-fra.com

Bigdecimal.divide et arrondi de Java

Au travail, nous avons trouvé un problème en essayant de diviser un grand nombre par 1000. Ce nombre provenait de la base de données.

Disons que j'ai cette méthode:

private static BigDecimal divideBy1000(BigDecimal dividendo) {
    if (dividendo == null) return null;

    return dividendo.divide(BigDecimal.valueOf(1000), RoundingMode.HALF_UP);
}

Quand je fais l'appel suivant

divideBy1000(new BigDecimal("176100000"))

Je reçois la valeur attendue de 176100. Mais si j'essaie la ligne ci-dessous

divideBy1000(new BigDecimal("1761e+5"))

Je reçois la valeur 200000. Pourquoi cela se produit-il? Les deux numéros sont identiques avec une représentation différente et le dernier est ce que je reçois de la base de données. Je comprends que, d'une manière ou d'une autre, la machine virtuelle Java divise le nombre 1761 par 1000, arrondissant et remplissant par des 0 à la fin.

Quelle est la meilleure façon d'éviter ce genre de comportement? Gardez à l'esprit que le numéro d'origine n'est pas contrôlé par moi.

26
Raphael do Vale

Comme spécifié dans javadoc, un BigDecimal est défini par une valeur entière et une échelle .

La valeur du nombre représenté par le BigDecimal est donc (unscaledValue × 10 ^ (- scale)).

Ainsi, BigDecimal("1761e+5") a une échelle de -5 et BigDecimal(176100000) a une échelle de 0.

La division des deux BigDecimal se fait en utilisant les échelles -5 et 0 respectivement parce que les échelles ne sont pas spécifiées lors de la division. La documentation divide explique pourquoi les résultats sont différents.

divide

public BigDecimal divide(BigDecimal divisor)

Renvoie un BigDecimal dont la valeur est (this / divisor), Et dont l'échelle préférée est (this.scale() - divisor.scale()); si le quotient exact ne peut pas être représenté (car il a une expansion décimale sans terminaison) un ArithmeticException est levé.

Paramètres:

divisor - valeur par laquelle ce BigDecimal doit être divisé.

Renvoie:

this / divisor

Lance:

ArithmeticException - si le quotient exact n'a pas d'expansion décimale terminale

Depuis:

1,5

Si vous spécifiez une échelle lors de la division, par ex. dividendo.divide(BigDecimal.valueOf(1000), 0, RoundingMode.HALF_UP) vous obtiendrez le même résultat.

21
dcernahoschi

Les expressions new BigDecimal("176100000") et new BigDecimal("1761e+5") sont différentes. BigDecimal garde une trace de la valeur et de la précision.

BigDecimal("176100000") a 9 chiffres de précision et est représenté en interne comme BigInteger("176100000"), multiplié par 1. BigDecimal("1761e+5") a 4 chiffres de précision et est représenté en interne comme BigInteger("1761"), multiplié par 100000.

Lorsque vous divisez un BigDecimal par une valeur, le résultat respecte les chiffres de précision, entraînant des sorties différentes pour des valeurs apparemment égales.

6
Joshua

pour votre division avec BigDecimal.

dividendo.divide(divisor,2,RoundingMode.CEILING)//00.00 nothing for up and nothing for down

dans cette opération ont une précision de deux décimales.

2
leandro lion

Oui, c'est un peu ce que vous expérimentez. Si vous me le permettez, dans une situation où vous n'avez que des nombres exponentaux, vous devez les lancer puis utiliser votre méthode. Voyez ce que je suggère est ce morceau de code là-bas:

long  longValue = Double.valueOf("1761e+5").longValue();
BigDecimal value= new BigDecimal(longValue);

Utilisez-le dans une méthode qui convertirait ces chaînes en un nouveau BigDecimal et renverrait cette valeur BigDecimal. Ensuite, vous pouvez utiliser ces valeurs renvoyées avec divideBy1000, ce qui devrait éliminer tout problème que vous rencontrez.

Si vous en avez beaucoup, ce que vous pouvez faire également en stockant ces BigDecimal dans une structure de données comme une liste. Ensuite, utilisez une boucle foreach dans laquelle vous appliquez divideBy1000 et chaque nouvelle valeur serait stockée dans une liste différente. Il vous suffirait alors d'accéder à cette liste pour avoir votre nouvel ensemble de valeurs!

J'espère que cela aide :)

0
Kevin Avignon

Chaque fois que vous multipliez un BigDecimal par une puissance de 10, dans ce cas, vous multipliez par 10-3, vous pouvez utiliser dividendo.scaleByPowerOfTen(power) qui modifie uniquement l'échelle de l'objet BigDecimal et contourne les problèmes d'arrondi, ou au moins les déplace vers un calcul ultérieur.

Les autres réponses ici couvrent le cas plus général de la division par n'importe quel nombre.

0
meticoeus

Essayez d'utiliser round().

private static BigDecimal divideBy1000(BigDecimal dividendo) {
    if (dividendo == null) return null;

    return dividendo.divide(BigDecimal.valueOf(1000)).round(new MathContext(4, RoundingMode.HALF_UP));
}

 public static void main(String []args){
    BigDecimal bigD = new BigDecimal("1761e5");
    BigDecimal bigDr = divideBy1000(bigD);
    System.out.println(bigDr);
 }

La ligne new MathContext(4, RoundingMode.HALF_UP)) renvoie la division à 4 endroits.

Cela produit:

1.761E+5

C'est ce que tu veux. (:

0
Tetramputechture