Using numbers and performing mathematical operations on them is a common practice in any programming language. Numerical data types in Go represent many types of numeric data, ranging from common numbers to more meaningful representations such as currency, screen pixel size, points, geographical locations, time, color codes, and so forth. Therefore to effectively deal with numbers, it is important to understand the basics of how numbers function in Go. This article shows how to work with two of the most common numeric data types: *integer* and *float*, with a quick detour covering the concept of numbers in computing.

** Read:** Getting Started with Go Programming

## How Numbers Work in Programming

The *base* and *index* are the two important properties of a number. The index – or power (or exponent) – typically denotes how many times the number is to be multiplied. For example: (2 x 2 x 2 = 8) and (8 = 23 ), therefore (superscript) 3 in 23 is called the index.

We usually do not mention or write the base of a number because most of the time, we deal with only one type of number system called decimal numbers, hence the type is obvious. But as we deal with more than one type of number system, it is not apparently clear which number system we are talking about. In computing, four types of number systems are common: *decimal*, *binary*, *octal*, and *hexadecimal*. Meanwhile, we human beings are accustomed to only one type: *decimal*.

A decimal number – or floating point number – is represented by 0 – 9, 10 single-digit numbers. Other large digits are nothing but combinations of these 0-9 digits. Similarly, binary has two digits – 0 and 1. Octal has eight numbers, 0-7. Hexadecimal has 16 numbers. Since 10, 11, and so on are double-digit numbers, hexadecimal uses letters A to represent 10, B for 11, until we finally reach F for 15. The first 16 numbers are, therefore, from 0 to F, which in decimal means 0-15.

Now, typically the base of a number is written as subscript: (123)10 means a decimal number, (45)8 means an octal number, (1101)2 means a binary number, and finally (A920CD)16 means a hexadecimal number. Any numbers can be converted from one type to another, but that is beyond the scope of this section.

Two of the most commonly used numeric data types of values used in computing are *integers* and *floats*. Below we will quickly define what they mean from the concept of numbers in mathematics:

- The set of all positive counting numbers {1, 2, 3, …} is known as
*natural numbers*. - If we add zero to the set of natural numbers, {0, 1, 2, 3, …}, they are called
*whole numbers*.

*Integers*are a set of all whole numbers plus all the negative natural numbers – {…, -2, -1, 0, 1, 2, 3, …}. An important characteristic of*integers*is that they are precise numbers and have no rounding off errors.

Another type of number is called *floats* or *floating point* numbers, which represent varied precision such as 3.14, 3.147, or -234.56, etc. This is also commonly called *decimal* numbers.

- Mathematically, they are p/q numbers where q is not equal to 0 and called
*rational numbers*. - As opposed to rational numbers, there are
*irrational numbers*, which have endless non-repeating digits after the decimal point, such as the value of PI or*sqrt(3)*or*sqrt(2)*. - The
*rational*and*irrational numbers*together form the set of*real numbers*.

In computer programming, it is sufficient to have two data types: *integer* for all positive and negative precise numbers and *floats* to represent all positive and negative imprecise or fractional numbers or real numbers.

## How Do Computers Store Data?

Computer memory uses bit patterns to store data. It is entirely up to the programmer to decide what each bit pattern means in the context of the program. It may be a number, a character, or any other object. This interpretation of a binary pattern is called *data representation* or *encoding*. Therefore, numbers are also a piece of data represented by a fixed number of bits.

An n-bit storage can represent 2n distinct entities. For example a 3-bit storage can store eight distinct binary patterns: 000, 001, 010, 011, 100, 101, 110, 111. This means we represent numbers 0 to 7 or characters *‘A’* to *‘H’* or any other eight distinct things.

An integer is typically represented as 8-bit, 16-bit, 32-bit, or 64-bit values. This choice imposes a constraint on the range of values that data types can represent. Besides bit-length, the representation scheme, such as signed or unsigned integers, has an impact on the range of values. For example, an 8-bit unsigned integer has a range of 0 to 255, while an 8-bit signed integer has a range of -128 to 127. Note that both signed and unsigned integers, in this case, represent 256 distinct numbers.

Floating point numbers, although represented by bit patterns like everything else, are a bit tricky. Let’s say we use 32 bits to represent a real number as well as an integer. An integer being equally spaced (if we put in a number line), the maximum value that can be stored is 232. However, there can be infinite floating-point numbers between two integers (hence the concept of precision is important for floats. Read IEE754 for more details).

A minor change in the precision value, such as from 2.34567 to 2.34566, may not be representable because the spacing between the represented value at the lower end of the number line towards 0 is very small, and it increases or decreases as we move between the positive or negative direction in the number line. In cases of integer and float, the 32 bits store is used to hold numbers, but it is just that the numbers are unequally spaced in floating-point, which allows its range to be more even with limited precision than that of integers represented by 32 bits. However, the true nature of *real numbers* cannot be replicated in digital computers. Floating-point representation is our best shot at mimicking this.

