Déplacement des décimales dans un double

97

J'ai donc un double ensemble égal à 1234, je veux déplacer une décimale pour en faire 12,34

Donc, pour ce faire, je multiplie 0,1 à 1234 deux fois, un peu comme ça

double x = 1234;
for(int i=1;i<=2;i++)
{
  x = x*.1;
}
System.out.println(x);

Cela imprimera le résultat, "12.340000000000002"

Existe-t-il un moyen, sans simplement le mettre en forme à deux décimales, pour que le double stocke correctement 12,34?

BlackCow
la source
30
Voici un lien vers l'article original "Ce que tout informaticien devrait savoir sur l'arithmétique à
virgule
43
Y a-t-il une raison pour laquelle vous ne l'avez pas fait x /= 100;?
Mark Ingram

Réponses:

189

Si vous utilisez doubleou float, vous devez utiliser l'arrondi ou vous attendre à voir des erreurs d'arrondi. Si vous ne pouvez pas faire cela, utilisez BigDecimal.

Le problème que vous avez est que 0,1 n'est pas une représentation exacte, et en effectuant le calcul deux fois, vous ajoutez cette erreur.

Cependant, 100 peuvent être représentés avec précision, alors essayez:

double x = 1234;
x /= 100;
System.out.println(x);

qui imprime:

12.34

Cela fonctionne car Double.toString(d)effectue un petit nombre d'arrondis en votre nom, mais ce n'est pas beaucoup. Si vous vous demandez à quoi cela pourrait ressembler sans arrondir:

System.out.println(new BigDecimal(0.1));
System.out.println(new BigDecimal(x));

imprime:

0.100000000000000005551115123125782702118158340454101562
12.339999999999999857891452847979962825775146484375

En bref, l'arrondi est inévitable pour les réponses sensées en virgule flottante, que vous le fassiez explicitement ou non.


Remarque: x / 100et x * 0.01ne sont pas exactement les mêmes en matière d'erreur d'arrondi. En effet, l'erreur d'arrondi pour la première expression dépend des valeurs de x, tandis que 0.01dans la seconde a une erreur d'arrondi fixe.

for(int i=0;i<200;i++) {
    double d1 = (double) i / 100;
    double d2 = i * 0.01;
    if (d1 != d2)
        System.out.println(d1 + " != "+d2);
}

impressions

0.35 != 0.35000000000000003
0.41 != 0.41000000000000003
0.47 != 0.47000000000000003
0.57 != 0.5700000000000001
0.69 != 0.6900000000000001
0.7 != 0.7000000000000001
0.82 != 0.8200000000000001
0.83 != 0.8300000000000001
0.94 != 0.9400000000000001
0.95 != 0.9500000000000001
1.13 != 1.1300000000000001
1.14 != 1.1400000000000001
1.15 != 1.1500000000000001
1.38 != 1.3800000000000001
1.39 != 1.3900000000000001
1.4 != 1.4000000000000001
1.63 != 1.6300000000000001
1.64 != 1.6400000000000001
1.65 != 1.6500000000000001
1.66 != 1.6600000000000001
1.88 != 1.8800000000000001
1.89 != 1.8900000000000001
1.9 != 1.9000000000000001
1.91 != 1.9100000000000001
Peter Lawrey
la source
26
Je ne peux pas croire que je n'ai pas pensé à faire ça en premier lieu! Merci :-P
BlackCow
6
Bien que 100 puisse être représenté exactement au format binaire, la division par 100 ne peut pas être représentée exactement. Ainsi, l'écriture 1234/100, comme vous l'avez fait, ne fait vraiment rien sur le problème sous-jacent - elle devrait être exactement égale à l'écriture 1234 * 0.01.
Brooks Moses
1
@Peter Lawrey: Pouvez-vous expliquer davantage pourquoi si le nombre est impair ou même aurait une incidence sur l'arrondissement? Je pense que / = 100 et * =. 01 seraient les mêmes car même si 100 est un entier, il sera de toute façon converti en 100,0 en raison de la coercition de type.
eremzeit
1
/100et *0.01sont équivalents les uns aux autres, mais pas aux OP *0.1*0.1.
Amadan
1
Tout ce que je dis, c'est que multiplier par 0,1 deux fois introduira en moyenne une erreur plus grande que multiplier par 0,01 une fois; mais je concéderai volontiers le point de @ JasperBekkers à propos de 100 étant différents, étant exactement représentables en binaire.
Amadan
52

Non - si vous souhaitez stocker les valeurs décimales avec précision, utilisez BigDecimal. ne peutdouble tout simplement pas représenter exactement un nombre comme 0,1, pas plus que vous ne pouvez écrire la valeur d'un tiers exactement avec un nombre fini de chiffres décimaux.

Jon Skeet
la source
46

si c'est juste un formatage, essayez printf

double x = 1234;
for(int i=1;i<=2;i++)
{
  x = x*.1;
}
System.out.printf("%.2f",x);

production

12.34
Augusto
la source
8
Les réponses les mieux notées sont plus perspicaces sur le plan technique, mais c'est la bonne réponse au problème d'OP. Nous ne nous soucions généralement pas de la légère imprécision de double, donc BigDecimal est exagéré, mais lors de l'affichage, nous voulons souvent nous assurer que notre sortie correspond à notre intuition, c'est donc System.out.printf()la bonne voie à suivre.
dimo414
28

