Bedevere: Does wood sink in water?
Villager: No, it floats! It floats!
Bedevere: What also floats in water?
Arthur: A duck!
Bedevere: Exactly. So, logically…
Villager: If… she… weighs… the same as a duck… she’s made of wood.
Bedevere: And therefore?
Villager: A witch!
— Monty Python and the Holy Grail, 1975
We discuss floating-point numbers with emphasis on the data type
Double
. The section
Floating-point numbers briefly presents basic
arithmetic operations on floating-pointing numbers. Building on from the section
Printing numbers, in the section
Parsing numbers you will learn how to read a number from the
command line. The section Convert from integers
discusses how to convert an integer to a floating-point number, whereas the
section Convert from floating-points presents
subtle issues you would encounter when converting a floating-point number to an
integer.
Haskell uses the types Float
and Double
to represent
floating-point numbers. The type Float
uses single-precision to represent
floating-point numbers, while Double
uses double-precision. We will only
discuss the type Double
and relegate the topic of Float
to another source.
The type Double
supports the usual arithmetic operators: +
(addition), -
(subtraction), *
(multiplication), and /
(division). Unlike the types
Int
and Integer
, exponentiation for floating-point numbers
is performed via the operator **
. Observe the following GHCi session.
1
2
3
4
5
6
7
8
9
10
11
12
ghci> a = 7.0 :: Double
ghci> b = 2.0 :: Double
ghci> a + b
9.0
ghci> a - b
5.0
ghci> a * b
14.0
ghci> a / b
3.5
ghci> a**b
49.0
The above GHCi session shows floating-point numbers being represented in decimal form. Haskell can also understand scientific notation in the form of the exponential (or e) notation. Refer to the session below.
1
2
3
4
5
6
7
8
ghci> 2.1 * (10**3)
2100.0
ghci> 2.1e3
2100.0
ghci> 3.54 * (10**(-4))
3.5400000000000004e-4
ghci> 3.54e-4
3.54e-4
Let’s build on what we have learnt from the section Printing numbers. In particular, we will write a program to obtain from the command line the radius of a circle. We will use the radius to calculate the area of a circle having the given radius, then print the area to the command line. Here is our program:
:include: file=”assets/src/data/circle.hs”, line=25:-
In the terminal session below, we compile the program and execute it. When prompted to enter the radius, we enter the number 2. The program uses the provided radius to calculate and print the area of a circle having the given radius.
1
2
3
4
5
6
7
$ ghc circle.hs
[1 of 2] Compiling Main ( circle.hs, circle.o )
[2 of 2] Linking circle
$ ./circle
Please enter the radius.
2
Area of circle: 12.566370614359172
The program
:script: file=”assets/src/data/circle.hs”
uses the functions getLine
, read
, and pi
. The
program also uses the operator <-
. Let’s consider each of them in
turn.
getLine
, as its name suggests, reads a line from
standard input. The standard input is usually the prompt at the terminal.
The read line is converted to a string regardless of whether we enter a
string or numeric digits at the command line.<-
is used within a do
block to bind the result of a function to a local variable. In terms of the
program
:script: file=”assets/src/data/circle.hs”
the result of the function getLine
is assigned to the variable radius
.
Why not use =
for assignment instead of <-
? That’s a quirk of a do
block. Let’s leave it as is instead of being distracted by a technical
discussion about the distinction between =
and <-
for assignment.read
parses a string into a given data type. You must
provide the target type. In the code read radius :: Double
, the function
read
parses the string radius
and returns the string as a floating-point
number of type Double
. Neither the function read
nor our program does
any error checking. We assume that the variable radius
holds the string
representation of a number.pi
returns the constant $\pi$ as a floating-point
number.1
To convert an integer to a floating-point number, use the function
fromIntegral
. This function can convert from data of type
Int
or Integer
. Then use the symbol ::
to get the type
you want. For example, the GHCi session below converts various integers to type
Double
.
1
2
3
4
5
6
7
8
9
10
11
12
ghci> a = 3 :: Int
ghci> b = 3 :: Integer
ghci> c = fromIntegral a :: Double
ghci> :type c
c :: Double
ghci> c
3.0
ghci> d = fromIntegral b :: Double
ghci> :type d
d :: Double
ghci> d
3.0
The function fromInteger
can also be used to convert an integer
to another type. However, the function only works if you want to convert from
data of type Integer
. It would not work on data of type Int
. Observe GHCi
throwing a tantrum.
1
2
3
4
5
6
7
8
9
10
11
ghci> a = 4 :: Int
ghci> b = 4 :: Integer
ghci> fromInteger b :: Double
4.0
ghci> fromInteger a :: Double
<interactive>:4:13: error:
* Couldn't match expected type 'Integer' with actual type 'Int'
* In the first argument of 'fromInteger', namely 'a'
In the expression: fromInteger a :: Double
In an equation for 'it': it = fromInteger a :: Double
Converting from an integer to floating-point is straightforward. The other way around is more complicated. In the conversion from a floating-point number $x$ to an integer, you must take the following issues into account:
ceiling
, which implements the ceiling function.
The ceiling function rounds a number up to the nearest integer. For example,
ceiling 2.1
returns 3 and ceiling (-2.1)
returns $-2$.floor
, which implements the floor function. The floor
function rounds a number down to the nearest integer. For example, floor
2.1
returns 2 and floor (-2.1)
returns $-3$.round
. This method uses the tie-breaking rule of rounding half to
even. If the fractional part of $x$ is $0.5$, then $x$ is rounded to the
nearest even integer. For this reason, the technique is also known as
rounding to nearest, ties to even. For example, both round 3.5
and
round 4.5
result in 4. The expressions round (-3.5)
and round (-4.5)
produce $-4$.truncate
,
which implements the notion of truncation. The number $x$ is
rounded toward the nearest integer between $x$ and zero. If $x$ is positive,
the rounding is done by means of the floor function. If $x$ is negative,
rounding is done via the ceiling function. For example, truncate 2.6
results in 2 and truncate (-2.6)
yields $-2$.:exercise:
Haskell uses the operator ^
for non-negative integer exponentiation. The
operator **
is reserved for exponentiation of floating-point numbers. There is
a third exponentiation operator, i.e. ^^
, for numbers of type
Fractional
where the exponent can be a negative integer. Why
three different operators for exponentiation? Read the
discussion here.
:exercise: Write a program that prompts for a person’s name and age. The program then greets the person and prints their age in 10 years time.
:exercise: The gravity of the Moon is 0.166 times that of the gravity of Earth. If a person weighs $x$ pounds on Earth, their weight on the Moon would be $0.166x$ pounds. Write a program to prompt a person for their weight in pounds. Print out the person’s weight on the Moon, in pounds as well as in kilograms.
:exercise: The planet Mercury takes about 88 Earth days to complete one orbit around the Sun. Given an age in Earth years, write a program to convert the age to Mercury years, rounded to the nearest year. Assume that each year on Earth has 365 days.
:exercise: The golden ratio is the mathematical constant defined by
\[\varphi = \frac{ 1 + \sqrt{5} }{ 2 }.\]Write a program to print the golden ratio as a floating-point number. Use the
method sqrt
to calculate the square root of a number.
:exercise: The Planck constant is a fundamental physical constant used in the definition of the kilogram, among other applications. The constant is defined as
\[6.62607015 \times 10^{-34} \; \text{J} \cdot \text{Hz}^{-1}\]in terms of the unit “joule per hertz”. Express the Planck constant, excluding the unit of measurement, via the e notation.
:exercise:
The method div
can be defined in terms of the operator /
for
floating-point division and the method floor
. First, perform
floating-point division, then take the floor of the result. Similarly, the
method quot
can be defined in terms of /
and the method
truncate
.2 Perform floating-point division and apply truncate
to the result. Verify the above within a GHCi session.
:exercise:
Why are the numbers 42e-5
and -42e5
different from each other?
:exercise:
The method mod
calculates the integer remainder when an integer is
divided by another integer. The Haskell definition of mod
implements the
definition of remainder as popularized by Donald Knuth.3 If $a$ and $n$ are
integers with $n \neq 0$, the remainder $r_f$ of the division $a / n$ is defined
as
and written as $r_f = a \bmod n$. The method rem
also calculates
integer remainder. Instead of using the floor function, rem
uses truncation
for rounding. The remainder $r_t$ of the division $a / n$ is defined as
Verify the result of mod
against the definition of $r_f$ for the following
types of integers.
Repeat the exercise for rem
and $r_t$.
:exercise: A pyramid has a square base of length $\ell$. If the pyramid has height $h$, then the pyramid has a volume $V$ of
\[V = \frac{1}{3} \ell^2 h.\]The Great Pyramid of Giza has a square base of length 230.33 metres and a current height of 138.5 metres. Calculate the approximate volume of the Great Pyramid of Giza. Modify your result by taking into account that the pyramid originally had a height of 146.6 metres.
The sin of overconsumption is gluttony. Pie is an exception because $\sin(\pi) = 0$. ↩
These definitions of div
and quot
are consistent with the specification
as given in the document Haskell 2010 Language Report. ↩
Donald E. Knuth. The Art of Computer Programming, 3rd edition, volume 1. Addison Wesley Longman, 1997, pp.39–40. ↩