**Learn all about Working with Strings in Go.**

## What are Integer Types in Go?

Two of the most commonly used numeric data types in Go are integers and floats. Go represents integers of four distinct sizes – 8, 16, 32, and 64 bits. Go integers:

- Are represented as
*int8[-27,27-1]*,*int16[-215, 215-1]*,*int32[−231, 231-1]*and*int64[−263, 263-1]*respectively. They are all signed integers. - Have corresponding unsigned versions:
*uint8[0, 28-1,]*,*uint16[0, 216-1]*,*uint32[0, 232-1]*and*uint64[0, 264-1]*. - If we do not specify the bit-size and simply write
*int*or*uint*, then they can either mean 32 or 64 bits integers or unsigned-integers, respectively, depending upon the compiler. - There is another type called
*rune*, which is an alias of*int32*, conventionally used to represent Unicode code points. - Similarly, the
*byte*data type is an alias of*uint8*. - There is an unsigned integer type called
*uintptr*whose bit value is unspecified. It is used to hold bits of pointer value and is typically used for low-level programming. - A variable of a type that cannot store a higher number is silently truncated.

The signed numbers are represented in 2’s complement form, where higher-order bits are reserved as sign-bit. Unsigned integers use a full range of bits.

## What are Floating Point Types in Go?

There are two types of floating-point numbers represented as *float32* and *float64*.

- In the
*math*package, there is constant called*math.MaxFloat32*which denotes the largest*float32*value that the variable of this type can hold. - Similarly, there is another constant called
*math.float64*for 64-bit size. - A
*float32*value provides approximate precision up to 6 decimal places. - A
*float64*value provides approximate precision up to 15 decimal places.

## Operators and Order of Precedence in Go

According to the decreasing order of precedence, math, logic, and comparison related operators in Go can be arranged as follows:

- Level 5:
**(Mul), /(Div)*,*%(Modulo)*,*<<(Left-shift)*,*>>(Right-shift)*,*&(bitwise AND)*,*&^(bitwise clear AND NOT)* - Level 4:
*+(Add)*,*-(Sub)*,*|(bitwise OR)*,*^(bitwise XOR)* - Level 3:
*==(Equal to)*,*!=(Not equal to)*,*<(Less than)*,*<=(Less equal)*,*>(Greater than)*,*>=(Greater equal)* - Level 2:
*&&(logical AND)* - Level 1:
*||(Logical OR)*

Operators at the same level have left-to-right associativity. Therefore, appropriate parenthesis must be used for clarity. Note that the *%(Modulo)* operator applies only to integers, and when used with negative integers such as -7%3, the sign of the remainder is the same as that of the sign of the dividend. The operators in levels (5 to 1) have decreasing order of precedence, while operators in the same level have the same order of precedence.

However, if an expression has multiple operators of the same precedence, the order of calculation is from left to right. Therefore, an expression (1+2-3+4) results in 4 and not -4. Be cautious while applying the familiar *BODMAS* or *PEMDAS* rule. There can be incorrect interpretations due to them. Of course, one can use parenthesis properly to alleviate the confusion.

## Mathematical Operators in Go

The addition and subtraction operators in Go are similar to those in mathematics. Let’s run through a few quick examples of using mathematical operators in Go:

package main import "fmt" func main() { intX := 20 intY := 50 fmt.Println(intX + intY) //also, fmt.Println(intX - intY) intY = -10 //integers can be negative, using unary operator fmt.Println(intX + intY) //also, fmt.Println(intX - intY) floatX := 5.67 floatY := 2.56 //To properly format the output to 2 decimal place, we can use fmt.Printf fmt.Printf("%.2f", floatX+floatY) // fmt.Printf("%.2f", floatX+floatY) floatY = -1.24 //floats can be negative too fmt.Printf("\n%.2f", floatX+floatY) //fmt.Printf("%.2f", floatX+floatY) }

The above code would result in the following output if run in your integrated development environment (IDE) or code editor:

70 10 8.23 4.43

Now, what happens if we try to calculate using mismatched data types as follows:

fmt.Printf("\n%.2f", intX+floatY) //error fmt.Printf("\n%d", intX+floatY) //error

This is not allowed in Go. However, we may cast the variable to its corresponding type and do the calculation without any error:

fmt.Printf("\n%.2f", float64(intX)+floatY) //casting in to float fmt.Printf("\n%d", intX+int(floatY)) //casting float to int

But note that *typecasting* a variable to another type may result in lost information. For example, casting a *float* to an *int* variable rounds it off to its nearest integer. The fractional values are truncated. So be cautious while typecasting one variable to another type.

### Multiplication and Division Operators in Go

Multiplication and division operators in Go are also similar to their mathematical equivalents. Here is a quick example:

package main import "fmt" func main() { intX := 56 intY := 12 fmt.Println(intX * intY) fmt.Println(intX / intY) floatX := 5.67 floatY := 2.56 fmt.Printf("%.2f", floatX*floatY) fmt.Printf("\n%.2f", floatX/floatY) }

Running this code in your IDE or code editor results in:

672 4 14.52 2.21

Note that the division operator / in Go performs floor division in the case of an integer, which means the fractional part of the division result (quotient) is rounded off to its nearest floor (integer) value. A division typically has two integer parts as its result: quotient and remainder. The division */*, operator returns the quotient while the *modulo %*, operator returns the remainder after division. Check out the following example:

package main import "fmt" func main() { intX := 45 intY := 7 fmt.Printf("%d / %d = %d (Quotient)\n", intX, intY, (intX / intY)) fmt.Printf("%d %% %d = %d (Remainder)\n", intX, intY, (intX % intY)) }

The output:

45 / 7 = 6 (Quotient) 45 % 7 = 3 (Remainder)

### The Mod Function in Go

Can we do modulus with float data types? The answer is yes, but not with the *modulus %* operator. We have to use the *Mod* function from the *math* package. Here is a quick example:

package main import ( "fmt" "math" ) func main() { floatX := 57.0 floatY := 7.0 fmt.Printf("%.2f Mod %.2f = %.2f\n", floatX, floatY, math.Mod(floatX, floatY)) }

The output would be:

57.00 Mod 7.00 = 1.00

Apart from these, there are *assignment operators* = and *shorthand operators* written as *x+=5*, which essentially means *x=x+5*. The shorthand operator can be applied to all level 5 and level 4 operators such as x*=5(means, x=x*5), x&^=3(means, x=x&^3), x^=6(means, x=x^6), etc.

**Learn about other Methods in Go.**

## Bitwise Operators in Go

The *bitwise operators* are used for bit calculation. Here is a quick example of how to use them:

package main import ( "fmt" ) func main() { var x uint8 = 10<<2 | 9<<3 var y uint8 = 16<<3 | 8<<2 fmt.Printf("x = %08b\n", x) fmt.Printf("y = %08b\n", y) fmt.Printf("x&y = %08b\n", x&y) //AND fmt.Printf("x|y = %08b\n", x|y) //OR fmt.Printf("x^y = %08b\n", x^y) //XOR fmt.Printf("x&^y = %08b\n", x&^y) //AND NOT fmt.Printf("x<<2 = %08b\n", x<<2) //Left-shift fmt.Printf("x>>1 = %08b\n", x>>1) //Right-shift }

The output:

x = 01101000 y = 10100000 x&y = 00100000 x|y = 11101000 x^y = 11001000 x&^y = 01001000 x<<2 = 10100000 x>>1 = 00110100

## Working Big Numbers in Python

On some occasions, we may need to perform mathematical operations on numbers that exceed the limit of *int64/uint64* *float64* type. The Go standard library provides three unlimited accuracy numeric types for integers called *big.Int* for signed integers, *big.Rat* for rational numbers and *big.Float* for floating-point numbers.

The *big.Rat* (rational) only can store rational numbers. Irrational numbers such as the value of *PI*, *e*, or *sqrt(3)*, etc., cannot be stored in *big.Rat*. *Big* numbers can be of any length (limited only by memory), and their calculation is a lot slower than the built-in primitive types. For mathematical calculations, we cannot use the usual operators. Instead, we have to use methods such as *Add()*, *Mul()*, and so forth. Here is a quick and very short example showing how to use *Big* numbers in Go:

package main import ( "fmt" "math/big" ) func main() { bigInt1 := big.NewInt(int64(20)) //NewInt(x) returns an *Int set to the value of the int64 argument x bigInt2 := big.NewInt(int64(30)) bigFloat1 := big.NewFloat(8.3983) //NewFloat(x) returns a *Float initialized to the float64 argument x bigFloat2 := big.NewFloat(7.98327643) bigRat1 := big.NewRat(22, 7) // NewRat(x, y) returns a *Rat set to the fraction // x/y where x and y are int64 values bigRat2 := big.NewRat(67, 20) fmt.Println(bigInt1.Add(bigInt1, bigInt2)) fmt.Println(bigFloat1.Mul(bigFloat1, bigFloat2)) fmt.Println(bigRat1.Sub(bigRat1, bigRat2)) }

The output of this code if you type it in your code editor and run it would be:

50 67.04595044206901 -29/140

## Final Thoughts on Mathematical Operators in Go

Here we have given a quick tour on the application of mathematical operators and numeric data types in Go, focusing on integer and float types only. Go provides a lot more in the standard library to deal with numbers and the mathematical part of its use. We often do not deal with numbers that exceed 64-bit space. Integers and floats suffice most of our needs in Go, unless we are performing some scientific calculations.

Scientific calculation sometimes requires big numbers that must be exact in their calculation. Go has a provision for that too in the standard library. The use of big numbers may be rare, but they have their uses. On most occasions, however, primitive data types should be made the basis of all calculations because they are a lot faster. Also, remember that for efficiency’s sake, integers are always better than floating-point numbers.