Floating point Elixir Enum.Sum和我的自定义和函数之间意外的浮点结果差异
作为家庭作业,我实现了以下Floating point Elixir Enum.Sum和我的自定义和函数之间意外的浮点结果差异,floating-point,elixir,Floating Point,Elixir,作为家庭作业,我实现了以下sum函数来返回数字列表的总和: defmodule Homework do @spec sum(list(number())) :: number() def sum(l) do case l do [] -> 0 [a | as] -> a + sum(as) end end end 作为单元测试,我使用了以下比较: [-2, -2.1524700989447303, 1] |> fn(l) -
sum
函数来返回数字列表的总和:
defmodule Homework do
@spec sum(list(number())) :: number()
def sum(l) do
case l do
[] -> 0
[a | as] -> a + sum(as)
end
end
end
作为单元测试,我使用了以下比较:
[-2, -2.1524700989447303, 1] |> fn(l) -> Enum.sum(l) === Homework.sum(l) end.()
此测试失败,返回false
。当我在iex
中运行函数时,我得到了以下结果,这让我感到惊讶:
iex(1)> [-2, -2.1524700989447303, 1] |> Enum.sum
-3.1524700989447307
iex(2)> [-2, -2.1524700989447303, 1] |> Homework.sum
-3.1524700989447303
此外,这两个函数分别一致地生成-3.1524700989447307
和-3.1524700989447303
为什么会出现这种差异
编辑
这个问题为我指明了正确的方向,但我认为这个问题的实际答案(OTP中的一个实现细节)可能也会引起一些人的兴趣。这个问题的答案启发我去源代码看看实现是如何的,当然,当参数是它使用的,它在命令head+累加器中应用函数,而不是在我的实现中应用累加器+head: 编辑 @斯奈夫特尔的评论让我做了以下实验:
@spec sum(list(number())) :: number()
def sum(l) do
case Enum.reverse(l) do # reversing first
[] -> 0
[a | as] -> a + sum(as)
end
end
此新版本的结果与Enum.sum
相同:
iex(1)> Homework.sum([-2, -2.1524700989447303, 1])
-3.1524700989447307
iex(2)> Enum.sum([-2, -2.1524700989447303, 1])
-3.1524700989447307
看来这是关于订单的
编辑2
当列表顺序不相反时,将a+sum(as)
更改为sum(as)+a
对结果没有影响
def sum(l) do
case l do
[] -> 0
[a | as] -> sum(as) + a
end
end
iex(1)> Homework.sum([-2, -2.1524700989447303, 1])
-3.1524700989447303
iex(2)> Enum.sum([-2, -2.1524700989447303, 1])
-3.1524700989447307
因此,当我们谈论“顺序”的相关性时,它是发生的顺序,而不是操作数的顺序 我认为这里真正的问题是,在处理浮点数时,不应该期望完全相等,因为它们本质上是不精确的。如果需要精度,则可以使用诸如的模块
defmodule Homework do
def sum([]), do: Decimal.new(0)
def sum([a | as]), do: Decimal.add(a, sum(as))
end
测试会话:
iex(1)> test = [Decimal.new(-2), Decimal.new("-2.1524700989447303"), Decimal.new(1)]
iex(2)> Homework.sum(test) === :lists.foldl(&Decimal.add/2, Decimal.new(0), test)
true
提供了如何有效使用浮点数的一个很好的概述。我认为它的顺序是:
[-2,-2.1524700989447303,1]|>Enum.reverse |>Enum.sum
生成-3.1524700989447303
@denis.peplin???这是出乎意料的:与acc+head
相比,差异的可能重复不是head+acc
。浮点加法是可交换的,因此这两种方法将产生相同的结果。相反,区别在于他们使用的是累加器。如果您还没有学习“尾部递归”,那么值得花时间研究一下,以理解为什么Erlang的方法比您的方法要有效得多。@Sneftel我做了一个实验,它似乎是关于顺序的。。。我肯定会看看“尾部递归”,我正在读的这本书是关于这些事情的:)它是关于顺序的,但在左折叠与右折叠的意义上,不是在操作数顺序的意义上。尝试将a+sum(as)
更改为sum(as)+a
,您将看到它对结果没有影响。@Sneftel您是对的:)。我想我现在的心态是对的。
defmodule Homework do
def sum([]), do: Decimal.new(0)
def sum([a | as]), do: Decimal.add(a, sum(as))
end
iex(1)> test = [Decimal.new(-2), Decimal.new("-2.1524700989447303"), Decimal.new(1)]
iex(2)> Homework.sum(test) === :lists.foldl(&Decimal.add/2, Decimal.new(0), test)
true