Reputation: 794
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
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
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
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
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
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
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