Lesson 7. Whole numbers

After reading lesson 7, you’ll be able to

  • Use 10 types of whole numbers
  • Choose the right type
  • Use hexadecimal and binary representations

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.

Consider this

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?

7.1. Declaring integer variables

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
Tip

As with the floating-point types in lesson 6, it’s preferable to not specify the int type when it can be inferred.

Quick check 7.1

Q1:

If your glass is half full, which integer type would you use to represent the number of milliliters of water in your glass?

QC 7.1 answer

1:

The uint type (unsigned integer) is for positive integers only.

 

7.1.1. Integer types for every occasion

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.

Table 7.1. Architecture-independent integer types

Type

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.

Tip

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.

Note

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.

Quick check 7.2

Q1:

Which integer types support the value –20,151,021?

QC 7.2 answer

1:

The int32, int64, and int types would work.

 

7.1.2. Knowing your type

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.

Listing 7.1. Inspect a variable’s type: inspect.go
year := 2018
fmt.Printf("Type %T for %v
", year, year)          1

  • 1 Prints Type int for 2018

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

  • 1 Prints Type float64 for 365.2425
Quick check 7.3

Q1:

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.

QC 7.3 answer

1:

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

 

7.2. The uint8 type for 8-bit colors

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:

  • With a uint8, the variables are restricted to the range of valid values, eliminating over four billion incorrect possibilities compared to a 32-bit integer.
  • If there are a lot of colors to store sequentially, such as in an uncompressed image, you could achieve considerable memory savings by using 8-bit integers.
Hexadecimal in Go

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 and decimal values

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

  • 1 Prints 0 8d d5

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

  • 1 Prints color: #008dd5;

Quick check 7.4

Q1:

How many bytes are required to store a value of type uint8?

QC 7.4 answer

1:

An 8-bit (unsigned) integer only requires a single byte.

 

7.3. Integers wrap around

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.

Listing 7.2. Integers wrap around: integers-wrap.go
var red uint8 = 255
red++
fmt.Println(red)               1

var number int8 = 127
number++
fmt.Println(number)            2

  • 1 Prints 0
  • 2 Prints –128

7.3.1. Looking at the bits

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.

Listing 7.3. Display the bits: bits.go
var green uint8 = 3
fmt.Printf("%08b
", green)             1
green++
fmt.Printf("%08b
", green)             2

  • 1 Prints 00000011
  • 2 Prints 00000100
Quick check 7.5

Use the Go Playground to experiment with the wrapping behavior of integers:

1

In listing 7.2, the code increments red and number by 1. What happens when you add a larger number to either variable?

2

Go the other way. What happens if you decrement red when it’s 0 or number when it’s equal to –128?

3

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?

QC 7.5 answer

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

2

// wrap the other way
red = 0
red--
fmt.Println(red)               3

number = -128
number--
fmt.Println(number)            4

3

// 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

 

Tip

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.

Figure 7.1. Carrying the 1 in binary addition

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.

Listing 7.4. The bits when integers wrap: bits-wrap.go
var blue uint8 = 255
fmt.Printf("%08b
", blue)          1
blue++
fmt.Printf("%08b
", blue)          2

  • 1 Prints 11111111
  • 2 Prints 00000000
Figure 7.2. Where should the carry go?

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.

Quick check 7.6

Q1:

Which format verb lets you look at the bits?

QC 7.6 answer

1:

The %b format verb outputs integers in base 2.

 

7.3.2. Avoid wrapping around time

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.

Listing 7.5. 64-bit integers: time.go
package main

import (
    "fmt"
    "time"
)

func main() {
    future := time.Unix(12622780800, 0)
    fmt.Println(future)                      1
}

  • 1 Prints 2370-01-01 00:00:00 +0000 UTC in the Go Playground
Quick check 7.7

Q1:

Which integer type should you choose to avoid wrapping?

QC 7.7 answer

1:

Use an integer type large enough to hold the values you expect to store.

 

Summary

  • The most common integer types are int and uint, but some situations call for smaller or larger types.
  • Integer types need to be chosen carefully to avoid wrapping around, unless wrapping is what you want.
  • You looked at 10 more of the 15 numeric types in Go (int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64).

Let’s see if you got this...

Experiment: piggy.go

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).

Tip

If you need to find the remainder of dividing two numbers, use modulus (%).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset