Странное поведение float64 в Go

Проверив поведение float64, я обнаружил следующее.

  • Вывод 6-значной переменной float64 равен 100000.
  • Выход 7-значной переменной float64 равен 1e+06.
package main

import "fmt"

func main() {
    var a float64 = 100000
    fmt.Println(a) // output: 100000

    var b float64 = 1000000
    fmt.Println(b) // output: 1e+06 (expected: 1000000)
}

Может кто-нибудь помочь мне, почему вывод отличается в зависимости от количества цифр.

Вы читали документацию к пакету fmt? Там указано: «Печать форматов, используя форматы по умолчанию для своих операндов». Затем вы можете выяснить, что форматом по умолчанию для float64 является %g, а затем прочитать, что %g — это «%e для больших показателей, %f в противном случае. Точность обсуждается ниже.», и, наконец, «Точность по умолчанию для %e, %f и %#g равна 6; для %g это наименьшее количество цифр, необходимое для однозначной идентификации значения.»

kostix 22.08.2024 13:42

Выполняет ли «Точность по умолчанию для %e, %f и %#g равна 6;» значит, %g это %.6g верно?

ken 22.08.2024 14:44

Кстати, отметьте пользователя, которому вы отвечаете, используя @username в комментариях, иначе пользователь не получит уведомление.

kostix 22.08.2024 16:27

Нет, не совсем. Обратите внимание, что глагол %#g отличается от %g (# в Go означает, так сказать, «более внутреннее» представление). Как указано, %g — это %e или %f в зависимости от показателя степени (грубо и фактически неверно, но вы можете думать об этом так, как будто это количество цифр в числе). В вашем случае форматирование 1e5 с использованием %g обнаруживает, что показатель степени мал, поэтому выбирает %f в качестве фактического форматирования, которое, в свою очередь, использует «наименьшее количество цифр…» в качестве точности. В случае 1e6 выбирается %e. Я понятия не имею, точно ли задокументированы критерии выбора между %e и %f.

kostix 22.08.2024 16:31

Ну да, документация fmt, я бы сказал, слишком сухая и нормативная ;-)

kostix 22.08.2024 16:35

@kostix Извините, я забыл использовать тег, потому что это мой первый пост. ох, похоже, спецификация %g сложная. Я бы сказал, что документацию fmt тоже сложно читать:0 Спасибо за ответ!

ken 22.08.2024 18:00

См. также это.

kostix 22.08.2024 18:17

@kostix Ну, буду иметь ввиду!

ken 22.08.2024 18:33
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
1
8
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы наблюдаете поведение из-за форматирования Go по умолчанию для чисел с плавающей запятой. При выводе float64 Go пытается кратко представить число. Он печатает полное значение для меньших чисел (например, 100000). Однако как только в числе появляется больше цифр (например, 1000000), Go переключается на научную запись (1e+06).

Если вы хотите, чтобы полное значение печаталось без научных обозначений, вы можете использовать fmt.Printf с директивой форматирования:

package main

import "fmt"

func main() {
    var a float64 = 100000
    fmt.Printf("%.f\n", a)
    var b float64 = 1000000
    fmt.Printf("%.f\n", b)
}

Это напечатает оба числа без научных обозначений:

100000
1000000

Try on godbolt.org

Соответствующая функция, которая принимает это решение в научной записи, в исходном коде по адресу go.dev/src/strconv/ftoa.go по состоянию на 22 августа 2024 г.:

func formatDigits(dst []byte, shortest bool, neg bool, digs decimalSlice, prec int, fmt byte) []byte {
    switch fmt {
    case 'e', 'E':
        return fmtE(dst, neg, digs, prec, fmt)
    case 'f':
        return fmtF(dst, neg, digs, prec)
    case 'g', 'G':
        // trailing fractional zeros in 'e' form will be trimmed.
        eprec := prec
        if eprec > digs.nd && digs.nd >= digs.dp {
            eprec = digs.nd
        }
        // %e is used if the exponent from the conversion
        // is less than -4 or greater than or equal to the precision.
        // if precision was the shortest possible, use precision 6 for this decision.
        if shortest {
            eprec = 6
        }
        exp := digs.dp - 1
        if exp < -4 || exp >= eprec {
            if prec > digs.nd {
                prec = digs.nd
            }
            return fmtE(dst, neg, digs, prec-1, fmt+'e'-'g')
        }
        if prec > digs.dp {
            prec = digs.nd
        }
        return fmtF(dst, neg, digs, max(prec-digs.dp, 0))
    }

    // unknown format
    return append(dst, '%', fmt)
}

Спасибо за ваш ответ и исходный код :) @Sash Sinha

ken 22.08.2024 18:21

Другие вопросы по теме

Влияет ли знак на точность и точность чисел с плавающей запятой?
Можно ли с уверенностью предположить, что 32-битные числа с плавающей запятой можно напрямую сравнивать друг с другом, если значение соответствует мантиссе?
Разница между 0f и 0 на Vector2 и Vector3 в Unity
Почему я получаю большие случайные значения при использовании scanf("%f", &intVar)? Как входные данные с плавающей запятой преобразуются в эти значения?
Почему коэффициент разницы, где h = FLT_MIN, всегда равен 0? FLT_MIN теряется в неточностях с плавающей запятой?
Как справиться с переполнением в двойных скалярах при работе с очень большими числами в Python?
NASM x64: часть с плавающей запятой неправильно напечатана как 0,000000
Преобразование числа с плавающей запятой в 16-битное двоичное значение в C
Преобразование с потерями между длинными двойными и двойными
Неверный расчет математического выражения с использованием sin и cos в C?