Dans les logiciels financiers, il est courant d'utiliser des entiers pour quelques centimes. À l'école, on nous a appris à utiliser la virgule fixe au lieu du flottant, mais il s'agit généralement de puissances de deux. Le stockage de quelques centimes en nombres entiers peut également être appelé "virgule fixe".

int i=1234;
printf("%d.%02d\r\n",i/100,i%100);

En classe, on nous a demandé en général quels nombres peuvent être exactement représentés dans une base.

Pour base=p1^n1*p2^n2... vous pouvez représenter n'importe quel N où N = n * p1 ^ m1 * p2 ^ m2.

Soit base=14=2^1*7^1... vous pouvez représenter 1/7 1/14 1/28 1/49 mais pas 1/3

Je connais les logiciels financiers - j'ai converti les rapports financiers de Ticketmaster de VAX asm à PASCAL. Ils avaient leur propre formatln () avec des codes pour quelques centimes. La raison de la conversion était que les entiers 32 bits ne suffisaient plus. +/- 2 milliards de centimes, c'est 20 millions de dollars et cela a débordé pour la Coupe du monde ou les Jeux olympiques, j'ai oublié.

J'ai juré de garder le secret. Tant pis. Dans l'académie, si c'est bon, vous publiez; dans l'industrie, vous le gardez secret.

Terrence Andrew Davis
la source
12

vous pouvez essayer la représentation par nombre entier

int i =1234;
int q = i /100;
int r = i % 100;

System.out.printf("%d.%02d",q, r);
Angel Koh
la source
5
@Dan: Pourquoi? C'est la bonne approche pour les applications financières (ou toute autre application où même une petite erreur d'arrondi est inacceptable), tout en maintenant la vitesse au niveau du matériel. (Bien sûr, il serait enveloppé dans une classe, normalement, pas écrit à chaque fois)
Amadan
7
Il y a un léger problème avec cette solution - si le reste rest inférieur à 10, aucun remplissage de 0 ne se produit et 1204 produirait un résultat de 12,4. La chaîne de mise en forme correcte est plus similaire à "% d.% 02d"
jakebman
10

Cela est dû à la façon dont les ordinateurs stockent les nombres à virgule flottante. Ils ne le font pas exactement. En tant que programmeur, vous devriez lire ce guide en virgule flottante pour vous familiariser avec les épreuves et les tribulations de la gestion des nombres à virgule flottante.

CanSpice
la source
Argh, j'écrivais juste une explication reliant exactement le même endroit. +1.
Pop
@ Lord Haha, désolé. Je me suis de toute façon Skeeted. :-)
CanSpice
J'ai pensé que c'est pourquoi, mais je me demande s'il existe un moyen créatif de déplacer la décimale? Parce qu'il est possible de stocker 12,34 proprement dans un double, il n'aime tout simplement pas la multiplication par .1
BlackCow
1
S'il était possible de stocker proprement 12.34 dans un double, ne pensez-vous pas que Java l'aurait fait? Ce n'est pas. Vous devrez utiliser un autre type de données (comme BigDecimal). Aussi, pourquoi ne divisez-vous pas simplement par 100 au lieu de le faire en boucle?
CanSpice
Do'h ... ouais, en le divisant par 100, on obtient un net 12,34 ... merci :-P
BlackCow
9

C'est drôle que de nombreux articles mentionnent l'utilisation de BigDecimal mais que personne ne se soucie de donner la bonne réponse basée sur BigDecimal? Parce que même avec BigDecimal, vous pouvez toujours vous tromper, comme le montre ce code

String numstr = "1234";
System.out.println(new BigDecimal(numstr).movePointLeft(2));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal(0.01)));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal("0.01")));

Donne cette sortie

12.34
12.34000000000000025687785232264559454051777720451354980468750
12.34

Le constructeur BigDecimal mentionne spécifiquement qu'il est préférable d'utiliser le constructeur String qu'un constructeur numérique. La précision ultime est également influencée par le MathContext facultatif.

Selon le BigDecimal Javadoc, il est possible de créer un BigDecimal qui est exactement égal à 0,1, à condition d'utiliser le constructeur String.

Justin Rowe
la source
5

Oui il y a. Avec chaque opération double, vous risquez de perdre de la précision, mais le degré de précision diffère pour chaque opération et peut être minimisé en choisissant la bonne séquence d'opérations. Par exemple, lors de la multiplication d'un ensemble de nombres, il est préférable de trier l'ensemble par exposant avant de multiplier.

Tout livre décent sur le calcul des nombres le décrit. Par exemple: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Et pour répondre à votre question:

Utilisez diviser au lieu de multiplier, de cette façon vous obtenez un résultat correct.

double x = 1234;
for(int i=1;i<=2;i++)
{
  x =  x / 10.0;
}
System.out.println(x);
Jan Kotek
la source
3

Non, car les types à virgule flottante Java (en fait tous les types à virgule flottante) sont un compromis entre taille et précision. Bien qu'ils soient très utiles pour de nombreuses tâches, si vous avez besoin d'une précision arbitraire, vous devez utiliser BigDecimal.

biziclop
la source