3

I'm practicing Haskell by writing a program that will convert a price in cents into dollars. Obviously, this means that my Int will need to become a Double, but I want my final output to be that of a String. My code:

First, I defined my Price type:

type Price = Int

Then, I wrote my module:

formatDollars :: Price -> String
formatDollars x = show (fromIntegral (x `div` 100))

My test input for x was 188, which should give me 1.88.

But my output is always "1". This makes me think that my fromIntegral function might not be utilized correctly in my code, as if it's truncating after the decimal. Since I'm so new to Haskell, I'm sure it's a simple mistake; could someone help me out?

2
  • 3
    div is integer division, so from that point, the value is gone. Commented Oct 11, 2018 at 8:09
  • 2
    It's not so obvious it should ever be a Double; in fact, you should probably use a Data.Fixed.Centi. Commented Oct 11, 2018 at 11:19

2 Answers 2

7
188 `div` 100 == 1

You want to convert to Double before doing the division. (And then do real division, not integer division.)

formatDollars x = show ((fromIntegral x) / 100)
Sign up to request clarification or add additional context in comments.

Comments

6

div is integer division. So that means that div 188 100, you get:

Prelude> div 188 100
1

So that means that after the div, the 88 is "gone". Furthermore div truncates to negative infinity. As a result if the number is negative we get:

Prelude> div (-210) 100
-3

But you probably better do not convert the number to a Float anyway: Floats are not capable to precisely represent decimal numbers: approximations are used. For example if you write 0.82, a value will be stored that looks like 0.8200000000000001 (although this is again not the exact representation). In case the number is rather large, the mantissa is not always capable to represent the entire number, so there can be an error in the conversion. Finally if you print small or large numbers, by default show will use scientific notation (this is of course not "inherently" a problem, but I think it is rather odd if you print a price, I've never seen a supermarket with labels like $ 9e-2):

Prelude> 0.01 :: Float
1.0e-2

Which is probably not what you want.

You can simply split the number in three parts: the value before the decimal dot, and the two digits after the decimal dot. Like:

import Data.Char(intToDigit)

formatDollars :: Price -> String
formatDollars pr = show pr0 ++ ['.', pr1, pr2]
    where pr0 = quot pr 100
          digit n = intToDigit (abs (rem n 10))
          pr1 = digit (quot pr 10)
          pr2 = digit pr

The Decimal [hackage] package might also be an option. A DecimalRaw is represented in such a way that for the decimal system, it can represent numbers precisely. For example:

Prelude> import Data.Decimal
Prelude Data.Decimal> Decimal 2 188
1.88 

So we can avoid all the trouble, and write it like:

import Data.Decimal(DecimalRaw(Decimal))

formatDollars :: Price -> String
formatDollars = show . Decimal 2

2 Comments

I believe there's some number formatting stuff in Numeric (?) as well...
@MathematicalOrchid: yes like showHFloat, but I do not really like the RealFoat constraint here, since then again we are in the "floating point" world. Especially for prices, calculating with floating points can result in summing up values to values that not completely correct.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.