User:Davjam2/Solutions/Numbers

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Numeric Types[edit | edit source]

1.

These are initial thoughts on appropriate types. The precise requirements may indicate different types are needed.

  1. Physical measurements of room and furniture sizes are approximations and probably only accurate to 3 or 4 significant figures. Float would be a good choice for an internal representation, storing the dimensions in some particular unit (e.g. metres). You could then round to centimetres, or convert to/from feet and inches, etc, for interaction with the user.
  2. Prime numbers are positive integers, and large prime numbers will be outside the limit of numbers that can be represented by an Int. Natural or Integer would be good choices.
  3. Centi would be a good choice for most currencies (where the main monetary unit is divided into 100 subunits). If the program needs to deal accurately with other currencies (the Maltese Scudo is an extreme, and unlikely, possibility), other types may be required.
  4. Int8 or Word8 would be good choices. The fact that they perform modulo arithmetic (and don't give overflow errors) is advantageous here.
  5. Either Float or Double, depending on the accuracy required. The hypotenuse of a triangle where the other two sides are both length 1 is , which can't be expressed exactly in any of the standard types. Returning a Rational would (incorrectly) suggest an accurate rational result. Given the result of the function will be a Float or Double approximation, the inputs might as well be approximations (to the same precision) too.

2.

squareInteger :: Integer -> Integer
squareInteger x = x * x

squareRational :: Rational -> Rational
squareRational x = x * x

3.

triangleArea :: Double -> Double -> Double -> Double
triangleArea a b x = 0.5 * a * b * sin x

4.

halvings :: [Rational]
halvings = iterate (* half) half
  where half = 1 % 2


5.

Probably not. It can't represent any more than Rational can (which already offers unlimited size and precision). And it can't represent e.g. exactly, unlike Rational.

A better idea might be to write (or find) functions to format Rational numbers as exact decimals when possible or highlight recurring digits when not. (You might like to try this!)

What Numeric Means (The Num Class)[edit | edit source]

1.

You can check against the actual definition, for example:

> :info Float
   ...
instance Ord Float -- Defined in ‘GHC.Classes’
instance Enum Float -- Defined in ‘GHC.Float’
   ...

Float being an Enum might be a bit surprising!

2.

div will work with types that only represent integers and will return an integer result. There is also a function mod that will return the remainder. (There's also a divMod that does both together).

(/) will work with types also represent non-integer values, and will return the exact (for e.g. Rational) or a precise-but-not-exact (for e.g. Float) result of the division, with no concept of "remainder".

3.

square :: Num a => a -> a
square x = x * x

Note you can now do any of the following:

> square 3 :: Int
9
> square 3 :: Float
9.0
> square 3 :: Rational
9 % 1

Accuracy and Precision[edit | edit source]

1.

binaryParts :: Rational -> [Rational]
binaryParts = go halvings
  where
    go (h:hx) x = case h `compare` x of
      EQ -> [h]
      LT -> h : go hx (x - h)
      GT -> go hx x

Interestingly, the go function takes an infinite list as a parameter and can return an infinite list as a result. Due to lazy evaluation, this all works fine.

Overflow[edit | edit source]

1.

w8Add :: Word8 -> Word8 -> (Bool, Word8)
w8Add x y = (z < x, z) where z = x + y

y is a positive integer or zero (Word8 is unsigned). Hence adding y to x should give a value greater than or equal to x. If it's less, overflow must have occurred. Is it possibl for overflow to occur yet the result be bigger than x? No: the biggest y can be is 255, and x + 255 is always one less than x (except when x is 0, when no overflow happens).

2.

w8AddErr :: Word8 -> Word8 -> Word8
w8AddErr x y | z < x = error "overflow"
             | otherwise = z
  where z = x + y


3.

Here are two different solutions for i8Add, each illustrating some (mildly interesting) Haskell. The first uses a single where for two different guards:

i8Add :: Int8 -> Int8 -> (Bool, Int8)
i8Add x y | y > 0     = (z < x, z)
          | otherwise = (z > x, z)
  where z = x + y

The second shows a local function binding:

i8Add :: Int8 -> Int8 -> (Bool, Int8)
i8Add x y = (z `cmp` x, z)
  where
    z   = x + y
    cmp = if y > 0 then (<) else (>)

i8AddErr seems a bit easier with the second version:

i8AddErr :: Int8 -> Int8 -> Int8
i8AddErr x y | z `cmp` x = error "overflow"
             | otherwise = z
  where
    z   = x + y
    cmp = if y > 0 then (<) else (>)

Division by Zero[edit | edit source]

1.

isValidFloat :: Float -> Bool
isValidFloat x | isInfinite x = False
               | isNaN x      = False
               | otherwise    = True

or:

isValidFloat x = not (isInfinite x) && not (isNaN x)

2.

import Control.Exception --this needs to be at the top of the file

assertValidFloat :: Float -> a -> a
assertValidFloat x = assert (isValidFloat x)

Note that this is equivalent to:

assertValidFloat x y = assert (isValidFloat x) y

If you're wondering why the y is not needed, read this.

Enumerations[edit | edit source]

1.

The enumFrom functions represent the different combinations of [x..], [x,y,..] etc. The start value must always be present. The next value and final value are optional, so there are four variations: enumFrom, enumFromThen, enumFromTo and enumFromThenTo.

2.

Try for yourself! BUT BE CAREFUL with [120 ..] :: [Integer]. You might want to type take 30 $ [120 ..] :: [Integer]

Note the rounding errors with Float and how the results are consistent with the behaviour of succ.

3.

enumerate :: (Enum a, Bounded a) => [a]
enumerate = [minBound .. maxBound]

You can use it like this:

> enumerate :: [Bool]
[False,True]
> enumerate :: [Ordering]
[LT,EQ,GT]

4.

It won't for the minBound, for example:

> succ (pred 0) :: Word8
*** Exception: Enum.pred{Word8}: tried to take `pred' of minBound

5.

You would need to specify the type:

> toEnum (fromEnum EQ) :: Ordering
EQ
> toEnum (fromEnum EQ) :: Bool
True

For enumerations with more values than can be represented by an Int, "wrapping" may occur:

> toEnum (fromEnum 2158269056624017538838) :: Integer
-234

but:

> succ (pred 2158269056624017538838)
2158269056624017538838

is fine.

Type Classes[edit | edit source]

1.

Check for yourself!

2.

triangleArea :: Floating a => a -> a -> a -> a
triangleArea a b x = 0.5 * a * b * sin x

Note how this now has exactly the same class constraint as the sin function that we use, which makes complete sense.

3.

isValidFloat :: RealFloat a => a -> Bool
isValidFloat x | isInfinite x = False
               | isNaN x      = False
               | otherwise    = True

assertValidFloat :: RealFloat a => a -> b -> b
assertValidFloat x = assert (isValidFloat x)

4.

Probably: Num, Real, Integral, as well as Eq, Ord, Enum and Bounded.

5.

Probably: Num, Real, Fractional. Ideally also Eq, Ord (though we may struggle to implement these).

(Real has a method toRational defined as "the rational equivalent of its real argument with full precision" which would be impossible to implement as stated. It would probably be appropriate to return a rational approximation to some stated precision.)

It can't be Floating, since (for example) we can't represent .