confusion about convert `uint8` to `int8` - go

I want to convert uint8 to int, so I write a const 0xfc, and try to use int8(0xfc) to convert it. However the code raises an error:
package main
import (
"fmt"
)
func main() {
a := int8(0xfc) // compile error: constant 252 overflows int8
b := a
fmt.Println(b)
}
But if I defer the type conversion after assignment, the code can work around.
package main
import (
"fmt"
)
func main() {
a := 0xfc
b := int8(a) // ok
fmt.Println(b)
}
My question:
Is there any difference between these two codes?
Why does the first one raise a compile error?

see: https://golang.org/ref/spec#Constant_expressions
The values of typed constants must always be accurately representable by values of the constant type. The following constant expressions are illegal:
uint(-1) // -1 cannot be represented as a uint
int(3.14) // 3.14 cannot be represented as an int
int64(Huge) // 1267650600228229401496703205376 cannot be represented as an int64
Four * 300 // operand 300 cannot be represented as an int8 (type of Four)
Four * 100 // product 400 cannot be represented as an int8 (type of Four)
see:
https://blog.golang.org/constants
not all integer values can fit in all integer types. There are two problems that might arise: the value might be too large, or it might be a negative value being assigned to an unsigned integer type. For instance, int8 has range -128 through 127, so constants outside of that range can never be assigned to a variable of type int8:
var i8 int8 = 128 // Error: too large.
Similarly, uint8, also known as byte, has range 0 through 255, so a large or negative constant cannot be assigned to a uint8:
var u8 uint8 = -1 // Error: negative value.
This type-checking can catch mistakes like this one:
type Char byte
var c Char = '世' // Error: '世' has value 0x4e16, too large.
If the compiler complains about your use of a constant, it's likely a real bug like this.
My actual demand is to convert a byte to int32 when parsing a binary file. I may encounter the constant byte 0xfc, and should transfer it to the int8 before converting it to the int32 with the consideration of sign.
Yes, this is the way to go:
var b byte = 0xff
i32 := int32(int8(b))
fmt.Println(i32) // -1

Is there any difference between these two codes?
The first example uses a constant expression. The second uses plain expressions. Constant expressions are evaluated at compile time with different rules from plain expressions.
Why does the first one raise a compile error?
The int8(0xfc) is a typed constant expression. Values of typed constants must always be accurately representable by values of the constant type. The compiler reports an error because the value 252 cannot be represented by the values of int8.
Based on comments on other answers, I see that the goal is to get an int32 from a byte with sign extension. Given a byte variable b, use the expression int32(int8(b)) to get the int32 value with sign extension.

Related

Why range and subscription of a string produce different types?

import (
"fmt"
"reflect"
)
func main() {
s := "hello" // Same results with s := "世界"
for _, x := range s {
kx := reflect.ValueOf(x).Kind()
fmt.Printf("Type of x is %v\n", kx)
break
}
y := s[0]
ky := reflect.ValueOf(y).Kind()
fmt.Printf("Type of y is %v\n", ky)
}
// Type of x is int32
// Type of y is uint8
I was surprised to learn that I would get a different type if I use string subscription versus getting it via range.
Edit: I just realized that even s is a Unicode string, the type of y is always byte. This also means indexing into a string is unsafe unless it's an ASCII string.
For statements with range clause: (Link)
For a string value, the "range" clause iterates over the Unicode code points in the string starting at byte index 0. On successive iterations, the index value will be the index of the first byte of successive UTF-8-encoded code points in the string, and the second value, of type rune, will be the value of the corresponding code point. If the iteration encounters an invalid UTF-8 sequence, the second value will be 0xFFFD, the Unicode replacement character, and the next iteration will advance a single byte in the string.
Now let's look at the types: (Link)
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
So this explains why int32 is for a rune, and uint8 is for a byte.
Here's some code to make the point clear. I've added some code and changed the
string to make it better. I hope the comments are self-explanatory. Also, I'd recommend reading: https://blog.golang.org/strings as well.
package main
import (
"fmt"
"reflect"
)
func main() {
// Changed the string for better understanding
// Each character is not of single byte
s := "日本語"
// Range over the string, where x is a rune
for _, x := range s {
kx := reflect.ValueOf(x).Kind()
fmt.Printf(
"Type of x is %v (%c)\n",
kx,
x, // Expected (rune)
)
break
}
// Indexing (First byte of the string)
y := s[0]
ky := reflect.ValueOf(y).Kind()
fmt.Printf(
"Type of y is %v (%c)\n",
ky,
y,
/*
Uh-oh, not expected. We are getting just the first byte
of a string and not the full multi-byte character.
But we need '日' (3 byte character).
*/
)
// Indexing (First rune of the string)
z := []rune(s)[0]
kz := reflect.ValueOf(z).Kind()
fmt.Printf(
"Type of z is %v (%c)\n",
kz,
z, // Expected (rune)
)
}
Sample output:
Type of x is int32 (日)
Type of y is uint8 (æ)
Type of z is int32 (日)
Note: In case your terminal is not showing the same output; there might be some issue with character encoding settings. So, changing that might help.

