Haskell Data.Decimal 四舍五入的问题?
Haskell Data.Decimal for rounding issue?
我想创建一个列表,其中包含 1.0 到 2.0 之间的所有实数,以两位小数为增量。这个
dList = [1.00,1.01..2.00]
然而,创建浮动 运行-on 问题
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]
并摆脱浮动 运行-on,但它会产生巨大的错误消息。 This page 对于这个初学者来说是难以理解的。因此,我尝试使用在 ghci REPL 加载的 hs
文件来执行此操作。这是代码
import Data.Decimal
dList :: [Decimal]
dList = [1.00,1.01..2.0]
main = print dList
它产生
Could not find module ‘Data.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 的建议,即只接受浮动偏差,但只需通过 showFFloat
, printf
, or Text.Show.Pragmatic.print
.
以适当的圆形形式 打印 它们
★实际上,在这种特定情况下,两种解决方案几乎是等效的,因为将 Rational
转换为浮点数涉及浮点数除以分子分母.
模块加载问题的答案
如果您确实需要 Decimal
库(或其他一些库),则需要 依赖。
最简单的方法是使用 Stack 并将 Decimal
添加到您的 全局项目 。然后,您可以使用 stack ghci
加载文件,它会知道在哪里寻找 Data.Decimal
.
或者,IMO 最好,您应该自己创建一个 项目包 ,并且只依赖于 Decimal
。这可以通过 Stack 或 Cabal-install.
来完成
$ mkdir my-decimal-project
$ cd my-decimal-project
$ cabal init
现在你被问到一些关于项目名称等的问题,你大部分都可以使用默认值来回答。假设您的项目定义了一个 库 (如果需要,您可以稍后添加可执行文件。)
cabal init
创建了一个 my-decimal-project.cabal
文件。在该文件中,将 Decimal
添加到依赖项,并将您自己的源文件添加到 exposed-modules
.
然后您需要(仍在您的项目目录中)cabal install --dependencies-only
获取 Decimal
库,然后 cabal repl
加载您的模块。
备注
这个答案仅供所有初学者参考,只是想按照一本初学者 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,因此 stack
在 ghci --package Decimal
之前。关于我的设置的另一个独特之处是我正在使用 Emacs org-mode 的 Babel 代码块,它与基本类型和加载方式基本相同,即非项目。我确实尝试通过添加 --package Decimal
来改变 Emacs 的 haskell-process-args-stack-ghci
,它位于 haskell-customize.el
中,但它没有用。相反,我只是转到我的 bash 命令行并输入 stack ghci --package Decimal
,然后我重新启动了一个单独的 org-mode 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 会话不知道 org-mode 中的 *haskell*
ghci 会话。此外,当仅使用 Emacs haskell 模式独立进行类型和加载时,其 ghci 会话也不能很好地与 org-mode ghci 配合使用。我选择了极简主义的 Emacs org-mode babel,因为它看起来比 lhs
文字 Haskell 更好。如果有人知道如何让文字 Haskell 像组织模式一样唱歌,我很想知道。
事后分析
我想我坚持要弄清楚 Decimal
因为在研究整个舍入问题时,我开始看到建议的解决方案(不一定在这里,但其他地方)和可怕的技术争论的巨大分歧。 Decimal
似乎是竞争舍入策略的狂风暴雨中最简单的一个。四舍五入应该很简单,但在 Haskell 中,它变成了一个耗时的多个兔子洞之旅。
我想创建一个列表,其中包含 1.0 到 2.0 之间的所有实数,以两位小数为增量。这个
dList = [1.00,1.01..2.00]
然而,创建浮动 运行-on 问题
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]
并摆脱浮动 运行-on,但它会产生巨大的错误消息。 This page 对于这个初学者来说是难以理解的。因此,我尝试使用在 ghci REPL 加载的 hs
文件来执行此操作。这是代码
import Data.Decimal
dList :: [Decimal]
dList = [1.00,1.01..2.0]
main = print dList
它产生
Could not find module ‘Data.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 的建议,即只接受浮动偏差,但只需通过 showFFloat
, printf
, or Text.Show.Pragmatic.print
.
★实际上,在这种特定情况下,两种解决方案几乎是等效的,因为将 Rational
转换为浮点数涉及浮点数除以分子分母.
模块加载问题的答案
如果您确实需要 Decimal
库(或其他一些库),则需要 依赖。
最简单的方法是使用 Stack 并将
Decimal
添加到您的 全局项目 。然后,您可以使用stack ghci
加载文件,它会知道在哪里寻找Data.Decimal
.或者,IMO 最好,您应该自己创建一个 项目包 ,并且只依赖于
来完成Decimal
。这可以通过 Stack 或 Cabal-install.$ mkdir my-decimal-project $ cd my-decimal-project $ cabal init
现在你被问到一些关于项目名称等的问题,你大部分都可以使用默认值来回答。假设您的项目定义了一个 库 (如果需要,您可以稍后添加可执行文件。)
cabal init
创建了一个my-decimal-project.cabal
文件。在该文件中,将Decimal
添加到依赖项,并将您自己的源文件添加到exposed-modules
.然后您需要(仍在您的项目目录中)
cabal install --dependencies-only
获取Decimal
库,然后cabal repl
加载您的模块。
备注
这个答案仅供所有初学者参考,只是想按照一本初学者 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,因此 stack
在 ghci --package Decimal
之前。关于我的设置的另一个独特之处是我正在使用 Emacs org-mode 的 Babel 代码块,它与基本类型和加载方式基本相同,即非项目。我确实尝试通过添加 --package Decimal
来改变 Emacs 的 haskell-process-args-stack-ghci
,它位于 haskell-customize.el
中,但它没有用。相反,我只是转到我的 bash 命令行并输入 stack ghci --package Decimal
,然后我重新启动了一个单独的 org-mode 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 会话不知道 org-mode 中的 *haskell*
ghci 会话。此外,当仅使用 Emacs haskell 模式独立进行类型和加载时,其 ghci 会话也不能很好地与 org-mode ghci 配合使用。我选择了极简主义的 Emacs org-mode babel,因为它看起来比 lhs
文字 Haskell 更好。如果有人知道如何让文字 Haskell 像组织模式一样唱歌,我很想知道。
事后分析
我想我坚持要弄清楚 Decimal
因为在研究整个舍入问题时,我开始看到建议的解决方案(不一定在这里,但其他地方)和可怕的技术争论的巨大分歧。 Decimal
似乎是竞争舍入策略的狂风暴雨中最简单的一个。四舍五入应该很简单,但在 Haskell 中,它变成了一个耗时的多个兔子洞之旅。