Skip to content

Integer Types

C, C++, and Rust have a wide range of integer types that represent 8-bit, 16-bit, 32-bit, 64-bit, and even 128-bit integers, both in signed and unsigned versions. What they lack is an integer type that supports arbitrarily large numbers, numbers that may take 200 or even thousands of bits to represent. Haskell offers less fine-grained control over the number of bits used to represent fixed-width integers.1 There's the Int type, which is whatever Haskell considers the system integer type, usually 64 bits on modern systems. This is the most commonly used integer type in Haskell. The other integer type Haskell offers is Integer. This is an integer type that can represent arbitrarily large numbers.

The different choice of integer types offered by C, C++, Rust on the one hand and Haskell on the other hand is a reflection of the types of problems these languages are designed to solve. C, C++, and Rust are systems programming languages, which need to provide precise control over the machine-level representations of data. They also do not offer any data types built into the language that cannot be represented directly at the machine level and therefore incur overhead to manipulate. The Integer type is such a type because integers of arbitrary size can only be represented as arrays of machine integers (words) and arithmetic operations need to be implemented manually by appropriately combining machine-level instructions to pairs of words in these arrays of words. Haskell is focused on supporting powerful abstractions for programming high-level applications elegantly and with little effort. This makes it less necessary to precisely control the number of bits used to represent a fixed-width integer, and using the Integer type whenever you have no idea how large the numbers in your program can get relieves you of any worries that your code may misbehave due to number overflow.

The operations you can perform on integers are the ones you'd expect:

Addition

GHCi
>>> 5 + 3
8

Subtraction

GHCi
>>> 5 - 3
2

Multiplication

GHCi
>>> 5 * 3
15

Exponentiation

GHCi
>>> 5 ^ 3
125

Division and Remainder

The division operation is called div (divide) instead of (/)2 because (/) is floating-point division, discussed shortly. Since div's name is made up of letters, you need to enclose it in backticks to use it in infix notation:

GHCi
>>> 5 `div` 3
1

If we can perform integer division, we should also be able to take the remainder of dividing one number by another. This operator is called mod (modulo):

GHCi
>>> 5 `mod` 3
2

There are two other operators for integer division and remainder: quot (quotient) and rem (remainder). They differ from div and mod in how they round their results when operating on negative numbers. There are three problems with these two operators:

  • Their exact behaviour is not part of the Haskell specification.
  • The description of their difference to div and mod on Hackage makes no sense to me.
  • Even observing their behaviour and coming up with a sensible and simple description of their precise behaviour seemed difficult.

Consequently, I almost never use quot and rem. Who likes to call functions whose behaviour they don't understand and which may correspondingly make their code compute the wrong thing?

It is common enough to want to perform integer division and compute the remainder of the division at the same time to have a single operator for both. This operator comes in two versions again: divMod and quotRem. It returns a pair as a result. The first component is the same result that div (or quot) would have produced. The second component is the result that mod (or rem) would have produced:

GHCi
>>> 5 `divMod` 3
(1, 2)

Comparison Operators

In addition to maniplating them using basic arithmetic operators, numbers can be compared. You can test whether two numbers are equal:

GHCi
>>> 3 == 5
False
>>> 3 /= 5
True

It should be obvious how these operators work. The only thing to be highlighted is that the "not equal" operator is (/=) not (!=) as in most other programming languages. I guess this is a result of Haskell's mathematical pedigree because (/=) is an ASCII approximation of \(\ne\).

You also have your standard inequality operators:

GHCi
>>> 3 < 5
True
>>> 3 > 5
False
>>> 3 <= 5
True
>>> 3 >= 5
False

At this point, it is sufficient to know that all these operations are supported by integers. Later, we will see that these operators are part of type classes, a concept similar to Java interfaces or traits in Rust. Addition, subtraction, and multiplication are operations supported by any number type, represented by the type class Num. Integer division is supported by any integral number type, represented by the type class Integral. The comparison operators are split into two type classes, Eq and Ord, which represent types that can be tested for equality and types that have an ordering defined on them, respectively. Some types, such as binary trees, may be comparable for equality but there is generally no meaningful notion of one tree being less than another tree. So the binary tree would be an instance of the Eq type class but not of the Ord type class. Functions cannot even be effectively tested for equality, so there isn't even an equality operator for functions. For now, we will ignore type classes and simply be happy that the built-in types support exactly the operations we expect.


  1. There are additional modules you can load to use number types of a particular width, such as Word8, Word16, etc. You normally don't need them because you will care about the width of the integer-type you use only in system-level or performance-oriented programming, something you will rarely do in Haskell. 

  2. Whenever I mention an operator on its own, without arguments to which it is applied, I will enclose it in parentheses because this is how you would write these operators in prefix notation (see here) and also in type signatures.