web-dev-qa-db-fra.com

Golang flottant précision flottant32 vs float64

J'ai écrit un programme pour démontrer l'erreur en virgule flottante dans Go:

func main() {
    a := float64(0.2) 
    a += 0.1
    a -= 0.3
    var i int
    for i = 0; a < 1.0; i++ {
        a += a
    }
    fmt.Printf("After %d iterations, a = %e\n", i, a)
}

Il imprime:

After 54 iterations, a = 1.000000e+00

Cela correspond au comportement du même programme écrit en C (en utilisant le type double)

Toutefois, si float32 est utilisé à la place, le programme est bloqué dans une boucle infinie! Si vous modifiez le programme C pour utiliser un float au lieu d'un double, il imprime

After 27 iterations, a = 1.600000e+00

Pourquoi le programme Go n'a-t-il pas la même sortie que le programme C lors de l'utilisation de float32?

20
charliehorse55

D'accord avec ANisus, go fait la bonne chose. Concernant C, je ne suis pas convaincu par sa supposition.

La norme C ne dicte pas, mais la plupart des implémentations de libc convertiront la représentation décimale en flottant le plus proche (au moins pour se conformer à IEEE-754 2008 ou ISO 10967), donc je ne pense pas que ce soit l'explication la plus probable.

Il y a plusieurs raisons pour lesquelles le comportement du programme C peut différer ... En particulier, certains calculs intermédiaires peuvent être effectués avec une précision excessive (double ou double long).

La chose la plus probable à laquelle je peux penser, c'est si jamais vous écrivez 0,1 au lieu de 0,1f en C.
Dans ce cas, vous pourriez avoir causé une précision excessive lors de l'initialisation
(vous additionnez float a + double 0,1 => le float est converti en double, puis le résultat est reconverti en float)

Si j'émule ces opérations

float32(float32(float32(0.2) + float64(0.1)) - float64(0.3))

Ensuite, je trouve quelque chose près de 1.1920929e-8f

Après 27 itérations, cela revient à 1,6f

17
aka.nice

En utilisant math.Float32bits et math.Float64bits , vous pouvez voir comment Go représente les différentes valeurs décimales sous forme de valeur binaire IEEE 754:

Aire de jeux: https://play.golang.org/p/ZqzdCZLfvC

Résultat:

float32(0.1): 00111101110011001100110011001101
float32(0.2): 00111110010011001100110011001101
float32(0.3): 00111110100110011001100110011010
float64(0.1): 0011111110111001100110011001100110011001100110011001100110011010
float64(0.2): 0011111111001001100110011001100110011001100110011001100110011010
float64(0.3): 0011111111010011001100110011001100110011001100110011001100110011

Si vous convertissez ces représentation binaire en valeurs décimales et faites votre boucle, vous pouvez voir que pour float32, la valeur initiale de a sera:

0.20000000298023224
+ 0.10000000149011612
- 0.30000001192092896
= -7.4505806e-9

une valeur négative qui ne peut jamais se résumer à 1.

Alors, pourquoi C se comporte-t-il différemment?

Si vous regardez le modèle binaire (et savez un peu comment représenter les valeurs binaires), vous pouvez voir que Go arrondit le dernier bit alors que je suppose que C le recadre à la place.

Donc, dans un sens, alors que ni Go ni C ne peuvent représenter 0,1 exactement dans un flottant, Go utilise la valeur la plus proche de 0,1:

Go:   00111101110011001100110011001101 => 0.10000000149011612
C(?): 00111101110011001100110011001100 => 0.09999999403953552

Modifier:

J'ai posté ne question sur la façon dont C gère les constantes flottantes , et d'après la réponse, il semble que toute implémentation de la norme C soit autorisée à le faire. L'implémentation avec laquelle vous l'avez essayé l'a fait différemment de Go.

24
ANisus