Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Pourquoi 10^16+1-10^16 = 0 ?

16 réponses
Avatar
ZarkXe
Bonjour à tous,

Je du mal a comprend pourquoi 10^16 + 1 - 10^16 = 0 ?

Merci d'avance.

ZarkXe

---------------%<-------------
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main() {
double total3 = pow(10, 16) + 1 - pow(10, 16);
printf("pow(16) + 1 - pow(16) = %lf\n", total3);

return EXIT_SUCCESS;
}
---------------%<-------------

10 réponses

1 2
Avatar
Éric Lévénez
Le 14/10/12 20:51, ZarkXe a écrit :
Bonjour à tous,

Je du mal a comprend pourquoi 10^16 + 1 - 10^16 = 0 ?

Merci d'avance.

ZarkXe

---------------%<-------------
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main() {
double total3 = pow(10, 16) + 1 - pow(10, 16);
printf("pow(16) + 1 - pow(16) = %lfn", total3);

return EXIT_SUCCESS;
}
---------------%<-------------



Le double contient un flottant avec mantisse et exposant. La taille de
la mantisse ne peut contenir la valeur exacte de 10^16 + 1 et donc le +1
est en quelque sorte ignoré.

Pour avoir plus de chiffres significatifs, il faut passer à un truc du
genre :

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main(void)
{
long double total3 = powl(10, 16) + 1 - powl(10, 16);
printf("pow(16) + 1 - pow(16) = %Lfn", total3);

return EXIT_SUCCESS;
}



--
Éric Lévénez
FAQ de fclc : <http://www.levenez.com/lang/c/faq/>
Avatar
Samuel DEVULDER
Le 14/10/2012 20:51, ZarkXe a écrit :
Bonjour à tous,

Je du mal a comprend pourquoi 10^16 + 1 - 10^16 = 0 ?



Parce que le double n'a pas assez de précision pour représenter
exactement 10^16+1. En effet la mantisse ayant 52bits, on peut
représenter exactement les 53 premiers bits des valeurs numériques (1 de
plus à cause du bit de poids fort implicite). Or 2^53ž15, un peu plus
petit que 10^16. Donc en mémoire, au format IEEE753, (10^16+1) et
(10^16) sont indiscernables. Leur différence fait donc 0.

Pour éviter ce défaut, il vaut mieux réorganiser les équations pour
additionner/soustraire les termes par ordre de grandeur décroissante.
Ainsi si tu avais fait 10^16 - 10^16 + 1 tu aurais obtenu 1, car (10^16
- 10^16) aurait été évalué en 1er donnant 0, lequel additionné à 1 donne
bien 1.

sam.
Avatar
Lucas Levrel
Le 14 octobre 2012, Samuel DEVULDER a écrit :

Pour éviter ce défaut, il vaut mieux réorganiser les équations pour
additionner/soustraire les termes par ordre de grandeur décroissante.



À voir selon le cas. Dans un fil récent, il s'agissait de calculer la
somme des 1/k avec k entier. Si on commence par les plus gros (k
croissant), au bout d'un moment les additions ne font plus rien (les
termes deviennent individuellement négligeables).

Ce qu'il faut faire, me semble-t-il, c'est regrouper les termes de
grandeur comparable. Et se méfier des soustractions !

--
LL
Avatar
Alexandre Bacquart
On 10/14/2012 09:48 PM, Samuel DEVULDER wrote:
Le 14/10/2012 20:51, ZarkXe a écrit :
Bonjour à tous,

Je du mal a comprend pourquoi 10^16 + 1 - 10^16 = 0 ?



Parce que le double n'a pas assez de précision pour représenter
exactement 10^16+1.



Strictement parlant, il n'en a d'ailleurs même pas assez pour
représenter exactement 2 ;)

Les propriétés des flottants font qu'ajouter 1 à une valeur aussi grande
que 10^16 n'aura vraisemblablement aucun effet (ni float ni double ne
sont assez précis).

Le problème peut être résolu de plusieurs manières :

1. Méthode brute : http://gmplib.org/

2. Méthode old-school : utiliser des entiers. Si besoin de fractions,
faire de la virgule fixe (fixed point). Imparable, mais pour des valeurs
de l'ordre de 10^16, 64 bits est un minimum.

