Hemmelig
Hemmelig

Reputation: 794

Haskell Round Number

I have a haskell function which looks like that:

x 1 = 1 :: Double
x n = ((n-1)/3 + 1/(n-1)) * ((x (n-1))^2) - (n-1)^3/3 + 1 :: Double

Don't think about the formula, it should just add one, so x 2 = 2; x 3 = 3 and so on. But the results in Haskell are:

*Main> x 2
2.0
*Main> x 3
2.9999999999999996
*Main> x 4
3.9999999999999964
*Main> 

Can someone tell me, what I need to add in line number two so it is round the number to 6 decimal digits? I don't want to parse to int!

Thanks!

Upvotes: 0

Views: 7977

Answers (6)

David Fox
David Fox

Reputation: 654

I needed this functionality to format numbers for LaTeX output. Daniel Wagner's solution has most of the requirement, but lacks the rounding that maximizes the accuracy of the decimal (Fixed E6) representation:

> let micro x = showFixed True (realToFrac (x + 0.5e-6) :: Fixed E6)
> :type micro
micro :: (Real a, Fractional a) => a -> String
> micro (1/2)
"0.5"
> micro 1.9999999999999
"2"
> micro (1/3)
"0.333333"
> micro (2/3)
"0.666667"

Upvotes: 0

Paul Johnson
Paul Johnson

Reputation: 17786

Your problem is not Haskell, it's the binary representation of floating point numbers. Any language will give you similar results.

Saying that you want to "round" to a certain number of decimal digits is tricky because the binary representation for many decimal values is not exact. 0.1 decimal, for instance, is 0.000110011001100110011... recurring. For more about the details of floating point numbers and the headaches they can cause you should read What every computer scientist should know about floating point.

To see why binary fractions cause problems, consider 1/3 + 1/3 + 1/3. Obviously that is equal to 1. But if you round to 3 decimal places you get 0.333 + 0.333 + 0.333 = 0.999. Look familiar?

Now try evaluating [0,0.1..1]

You can get approximately what you want by multiplying by 1e6, rounding to the nearest integer, and dividing by 1e6. You say you don't want to "parse" to an Int, but it's the only way to do this. The reason I say that you only get approximately what you want is that obviously if your number rounds to 0.1 then you still only get an approximation instead of the exact value.

A function to round to 6 decimal places in Haskell would be

round6dp :: Double -> Double
round6dp x = fromIntegral (round $ x * 1e6) / 1e6

> round6dp pi
3.141593

Also, you might try importing Data.Ratio and replacing the type Double with Rational in your code. Rational numbers are expressed as a ratio between two integers, so you can represent 1/3 exactly. Haskell uses % as the ratio operator to distinguish it from normal division, so you can evaluate 1%3 / 3%2. If you do this then your code gives the right answer.

Upvotes: 4

Daniel Wagner
Daniel Wagner

Reputation: 152697

You may used the Fixed type for arithmetic with a fixed precision after the decimal point. There is even a type synonym for six decimal digits of precision. For example, let's define:

x :: (Eq a, Fractional a) => a -> a
x 1 = 1
x n = ((n-1)/3 + 1/(n-1)) * ((x (n-1))^2) - (n-1)^3/3 + 1

Now we can try it in ghci with six digits of precision:

Data.Fixed> x <$> [1,2,3,4] :: [Micro]
[1.000000,2.000000,2.999998,3.999981]

Of course, using fewer digits of precision doesn't actually help! You can't fix rounding errors by using less precision, only more. So, why not go for full precision? Since you are only using operations that need Fractional, you can use Rational for exact arithmetic. In ghci:

> x <$> [1,2,3,4] :: [Rational]
[1 % 1,2 % 1,3 % 1,4 % 1]

Perfect! (Read % as division.)

Upvotes: 6

Claude
Claude

Reputation: 1014

As leftroundabout says, you should probably use Rational for your calculation to be exact. Rounding after the fact will cause troubles.

