Is this a bug ?

164 views
Skip to first unread message

Jamil Djadala

unread,
Jun 3, 2021, 5:08:22 AM6/3/21
to golang-nuts


package main

import (
    "fmt"
)

func main() {
    const aa int = 0
    var a int
    fmt.Println(float64(1<<aa), a)
    fmt.Println(float64(1<<a), a) // ./prog.go:11:21: invalid operation: 1 << a (shift of type float64)
}
./prog.go:11:21: invalid operation: 1 << a (shift of type float64)

Brian Candler

unread,
Jun 3, 2021, 5:21:13 AM6/3/21
to golang-nuts
Weird.  It simplifies to this: https://play.golang.org/p/OsOhRMC6kBu

Jan Mercl

unread,
Jun 3, 2021, 5:52:23 AM6/3/21
to Brian Candler, golang-nuts
On Thu, Jun 3, 2021 at 11:21 AM Brian Candler <b.ca...@pobox.com> wrote:
>
> Weird. It simplifies to this: https://play.golang.org/p/OsOhRMC6kBu

I think this case is WAI.

From https://golang.org/ref/spec#Operators

" If the left operand of a non-constant shift expression is an untyped
constant, it is first implicitly converted to the type it would assume
if the shift expression were replaced by its left operand alone."

The OP case is strange in that the constness of the RHS operator
somehow changes the interpretation of the above quotation
specification, but I cannot find that it should.

Axel Wagner

unread,
Jun 3, 2021, 5:53:32 AM6/3/21
to golang-nuts
It is not a bug. The spec says:

If the left operand of a constant shift expression is an untyped constant, the result is an integer constant; otherwise it is a constant of the same type as the left operand, which must be of integer type. 
 
Any other operation on untyped constants results in an untyped constant of the same kind

(emphasis mine). Given that a is not a constant, 1<<a is not a constant shift expression. Therefore, it's "any other operation" and is given a type from context. The way this happens is described in Operators:

The right operand in a shift expression must have integer type or be an untyped constant representable by a value of type uint. If the left operand of a non-constant shift expression is an untyped constant, it is first implicitly converted to the type it would assume if the shift expression were replaced by its left operand alone.

Therefore, the untyped constant 1 in float64(1<<a) is given the type it would assume in float64(1). I don't think it is surprising, that that type is float64. Though arguably, there is a difference between "float64(1) is a float64 constant" and "the 1 in float64(1) is given the type float64".

There's the section on conversions, which says:

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

That seems to read as "1 stays an untyped constant and is then converted". On the other hand, the section on constants says:

A constant may be given a type explicitly by a constant declaration or conversion, […]

This says that the untyped constant is given a type, which seems to imply that "1 is given the type float64 by the conversion".

So, it can be argued that the spec is insufficiently clear here. Personally, I tend to think that the behavior is reasonably clear - at least the part where float64(1) gives 1 the type float64. We generally accept that an untyped constant is given the type of the context it appears in. So it does take a while to chase down why it behaves this way, but it's in the spec.

But either way: If anything, this is a bug in the spec, not a bug in the implementation. At this point (more than ten years after Go 1), if something requires as subtle a reading of the spec as this, it's the implementation that is right, unless implementations disagree.

On Thu, Jun 3, 2021 at 11:21 AM Brian Candler <b.ca...@pobox.com> wrote:
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/87ee9f5f-6db2-48e8-86cc-de0b88511a65n%40googlegroups.com.

peterGo

unread,
Jun 3, 2021, 10:42:32 AM6/3/21
to golang-nuts
Jan,

The untyped constant 1 assumes the type of x, which is float64. With explicit types:

package main

import "fmt"

func main() {
    var a int = 0
    // invalid operation: float64(1) << a (shift of type float64)
    var x float64 = float64(1) << a
    fmt.Println(x)
}


Peter

tapi...@gmail.com

unread,
Jun 3, 2021, 11:39:06 AM6/3/21
to golang-nuts
Another runnable example:

package main

const x = 8
var y = 8

var a byte = 1 << x / 2
var b byte = 1 << y / 2

func main() {
  println(a) // 128
  println(b) // 0
}

Brian Candler

unread,
Jun 4, 2021, 6:32:16 AM6/4/21
to golang-nuts
> var a byte = (1 << x) / 2
> var b byte = (1 << y) / 2

That one's more obvious [once the parentheses are added], and I find it helps understand the float case as well.

x is a constant, so 1 << x is calculated at compile time to arbitrary precision, and then converted to a byte.  That's fine.

y is a variable, so this is an expression to be calculated at run time. The calculation is done at byte precision, because the context says that the result is being stored as a byte.  You wouldn't expect it to be done at any higher precision, would you?

Similarly,

b := byte((1 << y) / 2)

and again, the implication is that the 1 is a byte.  If you want the calculation to be performed at a higher precision, before converting to byte, you can force it to whatever you need:

b := byte((int64(1) << y) / 2)

But in any case, if you accept that byte(1 << y) should be performed as a byte calculation, then it's logical that

c := float64(1 << y)

should be treated the same way.  That is, the 1 in that expression is a float64, unless told otherwise.



Reply all
Reply to author
Forward
0 new messages