Haskell数据.四舍五入问题的小数



我想创建一个以两位小数为增量的1.0到2.0之间的所有实数的列表。这个

dList = [1.00,1.01..2.00]

但是,会产生浮动运行问题

dList = [1.0,1.01,1.02,1.03,1.04,1.05,1.06,1.07,1.08,1.09,1.1,1.11,1.12,
1.1300000000000001,1.1400000000000001,1.1500000000000001, ...

为了弥补这一点,我在Data.Decimal中发现了我认为的一个函数,即roundTo。我希望最终能运行这个

map (roundTo 2) [1.1,1.2..2.0]

并取消了float运行,但它会产生一个巨大的错误消息。这个页面是为初学者准备的。因此,我尝试使用在ghci REPL中加载的hs文件来完成此操作。这是代码

import Data.Decimal
dList :: [Decimal]
dList = [1.00,1.01..2.0]
main = print dList

它产生

Could not find module ‘Data.Decimal’

迷路了,我…

注意

对于所有初学者来说,这个答案是一个仅供参考的答案,他们只是想学习Haskell的一本初学者书,该书让你在文本编辑器中键入代码,启动ghci REPL并执行:load my-haskell-code.hs

YMMV解决方案

如上所述,Data.Decimal不是一个标准的Prelude类包。它必须独立加载——不,简单地将import Data.Decimal放在代码的顶部是行不通的。正如leftaroundabout在上面的评论中所说,对于还没有做项目的Haskell初学者来说,最简单的方法就是启动ghci

stack ghci --package Decimal

当然YMMV取决于您如何安装Haskell。我通过堆栈项目管理安装了Haskell,因此在ghci --package Decimal之前安装了stack。我的设置的另一个独特之处是,我使用的是Emacs.org模式的Babel代码块,它与基本类型和加载方式基本相同,即非项目。我确实试图通过添加--package Decimal来更改位于haskell-customize.el中的Emacs的haskell-process-args-stack-ghci,但没有成功。相反,我只是简单地转到bash命令行,输入stack ghci --package Decimal,然后重新启动一个单独的组织模式Babel-ghci,它就工作了。现在,

dList :: [Decimal]
dList = [1.00,1.01..2.00]
> dList
[1,1.01,1.02,1.03,1.04,1.05,1.06,1.07,1.08,1.09,1.10,1.11,1.12,1.13,1.14,1.15,1.16,1.17,1.18,1.19,1.20,1.21,1.22,1.23,1.24,1.25,1.26,1.27,1.28,1.29,1.30,1.31,1.32,1.33,1.34,1.35,1.36,1.37,1.38,1.39,1.40,1.41,1.42,1.43,1.44,1.45,1.46,1.47,1.48,1.49,1.50,1.51,1.52,1.53,1.54,1.55,1.56,1.57,1.58,1.59,1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68,1.69,1.70,1.71,1.72,1.73,1.74,1.75,1.76,1.77,1.78,1.79,1.80,1.81,1.82,1.83,1.84,1.85,1.86,1.87,1.88,1.89,1.90,1.91,1.92,1.93,1.94,1.95,1.96,1.97,1.98,1.99,2.00]

没有混乱,没有大惊小怪。我杀死了ghci,并在没有--package Decimal的情况下加载了它,它仍然知道Decimal,所以这个更改被准永久地记录在我的~/.stack目录中的某个地方?奇怪的是,bash-ghci会话不知道组织模式下的*haskell*ghci会话。此外,当只使用Emacs haskell模式独立进行类型和加载时,其ghci会话也不能很好地与组织模式ghci配合使用。我选择了极简主义的Emacs组织模式babel,因为它似乎比lhs字面上的Haskell更好。如果有人知道如何让哈斯克尔像组织模式一样唱歌,我很想知道。

尸检

我想我坚持要弄清楚Decimal,因为在研究整个舍入问题时,我开始看到建议的解决方案(不一定在这里,但在其他地方)和可怕的技术争论之间存在巨大分歧。Decimal似乎是竞争激烈的围捕策略风暴中最简单的。四舍五入应该很简单,但在哈斯克尔,它变成了一次穿越多个兔子洞的耗时之旅。

回答您的特定浮动问题

到目前为止,这种情况下最简单的选择是

[n/100 | n<-[0..200]]

或相同想法的变体:

map (/100) [0..200]
(*1e-2) . fromIntegral <$> [0 .. 200 :: Int] -- (*) is more efficient than (/),
-- but will actually introduce rounding
-- errors (non-accumulating) again

不需要任何特殊的十进制库或有理数。

这比[x₀, x₁ .. xe]方法更有效的原因是,低于252的整数可以精确地用浮点表示(而小数不能)。因此,[0..200]的范围正是你想要的。然后,在最后,将这些数字中的每一个除以100仍然不会给你想要得到的百分之一的精确表示——因为这样的表示不存在——但你会为每个元素得到尽可能接近的近似值。事实上,最接近的近似值是以x.yz的形式打印的,即使使用标准的print函数也是如此。相比之下,在[1.00,1.01..2.0]中,您不断将已经近似的值相加,从而使误差复合。

也可以在精确的有理数中使用原始范围计算,然后将其转换为浮点–这仍然不需要十进制库

map fromRational [0, 0.01 .. 2]

推理算术通常是解决类似问题的简单方法,但我倾向于建议不要这样做,因为它的规模通常很差。在浮点运算中存在舍入问题的算法通常会在有理算术中遇到内存问题,因为在整个计算过程中需要不断增加的精度范围。更好的解决方案是首先避免像n/100建议那样需要精度。

此外,可以说[x₀, x₁ .. xe]语法无论如何都是一个坏设计;整数范围的语义要清晰得多。

还请注意,原始尝试中的浮点错误不一定是问题。对于所有有意义的目的来说,真实世界测量量中10-9的误差是可以忽略的。如果你需要一些真正精确的东西,你可能根本不应该使用分数值,而是使用直整数。因此,考虑Carl的建议,即只接受浮动偏差,但只需通过showFFloatprintfText.Show.Pragmatic.print以适当的舍入形式打印


实际上,在这种特定情况下,两种解决方案几乎相等,因为将Rational转换为浮点涉及分子除以分母的浮点运算

模块加载问题的答案

如果您确实需要Decimal库(或其他库),则需要依赖它

  • 最简单的方法是使用Stack并将Decimal添加到全局项目中。然后,您可以用stack ghci加载文件,它就会知道在哪里查找Data.Decimal

  • 或者,最好是IMO,您应该自己创建一个项目包,并仅使其依赖于Decimal。这可以通过Stack或Cabal安装来完成。

    $ mkdir my-decimal-project
    $ cd my-decimal-project
    $ cabal init
    

    现在有人问你一些关于项目名称等的问题,你可以用默认值来回答。假设您的项目定义了一个(如果需要,您可以稍后添加一个可执行文件。)

    CCD_ 37创建CCD_。在该文件中,将Decimal添加到依赖项,并将您自己的源文件添加到exposed-modules

    然后,您需要(仍在项目目录中)cabal install --dependencies-only来获取Decimal库,然后cabal repl来加载模块。

最新更新