2 Booleans andNumbers
2.3. Numeric Types
2.3.1. Integer Types
Go provides 11 separate integer types, five signed and five unsigned, plus an in-teger type for storing pointers—their names and values are shown in Table 2.5 (➤ 60). In addition, Go allows the use of byte as a synonym for the unsigned uint8type, and encourages the use ofruneas a synonym for theint32type when working with individual characters (i.e., Unicode code points). For most process-ing purposes the only integer type that we need isint. This is suitable for loop counters, array and slice indexes, and all general-purpose integer arithmetic; it is also normally the integer type that offers the fastest processing speeds. At the time of this writing, theinttype is represented by a signed 32-bit integer (even on 64-bit platforms), but is expected to change to 64-bit in a future Go version.
The other integer types that Go provides are needed when it comes to reading and writing integers outside the program—for example, from and to files or network connections. In such cases it is essential to know exactly how many bits must be read or written so that integers can be handled without corruption.
★A panic is an exception; see Chapter 1 (32 ➤ ) and §5.5, ➤ 212.
ptg7913109 Table 2.5 Go’s Integer Types and Ranges
Type Range
byte Synonym foruint8
int Theint32orint64range depending on the implementation
int8 [−128, 127]
int16 [−32768, 32767]
int32 [−2147 483648, 2147 483647]
int64 [−9 223372036 854 775808, 9 223372036 854 775807]
rune Synonym forint32
uint Theuint32oruint64range depending on the implementation
uint8 [0, 255]
uint16 [0, 65 535]
uint32 [0, 4 294 967 295]
uint64 [0, 18 446 744 073 709 551 615]
uintptr An unsigned integer capable of storing a pointer value (advanced) Table 2.6 Arithmetic Operators Applicable Only to Built-In Integer Types Syntax Description/result
^x The bitwise complement of x
x %= y Setsxto be the remainder of dividingxbyy; division by zero causes a runtime panic
x &= y Setsxto the bitwiseANDof xandy x |= y Setsxto the bitwiseORof xandy x ^= y Setsxto the bitwiseXORof xandy
x &^= y Setsxto the bitwise clear (AND NOT) ofxandy
x >>= u Setsxto the result of right-shifting itself by unsigned intushifts x <<= u Setsxto the result of left-shifting itself by unsigned intushifts x % y The remainder of dividingxbyy; division by zero causes a runtime
panic
x & y The bitwiseANDof xandy x | y The bitwiseORof xandy x ^ y The bitwiseXORof xandy
x &^ y The bitwise clear (AND NOT) of xandy
x << u The result of left-shiftingxby unsigned intushifts x >> u The result of right-shiftingxby unsigned intushifts
ptg7913109 A common practice is to store integers in memory using the int type, and to
convert to or from one of the explicitly signed and sized integer types when writing or reading integers. Thebyte(uint8) type is used for reading and writing raw bytes—for example, when handling UTF-8 encoded text. We saw the basics of reading and writing UTF-8 encoded text in the previous chapter’samericanise example (29 ➤ ), and will see how to read and write built-in and custom data in Chapter 8.
Go integers support all the arithmetic operations listed in Table 2.4 (59 ➤ ), and in addition they support all the arithmetic and bitwise operations listed in Table 2.6 (60 ➤ ). All of these operations have the expected standard behaviors, so they are not discussed further, especially since we will see plenty of examples throughout the book.
It is always safe to convert an integer of a smaller type to one of a larger type (e.g., from anint16to anint32); but downsizing an integer that is too big for the target type or converting a negative integer to an unsigned integer will silent-ly result in a truncated or otherwise unexpected value. In such cases it is best to use a custom downsizing function such as the one shown earlier (58 ➤ ). Of course, when attempting to downsize a literal (e.g.,int8(200)), the compiler will detect the problem and report an overflow error. Integers can also be converted to floating-point numbers using the standard Go syntax (e.g.,float64(integer)).
Go’s support for 64-bit integers makes it realistically possible to use scaled integers for precise calculations in some contexts. For example, computing the finances for a business usingint64s to represent millionths of a cent allows for calculations in the range of billions of dollars with sufficient accuracy for most purposes—especially if we are careful about divisions. And if we need to do financial calculations with perfect accuracy and avoid rounding errors we can use thebig.Rattype.
2.3.1.1. Big Integers
In some situations we need to perform perfectly accurate computations with whole numbers whose range exceeds even that of int64s anduint64s. In such cases we cannot use floating-point numbers because they are represented by ap-proximations. Fortunately, Go’s standard library provides two unlimited accura-cy integer types:big.Intfor integers andbig.Ratfor rationals (i.e., for numbers than can be represented as fractions such as23and 1.1496, but not irrationals like e orπ). These integer types can hold an arbitrary number of digits—providing only that the machine has sufficient memory—but are potentially a lot slower to process than built-in integers.
Since Go—like C and Java—does not support operator overloading, the methods provided forbig.Ints andbig.Rats have names—for example,Add()andMul(). In most cases the methods modify their receiver (i.e., the big integer they are called on), and also return their receiver as their result to support the chaining of
ptg7913109 operations. We won’t list all the functions and methods provided by themath/big
package since they can easily be looked up in the documentation and may have been added to since this was written; however, we will look at a representative example to get a flavor of howbig.Ints are used.
Using Go’sfloat64 type allows us to accurately compute to about 15 decimal digits—which is more than enough for most situations. However, if we want to compute to a large number of decimal places, say, tens or hundreds of places, as we might want to when computingπ, no built-in type is sufficient.
In 1706 John Machin developed a formula for calculating π to an arbitrary number of decimal places, and we can adapt this formula in conjunction with the Go standard library’sbig.Ints to computeπ to any number of decimal places.
The pure formula, and the arccot() function it relies on, are shown in Figure 2.1.
(No understanding of Machin’s formula is required to understand the use of the big.Intpackage introduced here.) Our implementation of the arccot() function accepts an additional argument to limit the precision of the calculation so that we don’t go beyond the number of digits required.
π = 4 × (4 × arccot(5) − arccot(239)) arccot(x) = 1x− 13 3x + 15
5x − 17 7x + … Figure 2.1 Machin’s formula
The entire program is less than 80 lines and is in the file pi_by_digits/pi_by_dig-its.go; here is itsmain()function.★
func main() {
places := handleCommandLine(1000) scaledPi := fmt.Sprint(π(places)) fmt.Printf("3.%s\n", scaledPi[1:]) }
The program assumes a default value of 1 000 decimal places, although the user can choose any number they like by entering a value on the command line. The handleCommandLine()function (not shown) returns the value it is passed or the number the user entered on the command line (if any, and if it is valid). The π()function returnsπ as abig.Intof value 314159…; we print this to a string, and then print the string on the console properly formatted so that the output appears as, say, 3.14159265358979323846264338327950288419716939937510 (here we have used a mere 50 digits).
★The implementation used here is based on http://en.literateprograms.org/Pi_with_Machin's_for-mula_(Python).
ptg7913109 func π(places int) *big.Int {
digits := big.NewInt(int64(places)) unity := big.NewInt(0)
ten := big.NewInt(10) exponent := big.NewInt(0)
unity.Exp(ten, exponent.Add(digits, ten), nil) ➊
pi := big.NewInt(4)
left := arccot(big.NewInt(5), unity) left.Mul(left, big.NewInt(4)) ➋
right := arccot(big.NewInt(239), unity) left.Sub(left, right)
pi.Mul(pi, left) ➌
return pi.Div(pi, big.NewInt(0).Exp(ten, ten, nil)) ➍
}
Theπ()function begins by computing a value for theunityvariable (10digits+10) which we use as a scale factor so that we can do all our calculations using inte-gers. The +10 adds an extra ten digits to those given by the user, to avoid round-ing errors. We then use Machin’s formula with our modifiedarccot()function (not shown) that takes theunityvariable as its second argument. Finally, we return the result divided by1010to reverse the effects of theunityscale factor.
To get the unity variable to hold the correct value we begin by creating four variables, all of type*big.Int(i.e., pointer tobig.Int; see §4.1,➤ 140). Theunity andexponentvariables are initialized to 0, thetenvariable to 10, and thedigits variable to the number of digits requested by the user. Theunitycomputation is performed in a single line (➊). Thebig.Int.Add()method adds 10 to the number of digits. Then the big.Int.Exp()method is used to raise 10 to the power of its second argument (digits+ 10). When used with a nil third argument—as here—big.Int.Exp(x, y, nil)performs the computationx ; with three non-y nil arguments,big.Int.Exp(x, y, z)computesx mod z. Notice that we did not needy to assign tounity; this is because mostbig.Intmethods modify their receiver as well as return it, so here,unityis modified to have the resultant value.
The rest of the computation follows a similar pattern. We set an initial value of pito 4 and then compute the inner left-hand part of Machin’s formula. We don’t need to assign toleftafter creating it (➋), since thebig.Int.Mul()method stores the result in its receiver (i.e., in this case in variableleft) as well as returning the result (which we can safely ignore). Next we compute the inner right-hand part of the formula and subtract therightfrom theleft(leaving the result in left). Now we multiplypi(of value 4) byleft(which holds the result of Machin’s formula). This produces the result but scaled byunity. So in the final line (➍) we reverse the scaling by dividing the result (inpi) by10 .10
Using thebig.Inttype takes some care since most methods modify their receiver (this is done for efficiency to save creating lots of temporarybig.Ints). Compare
ptg7913109 the line where we perform the computationpi×leftwith the result being stored
inpi(63 ➤ , ➌) to the line where we computepi÷10 and return the result (6310 ➤ ,
➍)—not caring that the value ofpihas been overwritten by the result.
Wherever possible it is best to use plainints, falling back toint64s if theint range isn’t sufficient, or usingfloat32s orfloat64s if the fact that they are ap-proximations is not a concern. However, if computations of perfect accuracy are required and we are prepared to pay the price in memory use and processing overhead, then we can usebig.Ints orbig.Rats—the latter particularly useful for financial calculations—scaling if necessary as we did here, when floating-point computations are required.