La précision en virgule flottante est-elle mutable ou invariante?

je continue d'obtenir des réponses mitigées à savoir si les nombres à virgule flottante (i.e. float , double , ou long double ) ont une et une seule valeur de précision, ou ont une valeur de précision qui peut varier.

un sujet appelé float vs. double precision semble impliquer que la précision en virgule flottante est absolue.

cependant, un autre sujet appelé différence entre flotteur et double dit,

en général un double a 15 à 16 décimal digits of precision

l'Autre source dit,

Variables de type flotteur ont généralement une précision de environ 7 chiffres significatifs

"

Variables de type double ont généralement une précision de environ 16 chiffres significatifs

Je n'aime pas me référer à des approximations comme celles ci-dessus si je travaille avec du code sensible qui peut se casser facilement lorsque mes valeurs ne sont pas exactes. Donc, mettons les pendules à l'heure. La précision en virgule flottante est-elle mutable ou invariante, et pourquoi?

36
demandé sur Community 2015-05-29 22:11:03

10 réponses

la précision est fixe, qui est exactement 53 chiffres binaires pour la double précision (ou 52 si nous excluons l'avance implicite 1). Cela revient à environ 15 chiffres décimaux .


L'OP m'a demandé d'expliquer pourquoi avoir exactement 53 chiffres binaires signifie "environ" 15 chiffres décimaux.

pour comprendre cela intuitivement, considérons un flottant moins précis format: au lieu d'un mantissa 52-bit comme les nombres de double précision ont, nous allons juste utiliser un mantissa 4-bit.

ainsi, chaque nombre ressemblera à: (-1) s × 2 yyy × 1.xxxx (où s est le signe bit, yyy est l'exposant, et 1.xxxx est le mantissa normalisé). Pour la discussion immédiate, nous nous concentrerons uniquement sur le mantissa et non sur le signe ou l'exposant.

voici un tableau de ce que 1.xxxx ressemble pour toutes les valeurs xxxx (tous les arrondissements sont à moitié égaux, tout comme la façon dont le mode d'arrondissement flottant par défaut fonctionne):

  xxxx  |  1.xxxx  |  value   |  2dd  |  3dd  
--------+----------+----------+-------+--------
  0000  |  1.0000  |  1.0     |  1.0  |  1.00
  0001  |  1.0001  |  1.0625  |  1.1  |  1.06
  0010  |  1.0010  |  1.125   |  1.1  |  1.12
  0011  |  1.0011  |  1.1875  |  1.2  |  1.19
  0100  |  1.0100  |  1.25    |  1.2  |  1.25
  0101  |  1.0101  |  1.3125  |  1.3  |  1.31
  0110  |  1.0110  |  1.375   |  1.4  |  1.38
  0111  |  1.0111  |  1.4375  |  1.4  |  1.44
  1000  |  1.1000  |  1.5     |  1.5  |  1.50
  1001  |  1.1001  |  1.5625  |  1.6  |  1.56
  1010  |  1.1010  |  1.625   |  1.6  |  1.62
  1011  |  1.1011  |  1.6875  |  1.7  |  1.69
  1100  |  1.1100  |  1.75    |  1.8  |  1.75
  1101  |  1.1101  |  1.8125  |  1.8  |  1.81
  1110  |  1.1110  |  1.875   |  1.9  |  1.88
  1111  |  1.1111  |  1.9375  |  1.9  |  1.94

combien de décimales cela donne-t-il? Vous pouvez dire 2, en ce que chaque valeur dans la gamme de deux décimales est couverte, bien que pas seulement; ou vous pouvez dire 3, qui couvre toutes les valeurs uniques, mais ne fournissent pas la couverture pour toutes les valeurs dans la gamme de trois décimales.

pour le bien de l'argument, nous dirons qu'il a 2 chiffres décimaux: la précision décimale sera le nombre de chiffres où toutes les valeurs de ces chiffres décimaux pourraient être représentées.


alors, que se passe-t-il si nous divisons par deux tous les nombres (donc nous utilisons yyy = -1)?

  xxxx  |  1.xxxx  |  value    |  1dd  |  2dd  
--------+----------+-----------+-------+--------
  0000  |  1.0000  |  0.5      |  0.5  |  0.50
  0001  |  1.0001  |  0.53125  |  0.5  |  0.53
  0010  |  1.0010  |  0.5625   |  0.6  |  0.56
  0011  |  1.0011  |  0.59375  |  0.6  |  0.59
  0100  |  1.0100  |  0.625    |  0.6  |  0.62
  0101  |  1.0101  |  0.65625  |  0.7  |  0.66
  0110  |  1.0110  |  0.6875   |  0.7  |  0.69
  0111  |  1.0111  |  0.71875  |  0.7  |  0.72
  1000  |  1.1000  |  0.75     |  0.8  |  0.75
  1001  |  1.1001  |  0.78125  |  0.8  |  0.78
  1010  |  1.1010  |  0.8125   |  0.8  |  0.81
  1011  |  1.1011  |  0.84375  |  0.8  |  0.84
  1100  |  1.1100  |  0.875    |  0.9  |  0.88
  1101  |  1.1101  |  0.90625  |  0.9  |  0.91
  1110  |  1.1110  |  0.9375   |  0.9  |  0.94
  1111  |  1.1111  |  0.96875  |  1.   |  0.97

selon les mêmes critères qu'avant, il s'agit maintenant d'un chiffre décimal. Donc vous pouvez voir comment, en fonction de la exposant, vous pouvez avoir plus ou moins des chiffres décimaux, parce que binaire et décimale des nombres à virgule flottante ne se mappent pas correctement l'un à l'autre .

le même argument s'applique aux nombres à virgule flottante de double précision (avec le mantissa 52 bits), seulement dans ce cas vous obtenez soit 15 ou 16 chiffres décimaux selon l'exposant.

28
répondu Chris Jester-Young 2015-05-31 22:14:01

Tous les ordinateurs modernes utilisent l'arithmétique flottante binaire. Cela signifie que nous avons un mantissa binaire, qui a typiquement 24 bits pour une précision simple, 53 bits pour une double précision et 64 bits pour une précision étendue. (La précision étendue est disponible sur les processeurs x86, mais pas sur les processeurs ARM ou éventuellement d'autres types de processeurs.)

24, 53, et 64 bit mantissas signifie que pour un nombre flottant entre 2 k et 2 k+1 le prochain plus grand nombre est 2 k-23 , 2 k-52 et 2 k-63 respectivement. C'est la résolution. L'erreur d'arrondi de chaque opération en virgule flottante est à plus de la moitié de cela.

alors comment cela se traduit-il en nombres décimaux? cela dépend .

prendre k = 0 et 1 ≤ x < 2. La résolution est 2 -23 , 2 -52 , et 2 -63 qui est sur le 1.19×10 -7 , 2.2×10 -16 , et 1.08×10 -19 respectivement. C'est un peu moins de 7, 16, et 19 décimales. Puis prendre k = 3 et

8 ≤ x < 16. La différence entre deux nombres à virgule flottante est maintenant 8 fois plus grande. Pour 8 ≤ x < 10 vous obtenez un peu plus de 6, moins de 15, et un peu plus de 18 décimales respectivement. Mais pour 10 ≤ x < 16 vous obtenez une décimale de plus!

vous obtenez le nombre le plus élevé de chiffres décimaux si x est seulement un peu moins de 2 k+1 et seulement un peu plus de 10 n , par exemple 1000 ≤ x < 1024. Vous obtenez le plus petit nombre de chiffres décimaux si x est juste un peu plus élevé que 2 k et un peu moins de 10 n , par exemple 1 1024 ≤ x < 1 1000 . La même précision binaire peut produire une précision décimale qui varie de jusqu'à 1,3 chiffres ou log 10 (2×10).

bien sûr, vous pourriez juste lire l'article " ce que chaque informaticien devrait savoir au sujet de l'arithmétique flottante ."

24
répondu gnasher729 2015-05-31 19:30:39
Le code

80x86 utilisant son coprocesseur matériel (à l'origine le 8087) fournit trois niveaux de précision: 32-bit, 64-bit, et 80-bit. Ceux-ci suivent de très près la norme IEEE-754 de 1985. La norme récente spécifie un format de 128 bits . Les formats à virgule flottante ont 24, 53, 65, et 113 bits de mantissa qui correspondent à 7.22, 15.95, 19.57, et 34.02 décimaux de précision.

le la formule est mantissa_bits / log_2 10 où la base log deux de dix est 3.321928095.

bien que la précision d'une implémentation particulière varie et non , elle peut apparaître lorsqu'une valeur à virgule flottante est convertie en décimale. Notez que la valeur 0.1 n'a pas de représentation binaire exacte. Il s'agit d'un motif bit répétitif (0.000110011001100110011001100110011001100...) comme nous sommes habitués à en décimal pour 0.3333333333333 à approximative 1/3.

de nombreuses langues ne prennent souvent pas en charge le format 80 bits. Certains compilateurs C peuvent proposer long double qui utilise soit des flotteurs de 80 bits, soit des flotteurs de 128 bits. Hélas, il pourrait aussi utiliser un float 64 bits, en fonction de l'implémentation.

le NPU possède des registres à 80 bits et effectue toutes les opérations en utilisant le résultat complet à 80 bits. Le Code qui calcule dans la pile NPU bénéficie de cette précision supplémentaire. Malheureusement, mauvaise génération de code-ou code mal écrit - peut tronquer ou arrondir les calculs intermédiaires en les stockant dans une variable 32 bits ou 64 bits.

9
répondu wallyk 2015-05-29 23:32:44

est-ce que la précision en virgule flottante est mutable ou invariante, et pourquoi?

typiquement, étant donné n'importe quels nombres dans la même gamme de puissance-de-2, la précision de point flottant est invariante - une valeur fixe. La précision absolue change à chaque puissance de 2 étape. Sur toute la gamme des FP, la précision est approximativement relative à la magnitude. Si l'on compare cette précision binaire relative en termes de précision décimale, on obtient a wobble variant entre DBL_DIG et DBL_DECIMAL_DIG décimaux - typiquement 15 à 17.


Qu'est-ce que la précision? Avec la FP, il est plus logique de discuter de la précision relative .

les nombres à virgule flottante ont la forme de:

Signe * Significande * pow(base,exposant)

ils ont un logarithme distribution. Il y a autour de autant de nombres différents de virgule flottante entre 100.0 et 3000.0 ( une gamme de 30x) qu'il y a entre 2.0 et 60.0. Cela est vrai indépendamment de la sous-représentation de stockage.

1.23456789e100 a à peu près la même précision relative que 1.23456789e-100 .


la Plupart des ordinateurs implemment double comme binary64 . Ce format a 53 bits de binaire précision.

les nombres n entre 1.0 et 2.0 ont la même précision absolue de 1 partie dans ((2.0-1.0)/pow(2,52).

Les nombres entre 64.0 et 128.0, aussi n , ont la même précision absolue de 1 partie dans ((128.0-64.0)/pow(2,52).

même groupe de nombres entre les puissances de 2, ont la même précision absolue.

sur toute la plage normale des nombres FP, cela se rapproche d'une précision relative uniforme.

lorsque ces nombres sont représentés sous forme décimale, la précision wobbles : les nombres 1.0 à 2.0 ont 1 bit de plus de précision absolue que les nombres 2.0 à 4.0. 2 bits de plus que 4.0 à 8.0, etc.

C fournit DBL_DIG , DBL_DECIMAL_DIG , et leurs float et long double homologues. DBL_DIG indique le minimum relatif précision décimale. DBL_DECIMAL_DIG peut être considéré comme le maximum relative précision décimale.

en général, cela signifie que double aura une précision de 15 à 17 décimales.

prendre en considération 1.0 et son prochain représentable double , les chiffres ne changent pas jusqu'à la 17e significative des chiffres décimaux. De chaque côté double est pow(2,-52) ou sur 2.2204e-16 en dehors.

/*
1 234567890123456789 */
1.000000000000000000...
1.000000000000000222...

maintenant considérer "8.521812787393891" et son prochain nombre représentatif comme une chaîne décimale en utilisant 16 chiffres décimaux significatifs. Ces deux cordes, converties en double sont les même 8.521812787393891142073699... même si elles diffèrent dans le 16ème chiffre. Dire que ce double avait 16 chiffres de précision était exagéré.

/*
1 234567890123456789 */
8.521812787393891
8.521812787393891142073699...
8.521812787393892
8
répondu chux 2015-06-05 15:27:35

Non, c'est variable. Le point de départ est la très faible norme IEEE-754, Il a seulement précisé le format des nombres de pointeur flottants comme ils sont stockés en mémoire. Vous pouvez compter sur 7 chiffres de précision pour une seule précision, 15 chiffres pour une double précision.

mais un défaut majeur de cette norme est qu'elle ne spécifie pas comment les calculs doivent être effectués. Et il y a des problèmes, le processeur à virgule flottante Intel 8087 en particulier a causé des programmeurs de nombreuses nuits blanches. Un défaut de conception important dans cette puce est qu'il stocke des valeurs de virgule flottante avec bits plus que le format de mémoire. 80 bits au lieu de 32 ou 64. La théorie derrière ce choix de conception est que cela permet de calculs intermédiaires pour être plus précis et causer moins d'erreur d'arrondi.

semble être une bonne idée, qui cependant n'a pas donné de bons résultats dans la pratique. Un compilateur essaiera de générer du code qui valeurs intermédiaires stockées dans L'UFP aussi longtemps que possible. Important pour la vitesse de code, stocker la valeur de retour à la mémoire est coûteux. Le problème est, il souvent doit stocker des valeurs en arrière, le nombre de registres dans le FPU sont limités et le code pourrait traverser une frontière de fonction. À ce moment, la valeur est tronquée et perd beaucoup de précision. De petits changements au code source peuvent maintenant produire des valeurs radicalement différentes. Aussi, la construction non optimisée d'un programme produit des résultats différents de la optimisé. D'une manière totalement non diagnostiquable, il faut regarder le code de la machine pour savoir pourquoi le résultat est différent.

Intel a redessiné leur processeur pour résoudre ce problème, l'ensemble D'instruction SSE calcule avec le même nombre de bits que le format de mémoire. Cependant, la refonte du générateur de code et de l'optimiseur d'un compilateur représente un investissement important. Les trois principaux compilateurs C++ ont tous changé. Mais par exemple, le jitter x86 dans le Framework .NET génère encore du code FPU, il le générera toujours.


il y a ensuite une erreur systémique, perdant la précision comme effet secondaire inévitable de la conversion et du calcul. Conversion tout d'abord, les humains travaillent en nombre en base 10, mais le processeur utilise la base 2. Les bons nombres ronds que nous utilisons, comme 0.1 ne peuvent pas être convertis en bons nombres ronds sur le processeur. 0.1 est parfait comme une somme de puissances de 10, mais il n'est pas fini somme de puissances de 2 qui produisent la même valeur. La conversion produit un nombre infini de 1 et de 0 de la même manière que vous ne pouvez pas parfaitement écrire 10 / 3. Il doit donc être tronqué le processeur et qui produit une valeur de +/- 0.5 bit de la valeur décimale.

et le calcul produit l'erreur. Une multiplication ou division double le nombre de bits dans le résultat, en l'arrondissant pour l'ajuster de nouveau dans la valeur stockée produit + / - 0.5 bit erreur. La soustraction est l'opération la plus dangereuse et peut causer la perte d'un lot de chiffres significatifs. Si vous, disons, calculez 1.234567 f-1.234566 f alors le résultat a seulement 1 chiffre significatif à gauche. C'est un résultat indésirable. La somme de la différence entre les nombres qui ont presque la même valeur, est très courante dans les algorithmes numériques.

obtenir des erreurs systémiques excessives est en fin de compte une faille dans le modèle mathématique. Juste comme exemple, vous ne jamais vouloir utiliser L'élimination gaussienne, il est très hostile à la précision. Et toujours envisager une approche alternative, la décomposition LU est une excellente approche. Cependant, il est peu courant qu'un mathématicien a été impliqué dans la construction du modèle et représentaient la précision attendue du résultat. Un livre courant comme Numerical Recipes n'accorde pas assez d'attention à la précision, bien qu'il vous éloigne indirectement des mauvais modèles en proposant le meilleur. En fin de compte, un programmeur est souvent coincé avec le problème. Eh bien, c'était facile alors n'importe qui pouvait le faire et je serais hors d'un bon travail rémunéré:)

6
répondu Hans Passant 2015-06-08 07:41:58

le type d'une variable à virgule flottante définit quelle plage de valeurs et combien de bits (!) peut être représenté. Comme il n'y a pas de relation entière entre la fraction décimale et la fraction binaire, la fraction décimale est en fait une approximation.

Deuxième: un Autre problème est la précision des opérations arithmétiques sont effectuées. Pensez à 1.0/3.0 ou PI. De telles valeurs ne peuvent pas être représentées avec un nombre limité de chiffres - ni décimal, ni binaire. De sorte que le les valeurs seront arrondies pour tenir dans un espace donné. Le plus de chiffres fractionnaires sont disponibles, plus la précision.

pensez maintenant à de multiples opérations de ce type étant appliquées, par exemple PI/3.0 . Il faudrait pour cela arrondir deux fois: PI en tant que tel n'est pas exact et le résultat non plus. Cela perdra la précision deux fois, si repreated il devient pire.

donc, retour à float et double : float A selon la norme (C11, Annexe F, aussi pour le reste) moins de bits disponibles, donc roundig sera moins précis que pour double . Il suffit de penser à avoir une décimale avec 2 chiffres fractionnaires (M. ff, call it float) et un avec quatre (M. ffff, call it double). Si double est utilisé pour tous les calculs, vous pouvez avoir plus d'opérations jusqu'à ce que votre résultat a seulement 2 chiffres fractionnels corrects, que si vous commencez déjà avec float, même si un résultat de float suffirait.

notez que sur certains CPU (embarqués) comme le bras Cortex-M4F, le FPU matériel ne prend en charge folat (simple précision), de sorte que le double arithmétique sera beaucoup plus coûteux. D'autres MCU n'ont pas de calculateur de point flottant Matériel du tout, donc ils doivent être simulés mon logiciel (très coûteux). Sur la plupart des GPU, float est également beaucoup moins cher à effectuer que double, parfois de plus d'un facteur de 10.

5
répondu too honest for this site 2015-05-30 17:14:45

Le stockage a un nombre de chiffres binaires, comme d'autres réponses expliquer.

une chose à savoir, le CPU peut exécuter des opérations à une précision différente en interne, comme 80 bits. Cela signifie que ce code peut déclencher:

void Kaboom( float a, float b, float c ) // same is true for other floating point types.
{
    float sum1 = a+b+c;
    float sum2 = a+b;
    sum2 += c; // let's assume that the compiler did not keep sum2 in a register and the value was write to memory then load again.
    if (sum1 !=sum2)
        throw "kaboom"; // this can happen.
}

il est plus probable avec un calcul plus complexe.

5
répondu galop1n 2015-06-01 01:36:53

je vais ajouter la réponse off-beat ici, et dire que puisque vous avez étiqueté cette question comme C++, il n'y a aucune garantie que ce soit au sujet de la précision des données de virgule flottante. La grande majorité des implémentations utilisent IEEE-754 lors de la mise en œuvre de leurs types de point flottant, mais cela n'est pas requis. La seule chose exigée par le langage C++ est que (c++ spec §3.9.1.8):

Il existe trois types de points flottants: flottant, double et long. double. Le type double est au moins autant de précision que le flotteur, et le type long double offre au moins autant de précision double. L'ensemble des valeurs de type float est un sous-ensemble de l'ensemble de valeurs de type double; l'ensemble des valeurs de type double est un sous-ensemble de l'ensemble de valeurs de type long double. la représentation de la valeur des types à virgule flottante est définie par la mise en œuvre . Les types intégraux et flottants sont appelés collectivement types arithmétiques. Spécialisations du modèle standard std:: numeric_limits (18.3) doit spécifier les valeurs maximales et minimales de chaque type arithmétique pour une mise en œuvre.
4
répondu MuertoExcobito 2015-05-29 19:41:03

La quantité d'espace nécessaire pour stocker un float sera constante, et même un double ; le montant de la précision utile sera en termes relatifs, qui varient généralement, cependant, entre une partie en 2 23 et une partie en 2 24 pour float , ou l'on part à 2 52 et 2 53 pour double . La précision très proche de zéro n'est pas si bonne, avec le deuxième plus petit positif la valeur étant deux fois plus grande que la plus petite, qui sera à son tour infiniment plus grande que zéro. Cependant, sur la plus grande partie de la plage, la précision varie comme décrit ci-dessus.

noter que bien qu'il ne soit souvent pas pratique d'avoir des types dont la précision relative varie de moins d'un facteur de deux dans toute sa gamme, la variation de la précision peut parfois amener les calculs à donner des calculs beaucoup moins précis qu'il semblerait qu'ils devraient. Considérons, par exemple: 16777215.0f + 4.0f - 4.0f . Toutes les valeurs seraient précisément représentées par float à l'aide de la même échelle, et les valeurs les plus proches de la plus grande sont +/- une partie dans 16.777.215, mais la première addition donne un résultat dans une partie de la gamme float où les valeurs sont séparées par une partie dans seulement 8.388.610, ce qui fait que le résultat est arrondi à 16.777.220. Par conséquent, soustraire 4 donne 16 777 216 au lieu de 16 777 215. Pour la plupart des valeurs de float près de 16777216 , en ajoutant 4.0f et en soustrayant 4.0f donneraient la valeur originale inchangée, mais la précision changeante juste au point de rupture fait que le résultat est coupé d'un bit supplémentaire à l'endroit le plus bas.

3
répondu supercat 2015-05-31 20:38:57

la réponse à cette question est simple mais compliquée. Ces nombres sont stockés en binaire. Selon qu'il s'agit d'un float ou d'un double, l'ordinateur utilise différentes quantités de binaire pour stocker le nombre. La précision que vous obtenez dépend de votre binaire. Si vous ne savez pas comment les nombres binaires fonctionnent, ce serait une bonne idée de vérifier. Mais simplement dit, certains nombres ont besoin de plus de uns et de zéros que d'autres nombres.

donc la précision est fixe (même nombre de chiffres binaires), mais la précision que vous obtenez dépend du nombre que vous utilisez.

0
répondu NendoTaka 2015-06-01 01:54:16