Константа 1 усекается до целого числа?

Почему этот код не компилируется?

package main
const a = 1.000001
const base = 0
const b = a+base
func main() {
    f(b)
}
func f(int) {}

$ go run a.go
# command-line-arguments
./a.go:4: constant 1 truncated to integer

Это говорит о том, что 1 усекается? Или это 1 не может быть усечено? О чем идет речь?

Кто-то ответил, что приведенный выше код не компилируется, потому что b это float64, Но тогда почему это компилируется:

package main
import "fmt"
const a = 1.000001
const b = a-0.000001
func main() {
    fmt.Printf("%T %v\n",a,a)
    fmt.Printf("%T %v\n",b,b)
    f(b)
}
func f(int) {}

$ go run a.go 
float64 1.000001
float64 1

? b это float64 здесь, но это может быть передано f,

4 ответа

Команда go недавно написала об этом в блоге, который я предлагаю вам прочитать.

От введения

Go - это статически типизированный язык, который не допускает операций, которые смешивают числовые типы. Вы не можете добавить float64 к int или даже к int32 к int. Тем не менее, допустимо писать 1e6*time.Second или math.Exp(1) или даже 1<< ('\ t' + 2.0). В Go константы, в отличие от переменных, ведут себя почти как обычные числа. Этот пост объясняет, почему это так и что это значит.

TLDR - константы нетипизированы в Go. Их тип кристаллизуется только в последний момент.

Это объясняет вашу проблему выше. Дано

func f(int) {}

затем

f(1) // ok
f(1.000) // OK
f(1.0E6) // OK
f(1.0001) // BAD

В Go очень строгие правила преобразования констант:

Постоянное значение x может быть преобразовано в тип T в любом из этих случаев:

  • x представимо значением типа T,
  • x является константой с плавающей запятой, T является типом с плавающей запятой, и x представлен значением типа T после округления с использованием правил округления до четности IEEE 754. Постоянная T(x) это округленное значение.
  • x является целочисленной константой и T это тип строки То же правило, что и для непостоянных x применяется в этом случае.

Сообщение в блоге golang о константах может быть полезно для дальнейшего понимания. Из-за строгости каждое преобразование, которое нарушает указанные правила, считается ошибкой. Это объясняется тем, что Go пытается представить константы с максимально возможной точностью. Это также означает, что окончательный тип определяется в контексте используемого выражения. Отбрасывание точности опровергает это и является признаком возможной ошибки программирования.

Если вы действительно хотите округлить значение до целого числа, преобразуйте его в переменную ( пример в игре):

const b = 1.01
c := b
f(int(c))

Это работает, поскольку компилятор не отслеживает происхождение значения и правила констант, которые не применяются к переменным.

Но тогда почему это работает, когда я изменяю это на это? const a = 1.000001;const b = a-0.000001

В этом примере b равно 1. 1 может быть представлено как целое число, поэтому округления и потери информации не происходит. Следовательно, это не ошибка, поскольку она соответствует правилам преобразования значений с плавающей запятой (как указано выше).

Ваша первая программа может быть переписана так:

package main
func main() {
    f(1.000001)
}
func f(int) {}

Который явно не передает целочисленное значение в целочисленную функцию.

Ваша вторая программа также может быть переписана так:

package main
import "fmt"
func main() {
    fmt.Printf("%T %v\n",1.000001,1.000001)
    fmt.Printf("%T %v\n",1,1)
    f(1)
}
func f(int) {}

Который выглядит хорошо.

Все, что я сделал, это заменил вручную a а также b константы. Это все иди делает.

Отказ от ответственности: у меня нет опыта работы с Go, но приведенный ниже ответ основан на общих принципах, касающихся типов данных.

Ваша функция f принимает входной параметр типа int, но фактическое значение, которое вы передаете ему, т.е. b имеет значение с плавающей запятой на основе вашего кода. Это приведет к усечению значения с плавающей запятой до целочисленного значения, как указано в сообщении об ошибке.

Я полагаю, что вы могли бы решить эту проблему, изменив сигнатуру функции, приняв значение плавающего типа в качестве входного параметра, т.е.

func f(float64) {}

Демо в Go

Чтобы сравнить это с языком, с которым я знаком (C#), вы можете посмотреть код ниже:

public static void Main(string[] args)
    {
        var a = 1.3;
        var b = 1.3 + 9;
        f(b);
        Console.WriteLine("Hello, world!");
    }

public static void f(int a)
    {
    }

С использованием var Ключевое слово, мы не делаем явно a а также b переменные типа данных double, Однако, поскольку значения с плавающей точкой присваиваются им, их тип выводится как double, Теперь, если вы определите метод f как принимая входной параметр типа данных int а затем перейти в a или же b, это даст вам ошибку. Однако, если вы измените метод, чтобы взять double значение вместо intВаш код будет скомпилирован без проблем.

Демо в C#

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