Golang précision en virgule flottante float32 vs float64
J'ai écrit un programme pour démontrer à virgule flottante d'erreur dans le jeu de 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 d'un même programme écrit en C (à l'aide de la double
type)
Toutefois, si float32
est utilisé au lieu de cela, le programme reste bloqué dans une boucle infinie! Si vous modifiez le programme C d'utiliser un float
au lieu d'un double
, il imprime
After 27 iterations, a = 1.600000e+00
Pourquoi ne pas le programme de Go ont le même résultat que le programme C lors de l'utilisation de float32
?
- Je ne vois pas de problème... 0.2 + 0.1 = 0.3, 0.3 - 0.3 = 0.0, boucle à travers 0.0 + 0.0 ne serait jamais s'élever au-dessus de 1,0 Ce que je suis confus sur la façon dont vous avez réussi à le faire sortir de la boucle avec la float64?
- les nombres à virgule flottante ne sont pas parfaitement exactes. En particulier, le nombre de 0,1 et 0,3 ne peut pas être représenté exactement. Cela provoque
a
avoir une valeur non nulle (même très petite) valeur avant d'entrer dans la boucle. Wikipedia a une explication. en.wikipedia.org/wiki/Guard_digit - J'ai commencé à jouer avec cette aire de jeux play.golang.org/p/Im6OFfTFPY, et j'ai un peu de voir ce que tu veux dire, mais il semble en Aller float32s sont représentés exactement, tandis que float64s ne sont pas
- Si vous cochez l'ASM du code avec
go tool 6g -S main.go
vous verrez la raison. Le calcul pour float32 est comme suit: 2.00000002980232230 e-01 + 1.00000001490116120 e-01 - 3.00000011920928950 e-01, qui est une valeur négative, et ne sera jamais la somme des 1. Pourquoi Aller à cela, je ne sais pas. - Joué avec une autre aire de jeux (play.golang.org/p/FZxCQTS9yG) un peu plus de temps et a constaté que lorsque vous imprimez le float64 jusqu'à 20 décimales, vous obtenez beaucoup plus de chiffres que de simplement
0.30...04
, vous obtenez0.30000000000000004440892098500626161694526672363281
et le reste est coupée. Je suppose qu'avec un float32, beaucoup plus coupée et il devient arrondi à un même0.3
. Cela pourrait expliquer l'arithmétique, mais pour l'instant c'est juste une théorie.
Vous devez vous connecter pour publier un commentaire.
D'accord avec ANisus, aller est en train de faire la bonne chose. Concernant le C, je ne suis pas convaincu par son deviner.
Le C standard ne dicte pas, mais la plupart des implémentations de libc convertir la représentation décimale la plus proche float (au moins pour se conformer à la norme IEEE-754 2008 ou ISO 10967), donc je ne pense pas que c'est l'explication la plus probable.
Il y a plusieurs raisons pour lesquelles le programme C comportement peut varier... en Particulier, certains calculs intermédiaires peuvent être réalisées avec un excès de précision (double ou long double).
La chose la plus probable je pense, c'est que si jamais vous avez écrit 0.1 au lieu de 0,1 f en C.
Dans ce cas, vous pourriez avoir cause un excès de précision lors de l'initialisation
(vous somme flotter un double+0.1 => le flotteur est converti en double, alors le résultat est de nouveau converti en float)
Si je émuler ces opérations
Puis-je trouver quelque chose près de 1.1920929 e-8f
Après 27 itérations, ce sommes à 1,6 f
À l'aide de
math.Float32bits
etmath.Float64bits
, vous pouvez voir comment Aller de l'représente les différentes valeurs décimales comme une norme IEEE 754 valeur binaire:Aire de jeux: https://play.golang.org/p/ZqzdCZLfvC
Résultat:
Si vous convertir ces représentation binaire à décimal des valeurs et de faire de votre boucle, vous pouvez voir que pour float32, la valeur initiale de
a
sera:une valeur négative qui ne peut jamais jamais la somme des 1.
Alors, pourquoi est-ce que C se comportent-ils différents?
Si vous regardez le modèle binaire (et de savoir légèrement sur la façon de représenter les valeurs binaires), vous pouvez voir qui Vont tours de la dernière bits alors que je suppose que C juste des cultures au lieu.
Donc, dans un sens, tandis que ni Aller ni C peut représenter de 0,1 exactement dans un flotteur, utilise la valeur la plus proche à 0.1:
Edit:
J'ai posté une question à propos de la façon dont C gère les constantes float, et de la réponse, il semble que la mise en œuvre de la norme C est autorisé à le faire. La mise en œuvre vous essayé avec juste fait différemment que d'Aller de l'.
strconv.FormatUint(x, 2)
,fmt.Printf
a un "%b" format. Pas besoin deunsafe
, il estmath.Float32bits
etmath.Float64bits
. Une meilleure version est: play.golang.org/p/ZqzdCZLfvC