Rounding to a certain number of digits in Haskell

I am trying to make a function to round a floating point number to a specific length of digits. So far I have come to the following:

import Numeric; digs :: Integral x => x -> [x] <br> digs 0 = [] <br> digs x = digs (x `div` 10) ++ [x `mod` 10] roundTo xt = let d = length $ digs $ round x <br> roundToMachine xt = (fromInteger $ round $ x * 10^^t) * 10^^(-t) in roundToMachine x (t - d) 

I use the digs function to determine the number of digits before the decimal point in order to optimize the input value (i.e. move everything through a comma, so 1.234 becomes 0.1234 * 10^1 )

The roundTo function seems to work for most inputs, however for some inputs I get strange results, for example. roundTo 1.0014 4 creates 1.0010000000000001 instead of 1.001 .

The problem in this example is caused by calculating 1001 * 1.0e-3 (which returns 1.0010000000000001 )

Is this just a problem in the Haskell numeric representation that I have to live with, or is there a better way to round a floating point number to a specific length of digits?

+7
math haskell
source share
2 answers

This is not a haskell problem, but a floating point problem. Since each floating point number is implemented in a finite number of bits, there are numbers that cannot be represented exactly. You can also see this by calculating 0.1 + 0.2 , which inconveniently returns 0.30000000000000004 instead of 0.3 . This is due to how floating point numbers are implemented for your language and hardware architecture.

The solution is to continue using your roundTo function to perform the calculations (this is exactly the same as you would get without special libraries), but if you want to print it on the screen, then you should use line formatting like Text.Printf.printf . You can specify the number of digits to round to conversion to a string with something like

 import Text.Printf roundToStr :: (PrintfArg a, Floating a) => Int -> a -> String roundToStr nf = printf ("%0." ++ show n ++ "f") f 

But, as I mentioned, this will return a string, not a number.

EDIT:

The best way could be

 roundToStr :: (PrintfArg a, Floating a) => Int -> a -> String roundToStr nf = printf (printf "%%0.%df" n) f 

but I have not tested, which is actually faster. Both will work the exact same way though.

EDIT 2:

As @augustss pointed out, you can make it even easier, just

 roundToStr :: (PrintfArg a, Floating a) => Int -> a -> String roundToStr = printf "%0.*f" 

which uses a formatting rule that I did not know about before.

+7
source share

I understand that this question was published almost 2 years ago, but I thought that I have an answer that does not require string conversion.

 -- x : number you want rounded, n : number of decimal places you want... truncate' :: Double -> Int -> Double truncate' xn = (fromIntegral (floor (x * t))) / t where t = 10^n -- How to answer your problem... λ truncate' 1.0014 3 1.001 -- 2 digits of a recurring decimal please... λ truncate' (1/3) 2 0.33 -- How about 6 digits of pi? λ truncate' pi 6 3.141592 

I have not tested it completely, so if you find the numbers, this does not work, let me know!

+10
source share

All Articles