However, you can use Data.Number.BigFloat from the numbers package, it provides decimal floating point with a configurable precision. This answers your question of "how to round to 6 significant decimal figures", as long as you remain in a type precise enough (eg Rational or BigFloat with a same or higher precision). Converting back to Double (for example) will just give you problems again.

It may be useful in some circumstances to do exact calculations with Rational, then convert to Decimal6 only for rounding (and then back to Rational again).

Example code:

{-# LANGUAGE NoMonomorphismRestriction #-}
import Data.Number.BigFloat

type Prec6 = EpsDiv10 (EpsDiv10 (EpsDiv10 (EpsDiv10 (EpsDiv10 Eps1))))
type Decimal6 = BigFloat Prec6

decimal6 :: Decimal6 -> Decimal6
decimal6 = id

double :: Double -> Double
double = id

fixed = print . decimal6 . realToFrac

broken = print . double . realToFrac . decimal6 . realToFrac

examples :: [Double]
examples = [pi, exp pi, exp (exp pi), exp (negate (exp pi))]

x 1 = 1
x n = ((n-1)/3 + 1/(n-1)) * ((x (n-1))^2) - (n-1)^3/3 + 1

testx = print . x

main :: IO ()
main = do
  putStrLn "fixed:"  >> mapM_ fixed examples
  putStrLn "broken:" >> mapM_ broken examples
  putStrLn "Rational:" >> mapM_ testx [1 .. 3 :: Rational]
  -- no Enum instance for BigFloat, so can't use [..] syntax at that type
  putStrLn "Decimal6:" >> mapM_ (testx . decimal6 . fromInteger) [1 .. 3]

Output:

fixed:
3.14159e0
2.31407e1
1.12169e10
8.91509e-11
broken:
3.1415929203539825
23.14070351758794
1.1216931216931217e10
8.915094339622642e-11
Rational:
1 % 1
2 % 1
3 % 1
Decimal6:
1.00000e0
2.00000e0
3.00000e0

Upvotes: 2

mschmidt
mschmidt

Reputation: 2790

As already explained, your problem is the finite precision used to represent numbers in computers. For higher precision floating point numbers you might want to have a look at the package Data.Scientific

Upvotes: 0

leftaroundabout
leftaroundabout

Reputation: 120711

First off, that function will easily go wrong. You're recursing down a floating-point number; if you miss 1 it'll just continue decreasing, down to -∞ (or in fact, down to -9×1015, because from that point, decrementing by one won't actually change the float value anymore and it'll just loop on forever). So, it should actually be

x :: Double -> Double
x n
 | n>1        = ((n-1)/3 + 1/(n-1)) * ((x (n-1))^2) - (n-1)^3/3 + 1
 | otherwise  = 1

Next, if you're troubled by the deviations from the expected exact results... welcome to the world of floating point! There are plenty of good uses for FP, but in none of them it should matter if there's a discrepancy at the 15th decimal. If this does matter for you, you should not use any floats, but make the signature

x :: Rational -> Rational

and immediately the results will be exact, though they'll appear in notation that looks a bit weird:

*Main> x <$> [1..3]
[1 % 1,2 % 1,3 % 1]

Now, all that said, there are ways to just suppress the symptoms of floating point. What you've asked for, “rounding a number internally”, can be achieved like thus:

quantize₁₀ :: Int -> Double -> Double
quantize₁₀ n x = fromIntegral (round $ x * η) / η
 where η = 10^n

then (with x :: Double -> Double again)

*Main> quantize₁₀ 6 . x <$> [1..3]
[1.0,2.0,3.0]

I would strongly recommend against this – again, if you find this kind of manipulation necessary, you probably shouldn't be using floating-point numbers at all.

A more reasonable thing would be to just have the string output rounded, without doing anything about the numbers themselves. The traditional way for this is C's printf, which also exists in Haskell:

*Main Text.Printf> printf "%.6f\n" $ x 3
3.000000

Upvotes: 3

Related Questions