3. Méthode hacker sûr de lui : isoler les calculs flottants sur des
petites valeurs et ceux sur des grandes valeurs. Après, le tout est de
définir petites et grandes valeurs. Et il vaut mieux avoir une très
bonne notion de ce qu'on fait.


--
Alexandre
Avatar
Marc Boyer
Le 15-10-2012, Lucas Levrel a écrit :
Le 14 octobre 2012, Samuel DEVULDER a écrit :
Pour éviter ce défaut, il vaut mieux réorganiser les équations pour
additionner/soustraire les termes par ordre de grandeur décroissante.



À voir selon le cas. Dans un fil récent, il s'agissait de calculer la
somme des 1/k avec k entier. Si on commence par les plus gros (k
croissant), au bout d'un moment les additions ne font plus rien (les
termes deviennent individuellement négligeables).

Ce qu'il faut faire, me semble-t-il, c'est regrouper les termes de
grandeur comparable. Et se méfier des soustractions !



Faut surtout garder en tête que l'analyse numérique est un job
à part, et que tant qu'il s'agit de calculer des trucs pour jouer,
on peut s'amuser, mais dès que ça devient sérieux, il faut faire
très attention.

On rapporte toujours l'exemple d'un logiciel de compta
dont les sommes HT + TVA != TTC.

Marc Boyer
--
À mesure que les inégalités regressent, les attentes se renforcent.
François Dubet
Avatar
Antoine Leca
Marc Boyer écrivit :
Faut surtout garder en tête que l'analyse numérique est un job
à part, [...] ça devient sérieux, il faut faire très attention.

On rapporte toujours l'exemple d'un logiciel de compta
dont les sommes HT + TVA != TTC.



Et le rapport avec soit le langage C, soit l'analyse numérique est ?


Antoine
Avatar
Antoine Leca
Alexandre Bacquart écrivit :
Les propriétés des flottants font qu'ajouter 1 à une valeur aussi grande
que 10^16 n'aura vraisemblablement aucun effet (ni float ni double ne
sont assez précis).



Bzzz ! Je soupçonne une dépendance cachée avec ta machine...


Le problème peut être résolu de plusieurs manières :

1. Méthode brute : http://gmplib.org/

2. Méthode old-school : utiliser des entiers. Si besoin de fractions,
faire de la virgule fixe (fixed point). Imparable, mais pour des valeurs
de l'ordre de 10^16, 64 bits est un minimum.

3. Méthode hacker sûr de lui : isoler les calculs flottants sur des
petites valeurs et ceux sur des grandes valeurs. Après, le tout est de
définir petites et grandes valeurs. Et il vaut mieux avoir une très
bonne notion de ce qu'on fait.



4. méthode gros bourrin (qui vaut exactement autant que la remarque
ci-dessus) : regardez un poil la documentation du compilateur, remarquer
que LDBL_DIG y vaut 19 ou 30 ce qui garantit que les opérations
utilisent plus de 16 chiffres décimaux significatifs, et utiliser des
long doubles...

5. méthode le-gars-des-années-60 : utiliser des flottants "double float"
précision doublée fait-maison c'est-à-dire struct {double d[2];}
Évidemment toutes les opérations doivent être programmées une par une ;
par rapport à GMP dont cette méthode est l'ancêtre, c'est un poil plus
rapide en général mais est limité à (environ) 30 chiffres décimaux.


Antoine
Avatar
Samuel DEVULDER
Le 15/10/2012 14:23, Alexandre Bacquart a écrit :
On 10/14/2012 09:48 PM, Samuel DEVULDER wrote:
Le 14/10/2012 20:51, ZarkXe a écrit :
Bonjour à tous,

Je du mal a comprend pourquoi 10^16 + 1 - 10^16 = 0 ?



Parce que le double n'a pas assez de précision pour représenter
exactement 10^16+1.



Strictement parlant, il n'en a d'ailleurs même pas assez pour
représenter exactement 2 ;)



Je ne comprends pas. Typiquement 2 sera représenté par les bits suivants
sur un float 32bit ieee 754:

0 10000000 00000000000000000000000
s <-expo-> <------mantisse------->
1 <7 bits> <----- 22 bits ------->

value = (1-2*s)*(1+mantisse/2^23)*2^(expo-127)

Dans l'exemple s=0, mantisse=0, expo8, donc value=2 exactement.

