Introduction
There once was a wise servant who saved the life of a prince.
The king promised to pay whatever the servant could dream up.
Knowing that the king loved chess, the servant told the king he would like to have grains of wheat.
One grain on the first square of a chessboard, with the number of grains doubling on each successive square.
Instructions
Instructions
Calculate the number of grains of wheat on a chessboard.
A chessboard has 64 squares.
Square 1 has one grain, square 2 has two grains, square 3 has four grains, and so on, doubling each time.
Write code that calculates:
- the number of grains on a given square
- the total number of grains on the chessboard
Dig Deeper
math.Pow and big exponentiation
math.Pow() and big exponentiation
package grains
import (
"errors"
"math"
"math/big"
)
// Square returns the number of grains on the square for the number passed in.
func Square(num int) (uint64, error) {
if num < 1 || num > 64 {
return 0, errors.New("square number must be 1 through 64")
}
return uint64(math.Pow(2, float64(num-1))), nil
}
// Total return the sum of the squares from 1 to 64, which is the number of squares on a chess board.
func Total() uint64 {
var num, exp = big.NewInt(2), big.NewInt(64)
return num.Exp(num, exp, nil).Sub(num, big.NewInt(1)).Uint64()
}
Go uses math.Pow() to raise a number by a certain exponent.
big.Int.Exp() can also be used to raise a number by an exponent to work with a big.Int.
Exponentiation is nicely suited to the problem, since we start with one grain and keep doubling the number of grains on each successive square.
1 grain is math.Pow(2, 0), 2 grains is math.Pow(2, 1), 4 is math.Pow(2, 2), and so on.
So, to get the right exponent, we subtract 1 from the square number num.
The easiest way to get total is to get the value for an imaginary 65th square,
and then subtract 1 from it.
To understand how that works, consider a board that has only two squares.
If we could grow the board to three squares, then we could get the number of grains on the imaginary third square,
which would be 4.
You could then subtract 4 by 1 to get 3, which is the number of grains on the first square (1) and the second square (2).
Remembering that the exponent must be one less than the square you want,
you would want to call math.Pow(2, 64) to get the number of grains on the imaginary 65th square.
However, that won’t work, because the resulting number is too large.
So, you can use big.NewInt to create the numbers to pass to Exp().
The output from Exp() is then used to call the Sub() function for subtracting that value by 1,
and its output is used for calling Uint64(),
which converts the big Int value to the uint64 value returned for all 64 squares.
Bit-shifting
Bit-shifting
// Package grains is a small library for determing the squares of a number.
package grains
import (
"errors"
)
// Square returns the number of grains on the square for the number passed in.
func Square(num int) (uint64, error) {
if num < 1 || num > 64 {
return 0, errors.New("square number must be 1 through 64")
}
return 1 << (num - 1), nil
}
// Total return the sum of the squares from 1 to 64, which is the number of squares on a chess board.
func Total() uint64 {
return 1<<64 - 1
}
Instead of using math to calculate the number of grains on a square, you can set a bit in the correct position of a uint64.
To understand how this works, consider just two squares that are represented in binary bits as 00.
You use the left-shift operator to set 1 at the position needed to make the correct decimal value.
- To set the one grain on Square One you shift
1 for 0 positions to the left.
So, if n is 1 for square One, you subtract n by 1 to get 0, which will not move it any positions to the left.
The result is binary 01, which is decimal 1.
- To set the two grains on Square Two you shift
1 for 1 position to the left.
So, if n is 2 for square Two, you subtract n by 1 to get 1, which will move it 1 position to the left.
The result is binary 10, which is decimal 2.
| Square | Shift Left By | Binary Value | Decimal Value |
|---|
| 1 | 0 | 0001 | 1 |
| 2 | 1 | 0010 | 2 |
| 3 | 2 | 0100 | 4 |
| 4 | 3 | 1000 | 8 |
For total we want all of the 64 bits set to 1 to get the sum of grains on all sixty-four squares.
The easy way to do this is to set the 65th bit to 1 and then subtract 1.
To go back to our two-square example, if we can grow to three squares, then we can shift 1 two positions to the left for binary 100,
which is decimal 4.
By subtracting 1 we get 3, which is the total amount of grains on the two squares.
| Square | Binary Value | Decimal Value |
|---|
| 3 | 0100 | 4 |
| Square | Sum Binary Value | Sum Decimal Value |
|---|
| 1 | 0001 | 1 |
| 2 | 0011 | 3 |
A shortcut would be to use the [`math.MaxUint64`](https://cs.opensource.google/go/go/+/refs/tags/go1.19.4:src/math/const.go;l=39)
constant, which is defined as `1<<64 - 1`.
Source: Exercism go/grains