6

I have a function in Haskell which looks like this

type Price    = Int

formatPence :: Price -> String
formatPence a = show (a `div` 100) ++ "." ++ show(a `mod` 100)

so that, for example, if I input formatPence 1023, the output would be "10.23". But I have a problem if I input 1202 because the output would be "12.2". What should I add? Thanks :)

Siti Aisyah
  • 71
  • 1
  • 3

3 Answers3

12

Perhaps you wanted one of the various show*Float functions in Numeric?

> :m Numeric
> showFFloat (Just 2) 123.456 ""
123.45
MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • This does not answer the question, he wants a function with type "Float -> String" this avoids that step which makes this solution useless... what if he wants to print to a file. – Famous Jameis Oct 10 '20 at 01:12
  • For such a function, just abstract out the float: `(\f -> showFFloat (Just 2) f "") :: Float -> String` – jlwoodwa Dec 23 '21 at 17:02
10

That's a standard problem that people have had ever since the infancy of computers. Hence it's perfectly well solved by the age-old printf (which Haskell more or less copied from C).

import Text.Printf

formatPence = printf "%.2f" . (/100) . fromIntegral

Oh, to note... this has a precision problem for very large amounts, because Double (which is implicitly used for the division) doesn't have as high resolution as Int.

Prelude Text.Printf> formatPence 10000000000000013
"100000000000000.13"
Prelude Text.Printf> formatPence 100000000000000013
"1000000000000000.10"
Prelude Text.Printf> formatPence 1000000000000000013
"10000000000000000.00"

So if you're dealing with amounts of multiple trillion dollars, better not use this.

(I suppose if you were dealing with such amounts, you probably wouldn't ask this question here... and you wouldn't be using Int.)

To fix this, you can use your original approach, but still use printf to format the extra zero:

type Price' = Integer

formatPence' :: Price' -> String
formatPence' a = show (a `div` 100) ++ "." ++ printf "%02u" (a `mod` 100) 

This will then work with arbitrarily ludicrous amounts:

> formatPence' 1000000000000000103
"10000000000000001.03"
> formatPence' 6529857623987620347562395876204395876395762398417639852764958726398527634972365928376529384
"65298576239876203475623958762043958763957623984176398527649587263985276349723659283765293.84"

Note that the manual div/mod leads in a problem for negative amounts, but that's easy to fix.

amalloy
  • 89,153
  • 8
  • 140
  • 205
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • 2
    @SitiAisyah, Calling `printf` a perfect solution to anything is going way too far. I think the [`formatting` package](https://hackage.haskell.org/package/formatting) offers a much better replacement for C's `printf`. It avoids `printf`'s error-prone runtime format string processing nonsense in favor of a strongly typed approach; the downside is that you may get impenetrable error messages from the compiler when you goof up. I'd personally appreciate a nice big collection of really idiomatic, well-named `blah -> String` functions, but I'm not sure if anyone has one. – dfeuer Mar 29 '16 at 17:22
  • 1
    Agreed. I'm _not_ a great proponent of `printf` with its weird variadic signature, which is basically typechecked only at runtime; but for simple formatting of single numbers its quite sufficient and it's in `base`, so, for this particular task I recommend it. – leftaroundabout Mar 29 '16 at 18:57
0

You need to pad the part after the decimal with a zero.

twoDP :: Int -> String
twoDP v = if length str == 1 then '0':str else str
   where str = show v

You could write a more general padding function:

leadingZeros :: Int -> Int -> String
leadingZeros n v = replicate (n - length str) '0' ++ str
   where str = show v

Don't mess around with floating point versions of "show" or "printf" for this kind of thing: that way lies madness.

Paul Johnson
  • 17,438
  • 3
  • 42
  • 59