Iván Cortés
Iván Cortés

Reputation: 652

Why converting the same value in two ways, the results are different?

Two kinds of conversion of the same constant to float64 return the same value, but when I try to convert these new values to int, the results are different.

...

const Big = 92233720368547758074444444

func needFloat(x float64) float64 {
    return x
}

func main() {
    fmt.Println(needFloat(Big))
    fmt.Println(float64(Big))

    fmt.Println(int(needFloat(Big)))
    fmt.Println(int(float64(Big)))
}

I'd expect the two first Println return the same type of value

fmt.Println(needFloat(Big)) // 9.223372036854776e+25
fmt.Println(float64(Big))   // 9.223372036854776e+25

so when I convert them to int, I expect the same output, but:

fmt.Println(int(needFloat(Big))) // -2147483648
fmt.Println(int(float64(Big)))   // constant 92233720368547758080000000 overflows int

Upvotes: 1

Views: 147

Answers (2)

torek
torek

Reputation: 487785

If your real question is why one attempt to convert to int produces a compile-time error message, but the other produces a very negative integer, it's because one is a compile-time conversion, and the other is a runtime conversion. I think it helps in these cases to be explicit about what you are expecting, and what can be run and what can't. Here's a Go Playground version of your code, where the last conversion is commented out. The reason for commenting it out is of course that it doesn't compile.

As Adrian noted in a comment, Big is a constant, specifically an untyped one. As Uvelichitel answered, a constant x (of any type) can be converted to a new and different type T if and only if

x is representable by a value of type T.

(The quote part is from the section Uvelichitel linked, except that mine adds the inner link for the word "representable".)

The expression float64(Big) is an explicit type conversion, with a constant as its x, so the result is a float64-typed constant with the given value. So far, that's fine: now we have 92233720368547758074444444 as a float64. This chops off some of the digits: the actual internal representation is 92233720368547758080000000 (see variant with %f directives). The low digits, ...74444444, have been rounded to ...80000000. See the link for "representable" for why the rounding occurs.

The expression int(float64(Big)) is an outer explicit type conversion surrounding an inner explicit type conversion. We already know what the inner type conversion does: it produces the float64 constant 92233720368547758080000000.0. The outer conversion tries to represent this new value as int, but it does not fit, producing an error:

./prog.go:18:17: constant 92233720368547758080000000 overflows int

if the commented-out line is uncommented. Note again that the value has been rounded, due to the inner conversion.

On the other hand, needFloat(Big) is a function call. Calling the function assigns the untyped constant to its argument (a float64) and obtains its return value (the same float64, value 92233720368547758080000000.0. Printing that prints what you'd expect, given the default or explicit formatting directive. The returned value is not a constant.

Similarly, int(needFloat(Big)) calls needFloat, which returns the same float64 value—not a constant—as before. The int explicit type conversion tries to convert this value to int at runtime, rather than at compile time. For such conversions between numeric types, there is a list of three explicit rules at https://golang.org/ref/spec#Conversions, plus a final caveat. Here, rule 2 applies: any fractional part is discarded. But the caveat also applies:

In all non-constant conversions involving floating-point or complex values, if the result type cannot represent the value the conversion succeeds but the result value is implementation-dependent.

In other words, there is no runtime error, but the int value you get—which in this case was -2147483648, which is the smallest allowed 32-bit integer—is up to the implementation. This particular implementation chose to use this particular negative number as its result. Another implementation might choose some other number. (Interestingly, in the playground, if I convert directly to uint I get zero. If I convert to int, then to uint, I get the 0x80000000 I expected.)

Hence, the key difference in terms of whether you get an error is whether you do the conversion at compile time, via constants, or at runtime, via runtime conversion.

Upvotes: 1

Uvelichitel
Uvelichitel

Reputation: 8490

int(float64(Big)) //illegal because

A constant value x can be converted to type T if x is representable by a value of T

int(needFloat(Big)) //is non-constant expression because of function call

A non-constant value x can be converted to type T in any of these cases: - x's type and T are both integer or floating point types.

https://golang.org/ref/spec#Conversions

Upvotes: 0

Related Questions