After reading lesson 7, you’ll be able to
Go offers 10 different types for whole numbers, collectively called integers. Integers don’t suffer from the accuracy issues of floating-point types, but they can’t store fractional numbers and they have a limited range. The integer type you choose will depend on the range of values needed for a given situation.
How many numbers can you represent with two tokens?
If the tokens are individually identifiable by position, there are four possible permutations. Both tokens, neither token, one token, or the other token. You could represent four numbers.
Computers are based on bits. A bit can either be off or on—0 or 1. Eight bits can represent 256 different values. How many bits would it take to represent the number 4,000,000,000?
Five integer types are signed, meaning they can represent both positive and negative whole numbers. The most common integer type is a signed integer abbreviated int:
var year int = 2018
The other five integer types are unsigned, meaning they’re for positive numbers only. The abbreviation for unsigned integer is uint:
var month uint = 2
When using type inference, Go will always pick the int type for a literal whole number. The following three lines are equivalent:
year := 2018 var year = 2018 var year int = 2018
As with the floating-point types in lesson 6, it’s preferable to not specify the int type when it can be inferred.
If your glass is half full, which integer type would you use to represent the number of milliliters of water in your glass?
Integers, whether signed or unsigned, come in a variety of sizes. The size affects their minimum and maximum values and how much memory they consume. There are eight architecture-independent types suffixed with the number of bits they need, as summarized in table 7.1.
Range |
Storage |
|
---|---|---|
int8 | –128 to 127 | 8-bit (one byte) |
uint8 | 0 to 255 | |
int16 | –32,768 to 32,767 | 16-bit (two bytes) |
uint16 | 0 to 65535 | |
int32 | –2,147,483,648 to 2,147,483,647 | 32-bit (four bytes) |
uint32 | 0 to 4,294,967,295 | |
int64 | –9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | 64-bit (eight bytes) |
uint64 | 0 to 18,446,744,073,709,551,615 |
That’s a lot of types to choose from! Later in this lesson, we’ll show some examples where specific integer types make sense, along with what happens if your program exceeds the available range.
There are two integer types not listed in table 7.1. The int and uint types are optimal for the target device. The Go Playground, Raspberry Pi 2, and older mobile phones provide a 32-bit environment where both int and uint are 32-bit values. Any recent computer will provide a 64-bit environment where int and uint will be 64-bit values.
If you’re operating on numbers larger than two billion, and if the code could be run on older 32-bit hardware, be sure to use int64 or uint64 instead of int and uint.
Although it’s tempting to think of int as an int32 on some devices and an int64 on other devices, these are three distinct types. The int type isn’t an alias for another type.
Which integer types support the value –20,151,021?
If you’re ever curious about which type the Go compiler inferred, the Printf function provides the %T format verb to display a variable’s type, as shown in the following listing.
year := 2018 fmt.Printf("Type %T for %v ", year, year) 1
Instead of repeating the variable twice, you can tell Printf to use the first argument [1] for the second format verb:
days := 365.2425 fmt.Printf("Type %T for %[1]v ", days) 1
Which types does Go infer for text between quotes, a whole number, a real number, and the word true (without quotes)? Expand listing 7.1 to declare variables with different values and run the program to see which types Go infers.
a := "text" fmt.Printf("Type %T for %[1]v ", a) 1 b := 42 fmt.Printf("Type %T for %[1]v ", b) 2 c := 3.14 fmt.Printf("Type %T for %[1]v ", c) 3 d := true fmt.Printf("Type %T for %[1]v ", d) 4
- 1 Prints Type string for text
- 2 Prints Type int for 42
- 3 Prints Type float64 for 3.14
- 4 Prints Type bool for true
In Cascading Style Sheets (CSS), colors on screen are specified as a red, green, blue triplet, each with a range of 0–255. It’s the perfect situation to use the uint8 type, an 8-bit unsigned integer able to represent values from 0–255:
var red, green, blue uint8 = 0, 141, 213
Here are the benefits of uint8 instead of a regular int for this case:
Colors in CSS are specified in hexadecimal instead of decimal. Hexadecimal represents numbers using 6 (hexa-) more digits than decimal’s 10. The first 10 digits are the same 0 through 9, but they’re followed by A through F. A in hexadecimal is equivalent to 10 in decimal, B to 11, and so on up to F, which is 15.
Decimal is a great system for 10-fingered organisms, but hexadecimal is better suited to computers. A single hexadecimal digit consumes four bits, called a nibble. Two hexadecimal digits require precisely eight bits, or one byte, making hexadecimal a convenient way to specify values for a uint8.
The following table shows some hexadecimal numbers and their equivalent numbers in decimal.
Hexadecimal |
Decimal |
---|---|
A | 10 |
F | 15 |
10 | 16 |
FF | 255 |
To distinguish between decimal and hexadecimal, Go requires a 0x prefix for hexadecimal. These two lines of code are equivalent:
var red, green, blue uint8 = 0, 141, 213 var red, green, blue uint8 = 0x00, 0x8d, 0xd5
To display numbers in hexadecimal, you can use the %x or %X format verbs with Printf:
fmt.Printf("%x %x %x", red, green, blue) 1
To output a color that would feel at home in a .css file, the hexadecimal values need some padding. As with the %v and %f format verbs, you can specify a minimum number of digits (2) and zero padding with %02x:
fmt.Printf("color: #%02x%02x%02x;", red, green, blue) 1
Integers are free of the rounding errors that make floating-point inaccurate, but all integer types have a different problem: a limited range. When that range is exceeded, integer types in Go wrap around.
An 8-bit unsigned integer (uint8) has a range of 0–255. Incrementing beyond 255 will wrap back to 0. The following listing increments both signed and unsigned 8-bit integers, causing them to wrap around.
var red uint8 = 255 red++ fmt.Println(red) 1 var number int8 = 127 number++ fmt.Println(number) 2
To understand why integers wrap, take a look at the bits. The %b format verb will show you the bits for an integer value. Like other format verbs, %b can be zero padded to a minimum length, as you can see in this listing.
var green uint8 = 3 fmt.Printf("%08b ", green) 1 green++ fmt.Printf("%08b ", green) 2
Use the Go Playground to experiment with the wrapping behavior of integers:
In listing 7.2, the code increments red and number by 1. What happens when you add a larger number to either variable?
Go the other way. What happens if you decrement red when it’s 0 or number when it’s equal to –128?
Wrapping applies to 16-bit, 32-bit, and 64-bit integers too. What happens if you declare a uint16 assigned to the maximum value of 65535 and then increment it by 1?
// add a number larger than one var red uint8 = 255 red += 2 fmt.Println(red) 1 var number int8 = 127 number += 3 fmt.Println(number) 2
// wrap the other way red = 0 red-- fmt.Println(red) 3 number = -128 number-- fmt.Println(number) 4
// wrapping with a 16-bit unsigned integer var green uint16 = 65535 green++ fmt.Println(green) 5
- 1 Prints 1
- 2 Prints -126
- 3 Prints 255
- 4 Prints 127
- 5 Prints 0
The math package defines math.MaxUint16 as 65535 and similar min/max constants for each architecture-independent integer type. Remember that int and uint could be either 32-bit or 64-bit, depending on the underlying hardware.
In listing 7.3, incrementing green causes the 1 to be carried, leaving zeros to the right. The result is 00000100 in binary, or 4 in decimal, as shown in figure 7.1.
The same thing happens when incrementing 255, with one critical difference: with only eight bits available, the 1 that’s carried has nowhere to go, so the value of blue is left as 0, as shown in the next listing and illustrated in figure 7.2.
var blue uint8 = 255 fmt.Printf("%08b ", blue) 1 blue++ fmt.Printf("%08b ", blue) 2
Wrapping may be what you want in some situations, but not always. The simplest way to avoid wrapping is to use an integer type large enough to hold the values you expect to store.
Which format verb lets you look at the bits?
On Unix-based operating systems, time is represented as the number of seconds since January 1, 1970 UTC (Coordinated Universal Time). In the year 2038, the number of seconds since January 1, 1970 will exceed two billion, the capacity of an int32.
Thankfully, int64 can support dates well beyond 2038. This is a situation where int32 or int simply won’t do. Only the int64 and uint64 integer types are able to store numbers well beyond two billion on all platforms.
The code in listing 7.5 uses the Unix function from the time package. It accepts two int64 parameters, corresponding to the number of seconds and the number of nanoseconds since January 1, 1970. Using a suitably large value (over 12 billion) demonstrates that dates beyond 2038 work just fine in Go.
package main import ( "fmt" "time" ) func main() { future := time.Unix(12622780800, 0) fmt.Println(future) 1 }
Which integer type should you choose to avoid wrapping?
Let’s see if you got this...
Write a new piggy bank program that uses integers to track the number of cents rather than dollars. Randomly place nickels (5¢), dimes (10¢), and quarters (25¢) into an empty piggy bank until it contains at least $20.
Display the running balance of the piggy bank after each deposit in dollars (for example, $1.05).
If you need to find the remainder of dividing two numbers, use modulus (%).