浮点 – Enum.Sum和我的自定义和函数之间的Elixir意外浮点结果差异

作为家庭作业,我已经实现了以下求和函数来返回数字列表的总和:

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.

为什么会出现这种差异?

编辑

问题Why does changing the sum order returns a different result?指出了我正确的方向,但我认为这个问题的实际答案(OTP中的实施细节)也可能对某人有意义.

最佳答案
这个问题Why does changing the sum order returns a different result?的答案启发我去源头看看实现是如何的,当然,当参数是list时它使用Erlang’s implementation of foldl,它在头部累加器中应用函数而不是像我的实现中的累加器头一样:

https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/enum.ex

@spec sum(t) :: number
def sum(enumerable) do
  reduce(enumerable, 0, &+/2)
end

@spec reduce(t, any, (element, any -> any)) :: any
def reduce(enumerable, acc, fun) when is_list(enumerable) do
  :lists.foldl(fun, acc, enumerable)
end

https://github.com/erlang/otp/blob/master/lib/stdlib/src/lists.erl

-spec foldl(Fun, Acc0, List) -> Acc1 when
      Fun :: fun((Elem :: T, AccIn) -> AccOut),
      Acc0 :: term(),
      Acc1 :: term(),
      AccIn :: term(),
      AccOut :: term(),
      List :: [T],
      T :: term().

foldl(F, Accu, [Hd|Tail]) ->
    foldl(F, F(Hd, Accu), Tail); %% here!
foldl(F, Accu, []) when is_function(F, 2) -> Accu.

编辑

@Sneftel的评论让我做了以下实验:

@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

当列表不是以相反顺序时,将和(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

因此,当我们谈论“秩序”的相关性时,它就是folding发生的顺序,而不是操作数的顺序.

转载注明原文:浮点 – Enum.Sum和我的自定义和函数之间的Elixir意外浮点结果差异 - 代码日志