операций с плавающей точкой в


Вот пример кода в go:

package main

import "fmt"

func mult32(a, b float32) float32 { return a*b }
func mult64(a, b float64) float64 { return a*b }


func main() {
    fmt.Println(3*4.3)                  // A1, 12.9
    fmt.Println(mult32(3, 4.3))         // B1, 12.900001
    fmt.Println(mult64(3, 4.3))         // C1, 12.899999999999999

    fmt.Println(12.9 - 3*4.3)           // A2, 1.8033161362862765e-130
    fmt.Println(12.9 - mult32(3, 4.3))  // B2, -9.536743e-07
    fmt.Println(12.9 - mult64(3, 4.3))  // C2, 1.7763568394002505e-15

    fmt.Println(12.9 - 3*4.3)                               // A4, 1.8033161362862765e-130
    fmt.Println(float32(12.9) - float32(3)*float32(4.3))    // B4, -9.536743e-07
    fmt.Println(float64(12.9) - float64(3)*float64(4.3))    // C4, 1.7763568394002505e-15

}

Результаты различия между линиями A1, B1 и C1 понятны. Однако, начиная с А2 до С2, приходит магия. В результате ни Б2, ни С2 соответствует результату от линии А2. То же самое верно и для линий x2 (x = A, B или C) - но выходы x2 и x4 одинаковы.

Просто для уверенности давайте напечатаем результаты в двоичном виде.

    fmt.Printf("%bn", 3*4.3)                   // A11, 7262054399134925p-49
    fmt.Printf("%bn", mult32(3, 4.3))          // B11, 13526631p-20
    fmt.Printf("%bn", mult64(3, 4.3))          // C11, 7262054399134924p-49

    fmt.Printf("%bn", 12.9 - 3*4.3)            // A12, 4503599627370496p-483
    fmt.Printf("%bn", 12.9 - mult32(3, 4.3))   // B12, -8388608p-43
    fmt.Printf("%bn", 12.9 - mult64(3, 4.3))   // C12, 4503599627370496p-101

    fmt.Printf("%bn", 12.9 - 3*4.3)                                // A14, 4503599627370496p-483
    fmt.Printf("%bn", float32(12.9) - float32(3)*float32(4.3))     // B14, -8388608p-43
    fmt.Printf("%bn", float64(12.9) - float64(3)*float64(4.3))     // C14, 4503599627370496p-101

Некоторые факты из приведенного выше кода (один в форме бин):

  1. есть разница между строками А11 и С11 (последняя цифра-непосредственно перед показателем степени).
  2. линии A12 и C12 почти одинаковы (за исключением экспоненты!!!), то же самое можно наблюдать между линиями А14 и С14.

И тут возникают вопросы:

  1. как выполняются вычисления голых (naked :)) чисел? (вычисления в каждой строке Axx)
  2. выполняются ли они компилятором / чем угодно?
  3. если да, то почему они отличаются? Оптимизация?
  4. являются ли они вычисляется в некоторой системе, которая отличается от IEE-754?
  5. если да, то почему?
  6. Оправдывает ли такой подход достижение более точной точности?

Код был протестирован на 64-битном linux под обоими "go run" и "go build" (go1.0.3), а также на этом сайте: http://tour.golang.org/

2 3

2 ответа:

  1. Константы :

      Числовые константы представляют собой значения произвольной точности и не переполняются.
  2. представляют собой целочисленные константы, имеющие не менее 256 бит.
  3. представляют константы с плавающей запятой, включая части комплексной константы, с мантиссой не менее 256 бит и знаковым показателем степени не менее 32 бит.
  4. Да, компилятором для констант времени компиляции.

  5. Да, это так. другое: требуется большая точность. См. 1.

  6. Да, см.

  7. Минимизировать накопление ошибок с плавающей запятой для многочленных выражений констант с плавающей запятой.

  8. Конечно, да. Может ли достижение более низкой точности быть когда-либо целью? Достаточно того, что операции с плавающей запятой во время выполнения внутренне несовершенны, нет необходимости добавлять больше неточностей от постоянных выражений.

Представляют константы с плавающей запятой, включая части комплексной константы, с мантиссой не менее 256 бит и знаковым показателем степени не менее 32 бит.

Обратите внимание, что Go 1.8 (в настоящее время бета-версия в 4 квартале 2016 года, выпущена в 1 квартале 2017 года) изменяет это определение:

Спецификация языка теперь требует только, чтобы реализации поддерживали до 16-битных экспонент в константах с плавающей запятой.
Это не влияет ни на "gc" или компиляторы gccgo, оба из которых все еще поддерживают 32-разрядные экспоненты.

Это происходит от изменения 17711

spec: требуется 16-битный минимальный показатель степени в константах, а не 32

16-битный двоичный показатель степени допускает постоянный диапазон, охватывающий примерно диапазон от 7e-9865 до 7e9863, что более чем достаточно для любой практической и гипотетической постоянной арифметики.

Кроме того, до недавнего времени cmd/compile не мог справиться во всяком случае, очень большие экспоненты корректны; то есть вероятность того, что любые реальные программы (кроме тестов, которые исследуют угловые случаи) будут затронуты, близка к нулю.

Наконец, ограничение минимального поддерживаемого диапазона значительно снижает сложность реализации в области, которая вряд ли имеет значение в реальности для новых или альтернативных реализаций, совместимых со спецификациями, которые не полагаются или не могут полагаться на ранее существовавшие арифметические пакеты точности arbitratry, поддерживающие 32-битный диапазон экспонент. Это технически изменение языка, но по причинам, упомянутым выше, это вряд ли повлияет на какие-либо реальные программы, и, конечно, не программы, скомпилированные с компиляторами gc или gccgo, поскольку они в настоящее время поддерживают до 32-битных экспонент.

См. выпуск 13572 упоминая, что:

В Go 1.4 компилятор отклонил экспоненты больше 10000 (из-за знания, что код не работает для больших экспонент) без каких-либо жалоб от пользователей.

В более ранние версии Go, большие экспоненты были молчаливо неправильно обработаны, опять же без каких-либо жалоб от пользователей.