我正在通过编写一个程序来练习Haskell,该程序会将美分的价格转换为美元。显然,这意味着我的Int将需要成为Double,但是我希望最终输出为String。我的代码:
首先,我定义了Price
类型:
type Price = Int
然后,我编写了我的模块:
formatDollars :: Price -> String
formatDollars x = show (fromIntegral (x `div` 100))
我对x
的测试输入为188,应为1.88。
但是我的输出始终为"1"
。这使我认为我的fromIntegral函数可能无法在我的代码中正确使用,就好像它在小数点后被截断了一样。由于我对Haskell非常陌生,因此我敢肯定这是一个简单的错误;有人可以帮我吗?
答案 0 :(得分:7)
188 `div` 100 == 1
您要在进行除法之前转换为Double
。 (然后进行实数除法,而不是整数除法。)
formatDollars x = show ((fromIntegral x) / 100)
答案 1 :(得分:6)
div
是整数的除法。因此,这意味着div 188 100
您将获得:
Prelude> div 188 100
1
因此,这意味着在div
之后,88
已“消失”。此外,div
会截断为负无穷大。结果,如果数字为负,我们得到:
Prelude> div (-210) 100
-3
但是您最好还是不要将数字转换为Float
:Float
不能精确表示十进制数字:使用了近似值。例如,如果您编写0.82
,将存储一个看起来像0.8200000000000001
的值(尽管这又不是精确的表示形式)。如果数字很大,则尾数并不总是能够代表整个数字,因此转换中可能会出现错误。最后,如果您打印较小或较大的数字,默认情况下show
将使用科学计数法(当然这不是“固有”的问题,但是如果您打印价格,我认为这很奇怪,我从未见过带有$ 9e-2
这样的标签的超市:
Prelude> 0.01 :: Float
1.0e-2
可能不是您想要的。
您可以简单地将数字分为三部分:小数点前的值和小数点后的两位。喜欢:
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
Decimal
[hackage]软件包也可能是一个选项。 DecimalRaw
的表示方式对于十进制系统而言,它可以精确地表示数字。例如:
Prelude> import Data.Decimal
Prelude Data.Decimal> Decimal 2 188
1.88
因此,我们可以避免所有麻烦,并将其编写为:
import Data.Decimal(DecimalRaw(Decimal))
formatDollars :: Price -> String
formatDollars = show . Decimal 2