11

In a program, I calculate a percentage and print it to the user. The issue is that the percentage printed is shown with too many decimals. I searched for a function addressing this issue, didn't find one, and programmed the rounding function below, but I wonder if there is a more standard way to do this instead. Also, the idea described in this Java topic is pretty neat, and if there already are functions to do that, I would love to know.

roundTo :: (Integral int1, Integral int2) => int1 -> Double -> Either int2 Double
roundTo digitsAfterComma value
    | (digitsAfterComma >= 1) =
        let
            factor = fromIntegral (10 ^ digitsAfterComma)
            result = ((/ factor) . fromIntegral . round . (* factor)) value
        in
            Right result
    | (digitsAfterComma == 0) = Left (round value)
    | otherwise = error "minimal precision must be non-negative"

Update: A more complete version follows, developed thanks to some of the answers I've received.

import qualified Text.Printf as T

showPercentage :: (Integral a, Show a) => a -> Double -> String
showPercentage digitsAfterComma fraction
    | (digitsAfterComma >= 1) =
        let formatString = "%." ++ (show digitsAfterComma) ++ "f%%"
        in  T.printf formatString percentage

    | (digitsAfterComma == 0) =
        let formatString = "%d%%"
        in  T.printf formatString (round percentage :: Int)

    | otherwise = error "minimal precision must be non-negative"
    where percentage = 100 * fraction
Fornost
  • 153
  • 2
  • 9

3 Answers3

12
> import Text.Printf
> printf "%.2f" 0.22324 :: String
"0.22"

You can use most format strings C's printf supports.

Keep in mind, however, that Haskell's printf involves some complex typeclass machinery, and can generate hard-to-read type errors. It is also very general, since it can also return IO actions, i.e.

> printf "%.2f" 0.22324 :: IO ()
0.22

does not return a string but directly prints it. I'd recommend you always add a type annotation (such as :: String above) after each call of printf, unless it's clear from the context what is the return type (e.g. in a do block with other IO actions).

Nathan Davis
  • 5,636
  • 27
  • 39
chi
  • 111,837
  • 3
  • 133
  • 218
8

printf is solid, and another library worth knowing about is formatting (which is based off of the lovely HoleyMonoid library):

Prelude Formatting> format ("to two decimal places: " % prec 2 % "!") 0.2222
"to two decimal places: 0.22!"

Note that formatting is type-safe, unlike printf:

Prelude Text.Printf Formatting> :t printf "%.2f" "hi"
printf "%.2f" "hi" :: PrintfType t => t
Prelude Text.Printf Formatting> printf "%.2f" "hi"
*** Exception: printf: bad formatting char 'f'

Prelude Text.Printf Formatting> :t format (prec 2) "hi"
-- type error
jtobin
  • 3,253
  • 3
  • 18
  • 27
1

Here's a function to display a floating point number used to represent a percentage. It doesn't include the % symbol, but you could prepend that. It's validating the number is between 0 and 100 because this function would produce incorrect results for a number like 1e7.

showPercent :: Double -> Maybe Text
showPercent d
  | d < 0 = Nothing
  | d > 100 = Nothing
  | otherwise = Just . pack $ f d
  where
    f x = reverse $ case reverse (show x) of
      '0':'.':rest -> rest
      rest         -> rest

Results:

λ showPercent (-5)
Nothing

λ showPercent 0
Just "0"

λ showPercent 5
Just "5"

λ showPercent 10.5
Just "10.5"

λ showPercent 15.765
Just "15.765"

λ showPercent 35.123000000
Just "35.123"

λ showPercent 51.123000321000
Just "51.123000321"

λ showPercent 200
Nothing
Jezen Thomas
  • 13,619
  • 6
  • 53
  • 91