Printing type of the numeric constant causes overflow

I am new to Go and currently following A Tour of Go.
I am currently at page Numeric Constants. Down below is a trimmed down version of the code that runs on that page:
package main
import "fmt"
const Big = 1 << 100
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needFloat(Big))
// fmt.Printf("Type of Big %T", Big)
}
this code compiles successfully with the output 1.2676506002282295e+29
The following code however will not compile and give an error:
package main
import "fmt"
const Big = 1 << 100
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needFloat(Big))
fmt.Printf("Type of Big %T", Big)
}
Output:
./prog.go:9:13: constant 1267650600228229401496703205376 overflows int
Why do you think this happened? I hope you will kindly explain.
The constant Big is an untyped constant. An untyped constant can be arbitrarily large and it doesn't have to fit into any predefined type's limits. It is interpreted and truncated in the context it is used.
The function needFloat gets a float64 argument. At this instance Big is converted to a float64 and used that way.
When you use it for Printf, it tries to pass it in as an int because it is not a decimal number (otherwise it would've converted it to float64), and it causes an overflow. Pass it as float64(Big), and it should work.
I guess the reason is that Big gets computed (i.e. casted right before being passed to needFloat, and gets instead computed as a int64 before the Printf. As a proof, the following statement computes correctly:
package main
import "fmt"
const Big = 1 << 100
func main() {
fmt.Printf("Type of Big %T", float64(Big))
}
Hope this helps.
The untyped constant n must be converted to a type before it can be assigned to the interface{} parameter in the call to fmt.Println.
fmt.Println(a ...interface{})
When the type can’t be inferred from the context, an untyped constant is converted to a bool, int, float64, complex128, string or rune depending of the format of the constant.
In this case the constant is an integer, but n is larger than the maximum value of an int.
However, n can be represented as a float64.
const n = 9876543210 * 9876543210
fmt.Println(float64(n))
For exact representation of big numbers, the math/big package implements arbitrary-precision arithmetic. It supports signed integers, rational numbers and floating-point numbers.
This is taken from https://yourbasic.org/golang/gotcha-constant-overflows-int/.

How does the constant value auto type work in golang?

Here is the code snippet:
a := 40
f := float64(a/100.0)
fmt.Println("Hello, playground", f)
f=0, why?
Spec: Constants:
An untyped constant has a default type which is the type to which the constant is implicitly converted in contexts where a typed value is required, for instance, in a short variable declaration such as i := 0 where there is no explicit type. The default type of an untyped constant is bool, rune, int, float64, complex128 or string respectively, depending on whether it is a boolean, rune, integer, floating-point, complex, or string constant.
a := 40 is a short variable declaration, the untyped integer constant 40 has a default type int, so a will be of type int.
In a/100.0 since a is of type int and 100.0 is representable by an integer, it will take the type of int, and a/100.0 will be an integer division, resulting in 0.
This 0 int value will be then converted to float64.
Note that if you would change the first line to:
a := 40.0
Then the output would be 0.4 because the 40.0 is an untyped floating point literal and so it has a default type of float64. So a will be of type float64, and a/100.0 will be a floating-point division, resulting in 0.4. Try it on the Go Playground.
You would get the same result if you'd use a typed constant, because here no default type is used, float64(40) is a typed constant, obviously of type float64 (try it on the Go Playground):
a := float64(40)
See The Go Blog: Constants.
Here default type for a is int since you use a:=40 and but 100.0 is just a constant so no auto-detect of type for variable.
So a/100.0 consider as integer division and which result is 0 which means f := float64(0) executed
Case Analysis
If you set 100.0 in a variable.
a := 40
b := 100.0
f := float64(a/b)
Then b is a float64 type. So you get error like
invalid operation: a / b (mismatched types int and float64)
Because you are trying to do divide operation with two different type
And if type cast a into float64() then it works
a := 40
b := 100.0
f := float64(a)/b
So, here both variable is float64 type in divide operation.
Now, as we cast a into float64 then in divide operation we can directly use 100.0 without variable.
a := 40
f := float64(a)/100.0

No panic when converting int to uint?

I'm confused about the following type conversion. I would expect both uint conversions to panic.
a := -1
_ = uint(a) // why no panic?
_ = uint(-1) // panics: constant -1 overflows uint
Why doesn't it panic in line 2?
https://play.golang.org/p/jcfDL8km2C
As mentioned in issue 6923:
T(c) where T is a type and c is a constant means to treat c as having type T rather than one of the default types.
It gives an error if c can not be represented in T, except that for float and complex constants we quietly round to T as long as the value is not too large.
Here:
const x uint = -1
var x uint = -1
This doesn't work because -1 cannot be (implicitly) converted to a uint.
_ = uint(a) // why no panic?
Because a is not an untyped constant, but a typed variable (int). See Playground and "what's wrong with Golang constant overflows uint64":
package main
import "fmt"
func main() {
a := -1
_ = uint(a) // why no panic?
var b uint
b = uint(a)
fmt.Println(b)
// _ = uint(-1) // panics: main.go:7: constant -1 overflows uint
}
Result: 4294967295 (on 32-bits system) or 18446744073709551615 (on 64-bits system), as commented by starriet
That are specific rules for the conversion of non-constant numeric values:
When converting between integer types, if the value is a signed integer, it is sign extended to implicit infinite precision; otherwise it is zero extended.
It is then truncated to fit in the result type's size.

What is the difference between int and int64 in Go?

I have a string containing an integer (which has been read from a file).
I'm trying to convert the string to an int using strconv.ParseInt(). ParseInt requires that I provide a bitsize (bit sizes 0, 8, 16, 32, and 64 correspond to int, int8, int16, int32, and int64).
The integer read from the file is small (i.e. it should fit in a normal int). If I pass a bitsize of 0, however, I get a result of type int64 (presumably because I'm running on a 64-bit OS).
Why is this happening? How do I just get a normal int? (If someone has a quick primer on when and why I should use the different int types, that would awesome!)
Edit: I can convert the int64 to a normal int using int([i64_var]). But I still don't understand why ParseInt() is giving me an int64 when I'm requesting a bitsize of 0.
func ParseInt(s string, base int, bitSize int) (i int64, err error)
ParseInt always returns int64.
bitSize defines the range of values.
If the value corresponding to s cannot be represented by a signed integer of the given size, err.Err = ErrRange.
http://golang.org/pkg/strconv/#ParseInt
type int int
int is a signed integer type that is at least 32 bits in size. It is a distinct type, however, and not an alias for, say, int32.
http://golang.org/pkg/builtin/#int
So int could be bigger than 32 bit in the future or on some systems like int in C.
I guess on some systems int64 might be faster than int32 because that system only works with 64-bit integers.
Here is an example of an error when bitSize is 8:
http://play.golang.org/p/_osjMqL6Nj
package main
import (
"fmt"
"strconv"
)
func main() {
i, err := strconv.ParseInt("123456", 10, 8)
fmt.Println(i, err)
}
Package strconv
func ParseInt
func ParseInt(s string, base int, bitSize int) (i int64, err error)
ParseInt interprets a string s in the given base (2 to 36) and returns
the corresponding value i. If base == 0, the base is implied by the
string's prefix: base 16 for "0x", base 8 for "0", and base 10
otherwise.
The bitSize argument specifies the integer type that the result must
fit into. Bit sizes 0, 8, 16, 32, and 64 correspond to int, int8,
int16, int32, and int64.
The errors that ParseInt returns have concrete type *NumError and
include err.Num = s. If s is empty or contains invalid digits, err.Err
= ErrSyntax; if the value corresponding to s cannot be represented by a signed integer of the given size, err.Err = ErrRange.
ParseInt always returns an int64 value. Depending on bitSize, this value will fit into int, int8, int16, int32, or int64. If the value cannot be represented by a signed integer of the size given by bitSize, then err.Err = ErrRange.
The Go Programming Language Specification
Numeric types
The value of an n-bit integer is n bits wide and represented using
two's complement arithmetic.
int8 the set of all signed 8-bit integers (-128 to 127)
int16 the set of all signed 16-bit integers (-32768 to 32767)
int32 the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
There is also a set of predeclared numeric types with
implementation-specific sizes:
uint either 32 or 64 bits
int same size as uint
int is either 32 or 64 bits, depending on the implementation. Usually it's 32 bits for 32-bit compilers and 64 bits for 64-bit compilers.
To find out the size of an int or uint, use strconv.IntSize.
Package strconv
Constants
const IntSize = intSize
IntSize is the size in bits of an int or uint value.
For example,
package main
import (
"fmt"
"runtime"
"strconv"
)
func main() {
fmt.Println(runtime.Compiler, runtime.GOARCH, runtime.GOOS)
fmt.Println(strconv.IntSize)
}
Output:
gc amd64 linux
64
strconv.ParseInt and friends return 64-bit versions to keep the API clean and simple.
Else one would have to create separate versions for each possible return type. Or return interface{}, which would then have to go through a type assertion. None of which are ideal.
int64 is chosen, because it can hold any integer size up to, and including, the supported 64-bits. The bit size you pass into the function, ensures that the value is properly clamped to the correct range. So you can simply do a type conversion on the returned value, to turn it into whatever integer type you require.
As for the difference between int and int64, this is architecture-dependant. int is simply an alias for either a 32-bit or 64-bit integer, depending on the architecture you are compiling for.
For the discerning eye: The returned value is a signed integer. There is a separate strconv.ParseUint function for unsigned integers, which returns uint64 and follows the same reasoning as explained above.
For your purposes, strconv.Atoi() would be more convenient I think.
The other answers have been pretty exhaustive about explaining the int type, but I think a link to the Go language specification is merited here: http://golang.org/ref/spec#Numeric_types
An int is the default signed type in go: it takes 32 bits (4 bytes) on a 32-bit machine and 64 bits(8 bytes) on a 64-bit machine.
Reference- The way to go by Ivo Balbaert
In Go lang, each type is considered as separate data type which can not be used interchangeably with the base type. For example,
type CustomInt64 int64
In the above declaration, CustomInt64 and built-in int64 are two separate data types and cannot be used interchangeably.
The same is the case with int, int32, and int64, all of these are separate data types that can't be used interchangeably. Where int32 is 32 its integer type, int64 is 64 bits and the size of the generic int type is platform dependent. It is 32 bits wide on a 32-bit system and 64-bits wide on a 64-bit system. So we must be careful and specific while specifying generic data types like int, uint, and float. It may cause a problem somewhere in code and will crash application on a different platform.

Resources