User:Davjam2/Numbers

Quite a bit is said about numbers in the Type basics II and Classes and types pages. Those pages use the numeric types and classes in Haskell to illustrate the Haskell type system. This page is more focussed on numbers themselves: how to do different types of numeric computations using the Haskell types and classes.

Numbers


Mathematically, numbers can be classified into different sets, such as the integers ($$\mathbb{Z}$$), the rational numbers ($$\mathbb{Q}$$) and the real numbers ($$\mathbb{R}$$). It is common to say that the integers are a subset of the rational numbers, and that the rational number 5 "is an integer". Some operations, such as division, are only possible within certain sets of numbers: there is no solution to $$2x=5$$ amongst the integers, but there is amongst the rational numbers. Rational numbers are said to be closed under division (excluding division by zero), but integers are not.

No programming language (not even Haskell) has a way of representing "any number". Generally, a language will have different numeric types, each representing (possibly a subset of) the numbers in one of the classification sets. In Haskell, there are types such as,   and   that closely represent $$\mathbb{Z}$$, $$\mathbb{Q}$$ and a subset of $$\mathbb{R}$$. Prelude provides a number of these types, and more are available in other modules. None of these types is perfect in all situations: for example, none of the standard types can represent $$\sqrt{2}$$ or $$\pi$$ precisely.

In Haskell, the  and   are definitely not the same thing. It's not even possible to compare them directly using, since   can only compare values of the same type. Many programming languages allow operations on numbers of different types (such as adding the integer 5 to the number 3.14). They usually do this by quietly "upgrading" one of the numbers so that they are both of the same types before applying the operation. Haskell does not directly allow addition of different types of number, nor does it quietly "upgrade" numbers to different types. It does, however, allow explicit conversion between types.

Most standard numeric operations are defined as class methods, so that they work on all of the relevant numeric types. For example:  (addition) is defined to work on all numeric types;   (division, which can find the solution to $$2x=5$$) is not available for integer types; and   (finding the sine of an angle) will work on types representing (as well as they can) real numbers. A smaller number of mathematical operations (e.g. ) are defined as functions (which call the class methods). Many of the numerical functions (class methods or not) are partial, and will throw exceptions for certain parameters (for example division by zero, for some types). You should either ensure your code avoids such parameters, or handles the exceptions that may occur.

Numeric Types
Some of the Haskell numeric types are:

The set shown against a type is the "smallest" of those sets illustrated above that contains all of the numeric values of that type. The types,   and   can represent all of the values in the corresponding sets, subject to the computer's memory and performance limits. The other types can represent only subsets of the corresponding sets, for example  can only represent integers in the range -128 to 127, and   can only represent those real numbers within the (approximate) range –3.4028235 × 1038 to –3.4028235 × 1038 that can be written with about 8 significant digits.

In mathematics, any value in a "smaller" set has an equivalent in a "larger" set. For example, $$\mathbb{Z} \subset \mathbb{R}$$, and all integers have an equivalent real number. In Haskell types, this relationship is only partially preserved:  has an equivalent , but   does not have a corresponding value of type   (since   does not represent all real numbers).

The $$^+$$ indicates types that include values (such as "infinity" and "negative zero") in addition to those in the corresponding sets.

A blank in the Module column means the type is exported by Prelude.

Some of the numeric types ( and  ) are parametized types, i.e. you can't create a value of type , but you can create a value of type  ,  , etc.

There are some type-specific functions, for example, which returns the numerator of a.

There are many more numeric types than listed in the table above, and the set of numeric types can be extended:
 * you can create your own―something we will do later in this page; and
 * you can search Hoogle for other libraries that provide types such as matrices, polynomials, etc.

What Numeric Means (The Class)
By "numeric", we mean that the types support some basic maths such as addition and multiplication. In Haskell, these basic maths operations are defined in the  class. The types listed above all are instances of, and each provides an appropriate implementation for them.

Note the type of the addition operator:

As explained previously, this type signature shows that  is a function that takes two parameters (of the same type), and returns a value (of that same type). But the type can't be any of type (it can't be a, for example), it has to be a type that's an instance of the class. Hence  is polymorphic: it can operate on different types (at different times; not in the same expression), subject to the   constraint. (We will talk about how to calculate the sum of numbers of different types in the section on numeric conversions later.)