Plus généralement parlant tous les entiers de 0 à 2^23-1 peuvent être
représentés et ajoutés ou soustraits sans erreur sur un float32 (la
mantisse contient assez de bits pour cela), et jusqu'à 2^53-1 sur un
float64. (je ne compte pas les nombres dénormalisés).

Un bon exemple pour jouer avec le format IEEE est de coder une
implémentation d'une représentation minifloat 8bits 1.4.3
(http://en.wikipedia.org/wiki/Minifloat). C'est très marrant, il n'y a
que 242 valeurs possibles mais elles s'étalent de -122880 à 122880, soit
une amplitude plus grande que 16bits. On peut y représenter exactement
tous les entiers de -16 à +16. C'est pas mal avec si peu de bits!

Les propriétés des flottants font qu'ajouter 1 à une valeur aussi grande
que 10^16 n'aura vraisemblablement aucun effet (ni float ni double ne
sont assez précis).



Oui, en plus je pense le résultat dépend du mode d'arrondi de l'unité
flottante ou si elle travaille sur un format étendu en interne ou pas.

sam.
Avatar
Marc Boyer
Le 15-10-2012, Antoine Leca a écrit :
Marc Boyer écrivit :
Faut surtout garder en tête que l'analyse numérique est un job
à part, [...] ça devient sérieux, il faut faire très attention.

On rapporte toujours l'exemple d'un logiciel de compta
dont les sommes HT + TVA != TTC.



Et le rapport avec soit le langage C, soit l'analyse numérique est ?



Ben, que (x) et (x * (1-e)) + (x*e) ne sont pas garantis comme égaux
en flottant, même pour des valeurs asez "assez peu" de chiffres
significatifs (une TVA, c'est 4 chiffres significatifs et une facture
chez le commun des mortel dépasse rarement les 10 chiffres).

Marc Boyer
--
À mesure que les inégalités regressent, les attentes se renforcent.
François Dubet
Avatar
Antoine Leca
Marc Boyer écrivit :
Le 15-10-2012, Antoine Leca a écrit :
Marc Boyer écrivit :
Faut surtout garder en tête que l'analyse numérique est un job
à part, [...] ça devient sérieux, il faut faire très attention.

On rapporte toujours l'exemple d'un logiciel de compta
dont les sommes HT + TVA != TTC.



Et le rapport avec soit le langage C, soit l'analyse numérique est ?



Ben, que (x) et (x * (1-e)) + (x*e) ne sont pas garantis comme égaux
en flottant, même pour des valeurs asez "assez peu" de chiffres
significatifs (une TVA, c'est 4 chiffres significatifs et une facture
chez le commun des mortel dépasse rarement les 10 chiffres).



On est hors sujet mais je continue un peu : il me paraît évident (depuis
de nombreuses années, donc avant de programmer en C) que les
applications de comptabilité ne doivent pas manipuler les montants en
utilisant directement les types flottants, parce que les chiffres
décimaux après la virgule n'ont pas de représentation unique.

Comme en C il n'y a/avait pas de virgule fixe, cela ne laissait que les
type entiers ; autrement dit en C89 au maximum 7+2 chiffres
significatifs ce qui était insuffisant. D'où l'idée proposée par
certains d'utiliser le type double qui garantit 15 chiffres, suffisant
en pratique en comptabilité *si on prend garde à calculer en centimes*
(ou en millimes voire décimillimes). Les doubles ne sont alors utilisée
que comme des entiers avec une étendue de 53 bits (cf. le message de
Samuel); évidemment depuis C99 cela n'est plus nécessaire (à supposer
que beaucoup de fous ont programmé de la compta en C).

À propos du nombre de chiffres, un total de bilan en pesètes ou en lires
atteint sans difficulté 11 chiffres avant la virgule... C'est vrai
qu'avec l'euro on est revenu avec des sommes plus facile à manipuler,
mais il existe toujours des pays où on peut avoir besoin de plus de
chiffres (un milliard de yens ou de francs CFA/CFP n'est pas une facture
irréaliste).

À propos de calcul de TVA, pour moi la TVA est toujours en centimes
(même si la manière d'arrondir peut être l'objet de discussions
«intéressantes» avec le fisc...) Par contre, la formule HT + TVA != TTC
peut apparaître quand tu dois recalculer le montant HT à partir du TTC:
il est des fois où cela ne tombe pas juste !


Antoine
1 2