Точность плавающей точки Golang float32 против float64
я написал программу для демонстрации ошибки с плавающей запятой в 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 = %en", i, a)
}
он печатает:
After 54 iterations, a = 1.000000e+00
это соответствует поведению той же программы, написанной на C (используя double
type)
float32
используется вместо этого, программа застревает в бесконечном цикле! Если вы измените программу C на использование float
вместо double
, он печатает
After 27 iterations, a = 1.600000e+00
почему программа Go не имеет того же выхода, что и программа C при использовании float32
?
2 ответов
согласен с анисом, go делает правильную вещь. Что касается C, я не убежден его догадкой.
стандарт C не диктует, но большинство реализаций libc преобразует десятичное представление в ближайший float (по крайней мере, в соответствии с IEEE-754 2008 или ISO 10967), поэтому я не думаю, что это наиболее вероятное объяснение.
существует несколько причин, по которым поведение программы C может отличаться... Особенно, некоторые промежуточные вычисления могут быть выполняется с избыточной точностью (double или long double).
самое вероятное, что я могу придумать, это если когда-либо вы написали 0.1 вместо 0.1 f В C.
В этом случае вы можете вызвать избыточную точность в инициализации
(вы суммируете float a+double 0.1 => поплавок преобразуется в double, затем результат преобразуется обратно в float)
если я эмулирую эти операции
float32(float32(float32(0.2) + float64(0.1)) - float64(0.3))
затем я нахожу что-то около 1.1920929 e-8f
после 27 итерации, это составляет 1.6 f
используя math.Float32bits
и math.Float64bits
, вы можете увидеть, как Go представляет различные десятичные значения как двоичное значение IEEE 754:
площадка:https://play.golang.org/p/ZqzdCZLfvC
результат:
float32(0.1): 00111101110011001100110011001101
float32(0.2): 00111110010011001100110011001101
float32(0.3): 00111110100110011001100110011010
float64(0.1): 0011111110111001100110011001100110011001100110011001100110011010
float64(0.2): 0011111111001001100110011001100110011001100110011001100110011010
float64(0.3): 0011111111010011001100110011001100110011001100110011001100110011
если вы преобразовать эти двоичное представление десятичных значений и сделать свой цикл, вы можете увидеть, что для float32, начальное значение a
будет быть:
0.20000000298023224
+ 0.10000000149011612
- 0.30000001192092896
= -7.4505806e-9
отрицательное значение, которое никогда не может суммировать до 1.
Итак, почему C ведет себя по-другому?
если вы посмотрите на двоичный шаблон (и немного знаете, как представлять двоичные значения), вы можете увидеть, что Go округляет последний бит, а я предполагаю, что C просто обрезает его.
таким образом, в некотором смысле, хотя ни Go, ни C не могут точно представлять 0.1 в поплавке, Go использует значение, ближайшее к 0.1:
Go: 00111101110011001100110011001101 => 0.10000000149011612
C(?): 00111101110011001100110011001100 => 0.09999999403953552
Edit:
Я написал вопрос о том, как C обрабатывает константы float, и из ответа кажется, что любая реализация стандарта C может делать либо то, либо другое. Реализация, которую вы попробовали, просто сделала это по-другому, чем Go.