also includes functions for multiplication and subtraction (and some more; we will see the details later). It does not provide division. There are additional numeric classes that provide functions that are only suitable for a subset of the numeric types, for example:
 * , which returns the sine of an angle, where it doesn't make sense to return any of the integral types; and
 * and, which do different types of division suitable for different types of number.

also includes the function, which creates a value of any numeric type from an integer (e.g.   gives  ). This is one of a number of type conversion functions discussed in more detail later. It is also used by the Haskell when compiling a numeric literal.

All of the numeric types above are instances of, though this should be used with care with types representing approximations (e.g.  ). Many are also instances of other standard classes ( ,   and  ), but there are nuances in their behaviour.

Accuracy and Precision
Some of these types (e.g. ) offer complete accuracy. You can multiply two 50-digit  values together and get a mathematically exact 100-digit result. is also completely accurate.

Some of the types (e.g.,  ) are completely accurate, provided the numbers stay within the range supported by the type. (Note that operations that cause overflow typically don't raise exceptions).

Some types (e.g.  and  ) are used for recording approximations. offers greater precision that, but neither can represent the exact value of $$\pi$$, for example. They also suffer from rounding errors, e.g:

As a result, for approximation types:
 * it probably makes sense to round values as required prior to display to a user (i.e. don't use the result of ); and
 * it probably doesn't make sense to compare them for equality.

Overflow
In general, mathematical operations in Haskell do not raise overflow exceptions. The actual behaviour differs amongst the types.

''TODO: Fix the statements about modulo behaviour for integral types since they may not be correct. The Haskell report says the overflow behaviour is undefined, and it is possible that it is different in different implementations (e.g. on LLVM).''

For bounded integral types, values "wrap" (i.e. the arithmetic is performed modulo 2n, where n is the number of bits in the type):

If overflow is likely, you should consider the options, for example:
 * use a different type (e.g.  instead of , where overflow is not possible); or
 * if a different type is not possible (e.g. it's an integer that needs to be stored in some fixed size field in a database), maybe use a numeric type from a library that does raise exceptions on overflow (or use e.g.  but do validation prior to trying to store the result); or
 * be happy that it's what you wanted (e.g. for calculating a checksum of a file).

Note that  does raise an exception for a bounded integral type when exceeding a bound:

can raise an exception during subtraction:

When floating point types overflow, they take the value :

Division by Zero
Division by zero will raise an exception for some types, but for  and , a value   is returned:

However, it is possible to create  values with zero denominators by directly using the   constructor or utility functions:

Note that  and   also support negative infinity, negative zero, and NaN ("not a number"):

They will also return  for e.g..

They support functions  and   so you can check for potential errors.

Enumerations
Many of the numeric types are instances of, including fractional types. The  class includes methods   and   for converting between values of the type and values of the (bounded) type. Most types (including most numeric types) will not have a 1:1 mapping with, leading to some interesting behaviour.

There is no requirement that  represents the numeric value n, though this is the case for many types:

In some cases where n is out of bounds,  will cause "wrapping" but   will raise an exception:

will have corresponding behaviour, and in some cases it truncates fractional parts:

Where the result is out of bounds for an, typically "wrapping" will occur without raising exceptions:

and  are consistent with the above. In many cases,  has the same effect as adding 1 (but not for   etc). As mentioned above,  may raise an exception when exceeding the bounds of a type. For some types  will work correctly even though   "wraps":

For imprecise floating-point types,  will make no difference to large values due to lost precision:

also includes methods  and variations. Haskell compiles,  , etc, into these.

Internal Representation
Most numeric types provide functions that give access to their internal representations.

Some numeric types are made of separate components, for example  has two   components, and   has two   components. Both types have functions e.g.  or , that access these.

exposes its infix constructor, which can be used for creating complex numbers, pattern matching, etc. does not expose its constructor (it's, hidden in  ). is not a constructor, it's an operator that first reduces the ratio (e.g. $$\tfrac{3}{12}$$ to $$\tfrac{1}{4}$$) and then creates the Ratio. In addition, the  method displays.

The bits representation of integers can be accessed via  module, for example:

There are also functions such as  for accessing details of floating point numbers.

Type Classes
As stated above, most standard numeric operations are defined as class methods, so that they can be applied to all appropriate types. The numeric type classes in Prelude are:

The classes form a hierarchy (shown here), so that a type can't be an instance of, say  if it's not also an instance of.

Introduction
Suppose we want a function that can add any  value to $$\pi$$. Let's try:

Oops - we get an error:. This makes sense: the definition of  requires both types to be the same.

How do we fix this? Using the  method on the   class:

is one of a number of numeric conversion functions that we'll look at.

However, before looking at them, note that there aren't separate,  ,  , etc, functions. Instead,  is a method on the class , and each instance provides its own implementation. When  is called, the function that calls it needs to be clear which type should be returned. In the above example, the required type was determined by type inference, based on the types of  and.

More Integral Conversions


The diagram on the right shows  in a class hierarchy diagram, along with a number of other conversion functions. It is a method on the  class, so can create a value of any numeric type from a   value.

Another method shown is. This is a method on the  class, so any   type (e.g. ,  , and even   itself) provides a method to create an   with the equivalent numeric value. Prelude also includes a function  that combines   and , making it easy to convert from any   type to any numeric type.

Fractional Conversions
Two class methods support the majority of fractional conversions:

is a method of the  class, so it's possible to create a   of any type from a. As with, the function that calls   needs to be clear what type should be returned.

Just as with, we have a type problem if we try to write this:

since we're trying to add a  to a.

is a method of the  class, so will work for e.g. ,  ,  , etc, and even on   itself. Each type instance will define its own implementation, and is expected to return "the rational equivalent of its real argument with full precision". For approximate types (e.g. ) the result should therefore be an exact equivalent of the approximate value.

There is also the function, that is a combination of   and  , which can convert any instance of   to any instance of.

Note that, with  and ,   loses the negative of negative zeros and "rounds" infinities to large fractions. This impacts how  handles conversions between them. The functions  and   in   avoid these problems:

Rounding
(and hence ) will not work on, say, , since there is no way to return a value equivalent to   as an. However, there are functions such as,  , etc. These are methods on the   class that will return a value of any   type:

Since there's no integer that's a numeric equivalent of, these functions give different options for the conversion:

also provides the method, which decomposes a number into an integral and fractional parts, e.g:

, etc, and generic versions
Prelude includes functions such as  and   that return or take values only of type. You may think "well, I'd never need to use a huge number such as available in, so   is good enough." But remember there are other  types as well, such as , etc, and it might sometimes be useful to get/give those as parameters.

Hence the module  also exports "generic" versions of the functions:

Note that  can return any , although it will always return a value equivalent to an integer (e.g.  ).

Converting to/from Strings
There are a number of functions for converting numbers between numbers and strings. and  are some, but they only provide limited behaviour and may well not be adequate for many purposes.

Some other functions include  (which can show an integer value in any base), and   (which has an option to specify the number of decimal places). These functions return a type, which makes them more efficient when building large strings.

There is also the function  in the   module which can make it easier to embed formatted numbers within longer strings, for example:

Beware that  can't check that the parameter types against the format string at compile time, and will raise a run-time exception if they mismatch.

Operators, Sections
Many mathematical functions are operators, which are often written in infix notation, e.g., but can be written prefix, e.g.  . Non-operator functions are often written in prefix, e.g.  , but can be written infix, e.g..

Operators can be used in sections, e.g., which is equal to  , and  , which is equal to. Functions written in infix can also be used as sections, e.g.  or.

Fixity, Precedence
What is meant by the mathematical expression 5 + 32 × 4? There is a convention that we apply the operations in a certain order, like this: 5 + 32 × 4 = 5 + 9 × 4 = 5 + 36 = 41. The convention says that the exponentiation (raising to a power) operator has a higher precedence than multiplication, which in turn has a higher precedence than addition. If we wanted to add the 5 and 3 together first, we'd indicate it using brackets: (5 + 3)2 × 4 = 82 × 4 = 64 × 4 = 256.

What about the expression 7 − 3 − 2? The convention is that subtraction associates to the left, like this: 7 − 3 − 2 = 4 − 2 = 2. . If we wanted the 3 − 2 to happen first, we'd also need to indicate it using brackets: 7 − (3 − 2) = 7 − 1 = 6.

Haskell is aware of the conventions and the use of brackets:

In a Haskell program, the precedence and associativity of operators can be specified using "fixity" declarations. For example,  and   have these declarations:

This shows that  has a higher precedence than , that   associates to the left and that   associates to the right. The standard mathematical operators follow the standard conventions, and are part of a precedence hierarchy that includes other operators, as shown here.

Fixity declarations can also be given for functions, and are used when those functions are written in infix style, e.g.:

Hence  means the same as.

Note that prefix function application has the highest precedence, so  means , not.

(negate) infix and prefix
The  operator can be used normally as an infix operator. However, uniquely, it can also be used as an (unbracketed) prefix operator, where it has the same meaning as. This can be very useful, but can also be a bit confusing at times.

These expressions are OK, and  has the two-parameter subtraction meaning:
 * (normal infix operator, giving 3)
 * (normal prefix operator, giving 3)
 * (normal section, means ).
 * (means )
 * (means )

These expressions are OK, and  has the one-parameter   meaning:
 * (means, since   has a higher precedence than  )
 * (means )
 * (means ).
 * (means ).
 * (means )
 * (means )

These are not OK:
 * and . These don't compile, since the negate operator has lower (or the same) precedence as the other operator.
 * . This means, since function application has highest precedence, which doesn't compile.
 * . Due the the special  prefix operator,   is not treated as a section. Instead it's interpreted as , which is not a function (and so can't be passed to  ). However, you can use the   function instead:   which gives.

Note that removing spaces makes no difference. For example  fails in exactly the same way as , despite it being "obvious to everyone that it should mean  ".

You may want to put brackets around any usage of  as a prefix operator for clarity. (And your own sanity).

Integer Literals
Suppose we want to define a  variable with the value $$1+\pi$$. We can do this:

OK, it only gives an approximation, but it's otherwise brilliant:

But how does this work? Why don't we get a type error? is defined to return a. So  must return a. So each of the parameters to  must be a. is defined so it can be a value of any instance of, one of which is  , so it's happy. however looks like an integer, which is not a, so how does that work?

The answer is that the  actually compiles to , so   is essentially:

is has type signature:

which can create a value of any numeric type we want. And right now we want a  (so we can add it to another   giving a  ), so that's what we get.

We can see the effect of the usage of  here:

So now we see why a simple number (e.g. ) has the weird type : because it can be any type of number you want it to be.

If you do the  exercise below, you should have created a   implementation for , so can do:

Note that just because Haskell can interpret a numeric literal in code, doesn't mean  will interpret it in the same way (or at all):

Although it can if you also implement a  instance.

Fractional Literals
Numeric literals containing decimal points or the  symbol are compiled using , so you can write:

where 0.5 "looks like" a floating-point number, but means effectively:

where the decimal number 0.5 has been converted to $$\tfrac{1}{2}$$ and wrapped with. We can see the effect of  here:

Note that to be a fractional literal, digits are needed both before and after the decimal point. (i.e.  not  ).

Literals in exponent form are compiled using, even if they don't contain a decimal point, and even if they are integer values:

GHC has a language extension  that allows can allows.

Negative Literals
A "negative literal" compiles to an application of the prefix  operator. Hence  is equivalent to , and   is equivalent to.

In some cases, brackets may be needed due to operator precedence. For example  doesn't compile, but   does. and  also compile correctly.

GHC has a language extension  that changes the interpretation, so that   is equivalent to.

Pattern Matching
Pattern matching on literals (numeric, character or string) compiles into a use of, so also depends on the numeric type being an instance of. E.g:

compiles to:

Note that the following compiles without warning, but will never return 2:

Non-Decimal literals
It is also possible to type integer literals in octal or hexadecimal:

which are also all compiled using.

Fractional literals must be in decimal, but GHC has a language extension  that also allows them in hexadecimal. is then the same as.

Type Ambiguity
In some cases there is ambiguity in the type of an expression. In many cases, ambiguous expressions will not compile, for example:

without the type signature is an error. You could want a, an  , an  , but how does the compiler know?

To fix this, you can specify the type (ideally give  a type signature), or add an expression that uses   and requires it to be a specify type, e.g:

This expression would fix  to have type. (Alternatively I could write e.g., which would require   to have type  . But I can't have both expressions, since   can only have one type.)

In some cases, it's not possible to fix ambiguity using a top-level type signature. For example:

Here I've given  a type signature, but it's still ambiguous what types   should create. Here's one way to fix it :

These examples above are fairly contrived to make a point. But the problem does often arise, especially with numeric types. For example:

which takes a, and returns   if it's closer to an even number than an odd number.

Note the types:

Hence  can produce any   type and   can consume any   type. How is the compiler meant to know which of the many  types to use? And why don't we get a type ambiguity error? The answer is defaults.

Each module (file) can have a  code declaration, and if it's omitted it's assumed to be:

The details of exactly how it works are specified in the Haskell Report, but essentially means that instead of giving an error for some ambiguous types, it will use one of the defaults if possible. It will use  for ambiguous   types and   for ambiguous   types. Hence  will compile so that   will produce, and   will consume, a value of type.

If you turn on e.g., which I often do, you will get a warning when type defaulting occurs.

You can change the defaulting rules by adding your own  statement to the .hs file. You can even disable it by stating, in which case   would give an error.

Note that  (hence also  ) and   (hence also  ) are often subject to defaulting, since (by design) they can return a value of any one of a number of types. As a result, numeric literals (which compile using  or  ) are also often subject to defaulting.

default in GHCi
It is a little curious that, when you enter e.g.  into GHCi, it replies with. Observe:

We can see that GHCi is being as helpful as possible. It does the sum initially using the default type because we didn't tell it otherwise (and displaying 135 is probably more helpful than displaying a type ambiguity error). If, however, we specify a type, it will do the calculation using the requested type, which in this case results in a different value.

Also, when we ask it to tell us the type of an expression, it will do so without regard to any defaulting that might occur upon evaluation.

We can turn off defaulting in GHCi as in .hs files:

In standard Haskell 2010, defaulting (and the  statement) only applies to numeric types. GHC (since 6.8.1) supports a language extension  that allows defaulting of additional types. You can enable this in .hs files, but it is initially enabled when you start GHCi:

where the type has defaulted to (the type).

You can modify this behaviour by turning off :

Mathematical Operations
Most mathematical operations in Haskell attempt to mirror "true" mathematical operations. However, none of the operations match perfectly due to overflow, rounding, etc, with different behaviours for different types. In addition, the standard types  and   represent real numerical values (within some range and only to certain precision), but also include representations of positive and negative infinities and negative zero.

This section compares Haskell mathematical operators and "true" maths.

TODO: Add something on benefit of Infinities, e.g. in 

Addition, Subtraction and Multiplication
, (binary)  and   follow the mathematical operations of addition, multiplication and subtraction, subject to overflow and rounding. These operations are defined as methods of the  class, and are available for all numeric types. However, they are not closed for all types, for example  is not closed for the   type.

determines the additive inverse, where there is one.

Negative zeros and infinities behave "reasonably", for example:

From a Haskell syntax perspective,  can be used as a prefix operator to represent   (and so can't be used as a section with the operator on the left). There is a function, where   means "subtract 2 from 5", which can be used instead.

Division
Mathematically, integers are not not closed under division, but other numbers (rational, real, complex) are. Accordingly, Haskell splits division between two classes:
 * , which defines methods  and   (and the combined  ) for performing division with remainder (i.e. given integers $$a$$ and $$b$$, find integers $$q$$ and $$r$$ such that $$a = bq + r$$ and $$0 \le r < |b|$$).
 * , which defines method  for performing division without remainder (i.e. given $$a$$ and $$b$$, find $$q$$ such that $$a = bq$$). It also provides   for finding the multiplicative inverse.

provides two sets of "division with remainder" functions, illustrated here when dividing -11 by 4:

In mathematics there is no ability to divide by zero. In Haskell, division by zero is treated differently for different types:

However, note that these behaviours are not stated requirements of the  or   class methods. It would be possible for some  types (hand-written, or from other libraries) to return values, or for some   types to raise exceptions.

Exponentiation
Interestingly, Haskell has four different functions corresponding to exponentiation ("raising to a power"):

can also be used for floating-point types. gives $$x.r^n$$ where  is the   (often 2) of x.

,,  and
These are all functions that operate on  types, and call methods defined on the   class.

Each integer is either even or odd. returns a  indicating whether the argument is even, and   returns a   indicating whether the argument is odd.

calculates the greatest common divisor of two integers, using the Euclidean algorithm. For example,  = 4.

uses  to calculate the lowest common multiple. For example,  = 24.

and


determines the absolute value of x, often written $$\vert x \vert$$. determines the sign of x.

Both operations are are methods of the  class and are available for all numeric types. The two functions adhere to the law that, for any number x, $\equiv x$. For all standard numeric types,  returns a value of "magnitude" 0 or 1, but this is not a general requirement.

For any real number x: $$\equiv x$$ if $$x \ge 0$$ and  $$\equiv -x$$ if $$x < 0$$; and   is 1, 0 or -1. For types that support negative zero,  gives.

For a (non-zero) complex number x:  is a complex number with magnitude 1 and the same phase as x; and   is a the magnitude of x (as a complex number with imaginary part of 0). Hence if $$x = re^{i\theta}$$, then $$\equiv e^{i\theta}$$ and  $$\equiv r+0i$$

Irrational and Transcendental Functions
These functions all operate on instances of the  type, on either real or (with the exception of  ) complex numbers. There are a number of subtleties around their use:


 * precision: these functions produce only approximations to the mathematical results. For example, mathematically $$\sin(-\pi)=0$$, but GHC calculates to be.
 * principle values: mathematically, some "functions", especially "inverse" functions, are multivalued. For example, $$x \to x^2$$ maps both $$2$$ and $$-2$$ to $$4$$. The inverse function $$x \to \pm\sqrt{x}$$ maps $$4$$ to both $$2$$ and $$-2$$. Similarly $$x \to \cos x$$, which is periodic, maps $$0$$, $$2\pi$$ and $$4\pi$$ (and infinitely many other values) to $$1$$. The inverse function maps $$1$$ back to those infinitely many values. Sometimes only a single result is desired and, by convention, the notation $$\sqrt{x}$$ usually means only the positive value. Depending on context, $$\arccos x$$ may also refer to only a single principle value. The Haskell functions  and   only return a principle value for any given input:   gives   and   gives  . These principle values are somewhat (but no universally) standardised. In some cases, for example $$x \to \log_e x$$ (the inverse of $$x \to e^x$$), there is only a single result within the real numbers but multiple results (and hence defined principle values) within the complex numbers.
 * limited domains: mathematically, some functions are undefined for some values, for example $$\log_e 0$$ is undefined.  or   results are set to the IEEE values of ,   or   in these cases. For example,  gives  . In some cases a function may be undefined for some value when the results are restricted to real numbers, but defined when complex numbers are allowed. For example, there is no $$\sqrt{-1}$$ within the real numbers, but there is within the complex numbers. gives, but  gives . In some cases there is a valid real result but none is returned. For example, $$(-2)^3 = -8$$, but   gives.
 * discontinuities and branch cuts: some functions are discontinuous at some points. A small change in the input can result is an extreme change in the output. For example $$x \to \tan x$$ is discontinuous (and undefined) at $$\pi/2$$. gives   but  gives  . When complex numbers are considered, the discontinuities run along lines (often the axes), and are called branch cuts. The locations of these branch cuts depend on the principle values chosen for the functions, as explained below.
 * negative zeros:  and   also support negative zeros. Most arithmetic functions don't distinguish between   and , but some of the irrational and transcendental functions do. For example  gives   but  gives  . For complex numbers, branch cuts often lie "between"   and  . For example,  gives  and  gives

In many cases these subtleties are not problematic, but at times can conspire to create erroneous calculations. In particular, direct implementation of formulae that are mathematically equivalent may not yield computations that give the same results.

Principle Values and Branch Cuts


The locations of branch cuts depend on the defined principle values.

For example, consider $$z \to e^z$$, as illustrated on the diagram on the right. The red line highlights points $$z$$ on a line on the imaginary axis, extending to infinity in both directions. Per Euler's formula, the mapping of points on the red line by $$z \to e^z$$ all lie on the unit circle, illustrated in blue. The point 0 on the red line maps to 1. As we move up the red line from 0, points map to points anti-clockwise around the circle. $$(\pi/2)i$$ maps to $$i$$ and $$\pi i$$ to $$-1$$. The point $$3i$$ maps to a point just before $$-1$$ and $$4i$$ to a point a little after it. As we move down the red line from 0, points map to points further clockwise from 1. $$-(\pi/2)i$$ maps to $$-i$$ and $$-\pi i$$ maps to $$-1$$. We can see that both $$\pi i$$ and $$-\pi i$$ map to $$-1$$. Similarly $$4i$$ and a point at about $$-2.28i$$ also map to the same place as each other. In fact, the mapping of the red line wraps around the blue circle infinitely many times, so every point on the blue circle has infinitely many points that map to it.

$$x \to e^x$$ maps other vertical lines to other circles centred on the origin. Vertical lines to the left of the red line map to smaller circles, and lines to the right map to larger circles. Points on any one vertical line with imaginary parts at $$\pi$$ and $$-\pi$$ map to the same point as each other, on the negative real axis.



The inverse mathematical function $$z \to \log_e z$$ is the complex logarithm. Sometimes it is useful to consider it multi-valued, as illustrated on the left. Each point $$z$$ (except the origin) will map to points vertically aligned in different layers in the diagram. $$-1$$ maps to $$\pi i$$ and to $$-\pi i$$ (and to infinitely many other points). As $$z$$ travels around a circle, its mappings move around the surface following a helix, and moving up a whole level for each full circuit of the circle. There are no branch cuts.

Sometimes it's more convenient for its range to be restricted so that it has single, principle value. Typically (but not universally), this range is defined as those numbers with imaginary part in the range $$(-\pi, \pi]$$. Then $$-1$$ maps to only the principle value $$\pi i$$. The range of $$z \to \log_e z$$ for all the points on the blue circle is illustrated by the green line (excluding the point $$-\pi i$$), which represents a subset of the points on the original red line. There is a branch cut along the negative real axis: points on it (or just above it) map to points with an imaginary part of (or just less than) $$\pi$$. Points just below it map to points with an imaginary part just greater than $$-\pi$$.

The Haskell function  mirrors the behaviour of $$z \to e^z$$, except for rounding errors. (for  and  ) behaves the same as the restricted, single-valued, function $$z \to log_e z$$, but with a slightly different range due to the presence of negative zeros. It maps points with imaginary part  to points with imaginary part $$\pi$$ and points with imaginary part   to points with imaginary part $$-\pi$$. The branch cut is "between"  and   on the imaginary axis:

The range of  is those numbers with imaginary part in the range $$[-\pi, \pi]$$. It is represented by the green line, including the point $$-\pi i$$.

Both $$-1$$ (under $$z \to log_e z$$) and  (under  ) map to $$\pi i$$, indicated by the filled-in arrow on the diagram. Only  maps (under  ) to $$-\pi i$$, indicated by the unfilled arrow.

One consequence of the branch cut is that  is only the inverse of   for values within the range of  :

There is not a single agreed definition for either the principle values or branch cuts, and they may be different for different types, in other languages, in other systems and in different mathematical usage. It would, for example, be possible for a different (e.g. user-defined) type to define the range of  to be those numbers with imaginary part in the range $$[-\pi/2, 3\pi/2]$$, with a branch cut on the negative imaginary axis.

Rounding and Branch Cuts
Sometimes, rounding errors result in values being the "wrong side" of branch cuts. For example:

is within the range of, so we would expect the result to be. The problem is clear if we look at the intermediate result:

We can see the value has "overshot" $$-1$$ by a little bit, taking it to the wrong side of the branch cut. (Mathematically, $$e^{\pi i} = -1$$. Ideally,  would give , and   would give  ).

In this case, when we use, which operates to greater precision, it behaves as expected:

Unfortunately this problem is sometimes hard to detect and code around. It is also not limited to problems with the inaccuracy of the algorithms for the core functions such as. For example, consider. Mathematically, this should be negative when x > 0, and tend to zero as x increases. To start with, the function behaves as expected:

However, there comes a point when the precision of  means it can't distinguish   and. At this point, the function stops returning a negative value:

This by itself may not be a big problem. But now consider. When x > 0,  should return. However, as soon as x reaches the "failure point", it suddenly jumps to, giving an unexpected discontinuity and an inaccurate result:

A similar problem can occur with branch cuts, e.g. with :

Or with some build-in functions :

Exception Handling
Some numeric operations raise exceptions in some situations, such as division by zero. It would generally be an error to allow these to occur without handling the exceptions. The following sections are some of the ways to deal with this.

An Aside on Avoiding Errors using Types
It is often best to design data types so that it is impossible to write erroneous programs. As a contrived example, compare the following:

with:

The second version would allow  to compile, which would give an error at run-time. The first version ensures we can't write such erroneous code (as well as being a lot easier to read!).

Suppose we tried to adopt this approach to avoid division by zero errors. We could define a  type and redefine   with the type signature. Now we have to fix errors like  at compile time and would never get a division by zero run-time exception.

But at some point we might want to code e.g. . And now we have a problem:
 * we may want to allow either y or z to individually be zero (hence they would not be of type, so we'd need to convert them to   in order to pass the result to  ); and
 * y and z might be the same, so  would be zero and the conversion would fail with a run-time exception that zero is out-of-bounds for.

This attempt to use types to catch division by zero at compile time has failed: we've just swapped one possible run-time exception for another. (And added complexity to the types).

Program logic
One way to avoid run-time exceptions is to include logic in your program, especially at places where you get data from the outside world (e.g. a user).

Here's an example program:

It has a couple of problems, including that the user could type non-integer values (which we'll ignore for now), and that they or they could type in "0".

"Safe" functions
One approach often advocated for pure functions (i.e. where the result depends only on the arguments passed to it) is have the function return  for those values where it "fails". For example, the function:

drops the given prefix from a list, but returns  if the list did not start with it. There are also a number of safe functions in other modules (such as the cunningly named, that provides safe version of list functions such as  ).

Similarly, we could write "safe" versions of  etc:

Although a reasonable approach, it might be quite a challenge to build "safe" versions for all of the functions you use, especially since you would have to check the error conditions for each of them.

Note that you could use  instead of   to similar effect, and this would allow the response to indicate the type of error that occurred (not just that there was one).

Catching Exceptions
Another way to handle exceptions is with. This is defined as:

Both the result of  and its first argument are   monads. The second argument is an "exception handler". When executed,  executes its first argument and, if an exception is raised, the "handler" is executed with the value of the exception passed as an argument. Otherwise, the result is returned as normal.

This works well with many  actions, such as opening a file:

Here's a first attempt at using it in :

Which gives the following:

which isn't quite what we want. The problem is that, as a result of lazy evaluation,  is happy to start displaying the result of   before attempting to evaluate the. The  is only evaluated when   needs the   to evaluate , and this is when the exception is triggered. The behaviour would be the same if the  is outside of the.

However, we can "force" the evaluation of the  earlier, using  :

will create an  action which, when executed, will force evaluation of the , and throw the exception if appropriate.

Note that  doesn't "fully evaluate" expressions. For example, it would not cause an exception in.

Catching Exceptions can be Painful
It might be nice if we could display tables showing how numeric operators worked, like this:

But we need to be careful with operations that throw exceptions, so that we get this:

Our first attempt at  might start like this:

You probably found the conversion to  quite painful. We can no longer construct a string for each line using a pure function, since we want to catch exceptions in the middle of the string. Instead we have to execute  for each cell in the table, handing exceptions as we go, and much more of the code is written in the   monad.

Catching Exceptions in Pure Code
Wouldn't it be nice if we could catch exceptions in pure code? Then we could keep the pure function  in   above, replacing any exceptions with the "-" character we want.

There is a way to do this, using this function with a slightly scary name from the module :

which allows an IO computation to be performed at any time. It comes with a warning: For this to be safe, the IO computation should be free of side effects and independent of its environment. However, we only want to evaluate (pure) expressions and handle the exceptions they throw, so I think we're OK.

We can define:

Now:

Mod3
Let's create a new numeric type that does arithmetic modulo 3, so that addition works like this:

Here's a start:

Note that we can automatically derive instances of many standard classes for this type. Also, this shows that being a "numeric" doesn't necessarily mean we have to use numeric digits (0, 1, 2) to represent the values.

Let's make this an instance of the appropriate numeric classes.

Instance
In order to do addition using, we need to make this an instance of  :

OK - I got bored, so your turn:

Word8T
The standard  type performs arithmetic modulo 256. Sometimes it would be useful to have a type that throws exceptions on overflow instead, so let's create type  that does that.