Elixir Unexpected Floating Point Result Difference Between Enum.Sum and My Custom Sum Function Announcing the arrival of Valued Associate #679: Cesar Manara Planned maintenance scheduled April 17/18, 2019 at 00:00UTC (8:00pm US/Eastern) The Ask Question Wizard is Live! Data science time! April 2019 and salary with experience Should we burninate the [wrap] tag?Why does changing the sum order returns a different result?Difference between decimal, float and double in .NET?What is the difference between float and double?Speed comparison with Project Euler: C vs Python vs Erlang vs HaskellRuby Floating Point Math - Issue with Precision in Sum CalcWhy does these two code variants produce different floating-point results?Why does changing the sum order returns a different result?Char lists code point atomDividing 2 floating point numbers in C++ produces different results depending on the method usedAre Elixir structs really immutable?Sum of array of floats returns different results

Difference between these two cards?

What LEGO pieces have "real-world" functionality?

Sorting numerically

Is it true that "carbohydrates are of no use for the basal metabolic need"?

What are the pros and cons of Aerospike nosecones?

How can players work together to take actions that are otherwise impossible?

What do you call a plan that's an alternative plan in case your initial plan fails?

Should I call the interviewer directly, if HR aren't responding?

What are 'alternative tunings' of a guitar and why would you use them? Doesn't it make it more difficult to play?

Why did the IBM 650 use bi-quinary?

Does accepting a pardon have any bearing on trying that person for the same crime in a sovereign jurisdiction?

Why is "Captain Marvel" translated as male in Portugal?

Storing hydrofluoric acid before the invention of plastics

Does surprise arrest existing movement?

How can I fade player when goes inside or outside of the area?

Bonus calculation: Am I making a mountain out of a molehill?

3 doors, three guards, one stone

How can I make names more distinctive without making them longer?

How to deal with a team lead who never gives me credit?

When -s is used with third person singular. What's its use in this context?

Why does Python start at index -1 when indexing a list from the end?

Can Pao de Queijo, and similar foods, be kosher for Passover?

Is the Standard Deduction better than Itemized when both are the same amount?

What would be the ideal power source for a cybernetic eye?



Elixir Unexpected Floating Point Result Difference Between Enum.Sum and My Custom Sum Function



Announcing the arrival of Valued Associate #679: Cesar Manara
Planned maintenance scheduled April 17/18, 2019 at 00:00UTC (8:00pm US/Eastern)
The Ask Question Wizard is Live!
Data science time! April 2019 and salary with experience
Should we burninate the [wrap] tag?Why does changing the sum order returns a different result?Difference between decimal, float and double in .NET?What is the difference between float and double?Speed comparison with Project Euler: C vs Python vs Erlang vs HaskellRuby Floating Point Math - Issue with Precision in Sum CalcWhy does these two code variants produce different floating-point results?Why does changing the sum order returns a different result?Char lists code point atomDividing 2 floating point numbers in C++ produces different results depending on the method usedAre Elixir structs really immutable?Sum of array of floats returns different results



.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;








3















As a homework, I've implemented the following sum function to return the sum of a list of numbers:



defmodule Homework do
@spec sum(list(number())) :: number()
def sum(l) do
case l do
[] -> 0
[a | as] -> a + sum(as)
end
end
end


And as a unit test I've used the following comparison:



[-2, -2.1524700989447303, 1] |> fn(l) -> Enum.sum(l) === Homework.sum(l) end.()


And this test fails, returning false. When I ran the functions in iex I got the following result, which is surprising for me:



iex(1)> [-2, -2.1524700989447303, 1] |> Enum.sum
-3.1524700989447307
iex(2)> [-2, -2.1524700989447303, 1] |> Homework.sum
-3.1524700989447303


What is more, both functions consistently generate -3.1524700989447307 and -3.1524700989447303, respectively.



Why does this difference happens?



Edit



The question Why does changing the sum order returns a different result? pointed me in the right direction but I think that the actual answer to this question (an implementation detail in OTP) could be interesting to someone as well.










share|improve this question



















  • 1





    I think it's order: [-2, -2.1524700989447303, 1] |> Enum.reverse |> Enum.sum generates -3.1524700989447303

    – denis.peplin
    Mar 8 at 16:18











  • @denis.peplin ??? that's unexpected :D

    – Marcus Vinícius Monteiro
    Mar 8 at 16:20












  • Possible duplicate of Why does changing the sum order returns a different result?

    – Sneftel
    Mar 8 at 17:55

















3















As a homework, I've implemented the following sum function to return the sum of a list of numbers:



defmodule Homework do
@spec sum(list(number())) :: number()
def sum(l) do
case l do
[] -> 0
[a | as] -> a + sum(as)
end
end
end


And as a unit test I've used the following comparison:



[-2, -2.1524700989447303, 1] |> fn(l) -> Enum.sum(l) === Homework.sum(l) end.()


And this test fails, returning false. When I ran the functions in iex I got the following result, which is surprising for me:



iex(1)> [-2, -2.1524700989447303, 1] |> Enum.sum
-3.1524700989447307
iex(2)> [-2, -2.1524700989447303, 1] |> Homework.sum
-3.1524700989447303


What is more, both functions consistently generate -3.1524700989447307 and -3.1524700989447303, respectively.



Why does this difference happens?



Edit



The question Why does changing the sum order returns a different result? pointed me in the right direction but I think that the actual answer to this question (an implementation detail in OTP) could be interesting to someone as well.










share|improve this question



















  • 1





    I think it's order: [-2, -2.1524700989447303, 1] |> Enum.reverse |> Enum.sum generates -3.1524700989447303

    – denis.peplin
    Mar 8 at 16:18











  • @denis.peplin ??? that's unexpected :D

    – Marcus Vinícius Monteiro
    Mar 8 at 16:20












  • Possible duplicate of Why does changing the sum order returns a different result?

    – Sneftel
    Mar 8 at 17:55













3












3








3








As a homework, I've implemented the following sum function to return the sum of a list of numbers:



defmodule Homework do
@spec sum(list(number())) :: number()
def sum(l) do
case l do
[] -> 0
[a | as] -> a + sum(as)
end
end
end


And as a unit test I've used the following comparison:



[-2, -2.1524700989447303, 1] |> fn(l) -> Enum.sum(l) === Homework.sum(l) end.()


And this test fails, returning false. When I ran the functions in iex I got the following result, which is surprising for me:



iex(1)> [-2, -2.1524700989447303, 1] |> Enum.sum
-3.1524700989447307
iex(2)> [-2, -2.1524700989447303, 1] |> Homework.sum
-3.1524700989447303


What is more, both functions consistently generate -3.1524700989447307 and -3.1524700989447303, respectively.



Why does this difference happens?



Edit



The question Why does changing the sum order returns a different result? pointed me in the right direction but I think that the actual answer to this question (an implementation detail in OTP) could be interesting to someone as well.










share|improve this question
















As a homework, I've implemented the following sum function to return the sum of a list of numbers:



defmodule Homework do
@spec sum(list(number())) :: number()
def sum(l) do
case l do
[] -> 0
[a | as] -> a + sum(as)
end
end
end


And as a unit test I've used the following comparison:



[-2, -2.1524700989447303, 1] |> fn(l) -> Enum.sum(l) === Homework.sum(l) end.()


And this test fails, returning false. When I ran the functions in iex I got the following result, which is surprising for me:



iex(1)> [-2, -2.1524700989447303, 1] |> Enum.sum
-3.1524700989447307
iex(2)> [-2, -2.1524700989447303, 1] |> Homework.sum
-3.1524700989447303


What is more, both functions consistently generate -3.1524700989447307 and -3.1524700989447303, respectively.



Why does this difference happens?



Edit



The question Why does changing the sum order returns a different result? pointed me in the right direction but I think that the actual answer to this question (an implementation detail in OTP) could be interesting to someone as well.







floating-point erlang elixir






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Mar 8 at 19:10







Marcus Vinícius Monteiro

















asked Mar 8 at 16:14









Marcus Vinícius MonteiroMarcus Vinícius Monteiro

1,79731830




1,79731830







  • 1





    I think it's order: [-2, -2.1524700989447303, 1] |> Enum.reverse |> Enum.sum generates -3.1524700989447303

    – denis.peplin
    Mar 8 at 16:18











  • @denis.peplin ??? that's unexpected :D

    – Marcus Vinícius Monteiro
    Mar 8 at 16:20












  • Possible duplicate of Why does changing the sum order returns a different result?

    – Sneftel
    Mar 8 at 17:55












  • 1





    I think it's order: [-2, -2.1524700989447303, 1] |> Enum.reverse |> Enum.sum generates -3.1524700989447303

    – denis.peplin
    Mar 8 at 16:18











  • @denis.peplin ??? that's unexpected :D

    – Marcus Vinícius Monteiro
    Mar 8 at 16:20












  • Possible duplicate of Why does changing the sum order returns a different result?

    – Sneftel
    Mar 8 at 17:55







1




1





I think it's order: [-2, -2.1524700989447303, 1] |> Enum.reverse |> Enum.sum generates -3.1524700989447303

– denis.peplin
Mar 8 at 16:18





I think it's order: [-2, -2.1524700989447303, 1] |> Enum.reverse |> Enum.sum generates -3.1524700989447303

– denis.peplin
Mar 8 at 16:18













@denis.peplin ??? that's unexpected :D

– Marcus Vinícius Monteiro
Mar 8 at 16:20






@denis.peplin ??? that's unexpected :D

– Marcus Vinícius Monteiro
Mar 8 at 16:20














Possible duplicate of Why does changing the sum order returns a different result?

– Sneftel
Mar 8 at 17:55





Possible duplicate of Why does changing the sum order returns a different result?

– Sneftel
Mar 8 at 17:55












2 Answers
2






active

oldest

votes


















3














The answer to this question Why does changing the sum order returns a different result? inspired me go to the source to see how the implementation was and, surely enough, when the parameter is a list it uses Erlang's implementation of foldl, which applies the function in the order head + accumulator instead of accumulator + head like in my implementation:



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.


Edit



@Sneftel's comment made me do the following experiment:



@spec sum(list(number())) :: number()
def sum(l) do
case Enum.reverse(l) do # reversing first
[] -> 0
[a | as] -> a + sum(as)
end
end


This new version has the same result as Enum.sum:



iex(1)> Homework.sum([-2, -2.1524700989447303, 1])
-3.1524700989447307
iex(2)> Enum.sum([-2, -2.1524700989447303, 1])
-3.1524700989447307


So it seems like it is about the order.



Edit 2



Changing a + sum(as) to sum(as) + a makes no difference in the result when the list not in reverse order.



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


So when we talk about the relevance of 'order' it is the order in which folding is happening, not the order of the operands.






share|improve this answer




















  • 1





    The difference isn't head+acc versus acc+head. Floating-point addition is commutative, so those two would produce the same results. Rather, the difference is that they're using an accumulator at all. If you haven't learned about "tail recursion" yet, it's worth your time to look into that, to understand why Erlang's approach is much, much more efficient than yours.

    – Sneftel
    Mar 9 at 10:57











  • @Sneftel I've made an experiment and it seems like it is about the order... I'll definitely look at 'tail recursion' though, the book I'm reading is about those things :)

    – Marcus Vinícius Monteiro
    Mar 9 at 11:39






  • 1





    It is about the order, but in the sense of left-folding versus right-folding, not in the sense of the order of operands. Try changing a + sum(as) to sum(as) + a and you'll see that it has no effect on the result.

    – Sneftel
    Mar 9 at 12:00











  • @Sneftel you're right :) . I think I'm in the right mindset now.

    – Marcus Vinícius Monteiro
    Mar 9 at 12:25


















3














I think the real issue here is that one should never expect to do exact equality when working with floating point numbers, because they are inherently imprecise. If precision is needed, then a module such as Decimal can be used.



defmodule Homework do
def sum([]), do: Decimal.new(0)
def sum([a | as]), do: Decimal.add(a, sum(as))
end


Test session:



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


What Every Programmer Should Know About Floating-Point Arithmetic provides a nice overview of how to effectively work with floating point numbers.






share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "1"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader:
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    ,
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );













    draft saved

    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55067009%2felixir-unexpected-floating-point-result-difference-between-enum-sum-and-my-custo%23new-answer', 'question_page');

    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    3














    The answer to this question Why does changing the sum order returns a different result? inspired me go to the source to see how the implementation was and, surely enough, when the parameter is a list it uses Erlang's implementation of foldl, which applies the function in the order head + accumulator instead of accumulator + head like in my implementation:



    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.


    Edit



    @Sneftel's comment made me do the following experiment:



    @spec sum(list(number())) :: number()
    def sum(l) do
    case Enum.reverse(l) do # reversing first
    [] -> 0
    [a | as] -> a + sum(as)
    end
    end


    This new version has the same result as Enum.sum:



    iex(1)> Homework.sum([-2, -2.1524700989447303, 1])
    -3.1524700989447307
    iex(2)> Enum.sum([-2, -2.1524700989447303, 1])
    -3.1524700989447307


    So it seems like it is about the order.



    Edit 2



    Changing a + sum(as) to sum(as) + a makes no difference in the result when the list not in reverse order.



    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


    So when we talk about the relevance of 'order' it is the order in which folding is happening, not the order of the operands.






    share|improve this answer




















    • 1





      The difference isn't head+acc versus acc+head. Floating-point addition is commutative, so those two would produce the same results. Rather, the difference is that they're using an accumulator at all. If you haven't learned about "tail recursion" yet, it's worth your time to look into that, to understand why Erlang's approach is much, much more efficient than yours.

      – Sneftel
      Mar 9 at 10:57











    • @Sneftel I've made an experiment and it seems like it is about the order... I'll definitely look at 'tail recursion' though, the book I'm reading is about those things :)

      – Marcus Vinícius Monteiro
      Mar 9 at 11:39






    • 1





      It is about the order, but in the sense of left-folding versus right-folding, not in the sense of the order of operands. Try changing a + sum(as) to sum(as) + a and you'll see that it has no effect on the result.

      – Sneftel
      Mar 9 at 12:00











    • @Sneftel you're right :) . I think I'm in the right mindset now.

      – Marcus Vinícius Monteiro
      Mar 9 at 12:25















    3














    The answer to this question Why does changing the sum order returns a different result? inspired me go to the source to see how the implementation was and, surely enough, when the parameter is a list it uses Erlang's implementation of foldl, which applies the function in the order head + accumulator instead of accumulator + head like in my implementation:



    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.


    Edit



    @Sneftel's comment made me do the following experiment:



    @spec sum(list(number())) :: number()
    def sum(l) do
    case Enum.reverse(l) do # reversing first
    [] -> 0
    [a | as] -> a + sum(as)
    end
    end


    This new version has the same result as Enum.sum:



    iex(1)> Homework.sum([-2, -2.1524700989447303, 1])
    -3.1524700989447307
    iex(2)> Enum.sum([-2, -2.1524700989447303, 1])
    -3.1524700989447307


    So it seems like it is about the order.



    Edit 2



    Changing a + sum(as) to sum(as) + a makes no difference in the result when the list not in reverse order.



    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


    So when we talk about the relevance of 'order' it is the order in which folding is happening, not the order of the operands.






    share|improve this answer




















    • 1





      The difference isn't head+acc versus acc+head. Floating-point addition is commutative, so those two would produce the same results. Rather, the difference is that they're using an accumulator at all. If you haven't learned about "tail recursion" yet, it's worth your time to look into that, to understand why Erlang's approach is much, much more efficient than yours.

      – Sneftel
      Mar 9 at 10:57











    • @Sneftel I've made an experiment and it seems like it is about the order... I'll definitely look at 'tail recursion' though, the book I'm reading is about those things :)

      – Marcus Vinícius Monteiro
      Mar 9 at 11:39






    • 1





      It is about the order, but in the sense of left-folding versus right-folding, not in the sense of the order of operands. Try changing a + sum(as) to sum(as) + a and you'll see that it has no effect on the result.

      – Sneftel
      Mar 9 at 12:00











    • @Sneftel you're right :) . I think I'm in the right mindset now.

      – Marcus Vinícius Monteiro
      Mar 9 at 12:25













    3












    3








    3







    The answer to this question Why does changing the sum order returns a different result? inspired me go to the source to see how the implementation was and, surely enough, when the parameter is a list it uses Erlang's implementation of foldl, which applies the function in the order head + accumulator instead of accumulator + head like in my implementation:



    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.


    Edit



    @Sneftel's comment made me do the following experiment:



    @spec sum(list(number())) :: number()
    def sum(l) do
    case Enum.reverse(l) do # reversing first
    [] -> 0
    [a | as] -> a + sum(as)
    end
    end


    This new version has the same result as Enum.sum:



    iex(1)> Homework.sum([-2, -2.1524700989447303, 1])
    -3.1524700989447307
    iex(2)> Enum.sum([-2, -2.1524700989447303, 1])
    -3.1524700989447307


    So it seems like it is about the order.



    Edit 2



    Changing a + sum(as) to sum(as) + a makes no difference in the result when the list not in reverse order.



    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


    So when we talk about the relevance of 'order' it is the order in which folding is happening, not the order of the operands.






    share|improve this answer















    The answer to this question Why does changing the sum order returns a different result? inspired me go to the source to see how the implementation was and, surely enough, when the parameter is a list it uses Erlang's implementation of foldl, which applies the function in the order head + accumulator instead of accumulator + head like in my implementation:



    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.


    Edit



    @Sneftel's comment made me do the following experiment:



    @spec sum(list(number())) :: number()
    def sum(l) do
    case Enum.reverse(l) do # reversing first
    [] -> 0
    [a | as] -> a + sum(as)
    end
    end


    This new version has the same result as Enum.sum:



    iex(1)> Homework.sum([-2, -2.1524700989447303, 1])
    -3.1524700989447307
    iex(2)> Enum.sum([-2, -2.1524700989447303, 1])
    -3.1524700989447307


    So it seems like it is about the order.



    Edit 2



    Changing a + sum(as) to sum(as) + a makes no difference in the result when the list not in reverse order.



    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


    So when we talk about the relevance of 'order' it is the order in which folding is happening, not the order of the operands.







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Mar 9 at 12:22

























    answered Mar 8 at 18:56









    Marcus Vinícius MonteiroMarcus Vinícius Monteiro

    1,79731830




    1,79731830







    • 1





      The difference isn't head+acc versus acc+head. Floating-point addition is commutative, so those two would produce the same results. Rather, the difference is that they're using an accumulator at all. If you haven't learned about "tail recursion" yet, it's worth your time to look into that, to understand why Erlang's approach is much, much more efficient than yours.

      – Sneftel
      Mar 9 at 10:57











    • @Sneftel I've made an experiment and it seems like it is about the order... I'll definitely look at 'tail recursion' though, the book I'm reading is about those things :)

      – Marcus Vinícius Monteiro
      Mar 9 at 11:39






    • 1





      It is about the order, but in the sense of left-folding versus right-folding, not in the sense of the order of operands. Try changing a + sum(as) to sum(as) + a and you'll see that it has no effect on the result.

      – Sneftel
      Mar 9 at 12:00











    • @Sneftel you're right :) . I think I'm in the right mindset now.

      – Marcus Vinícius Monteiro
      Mar 9 at 12:25












    • 1





      The difference isn't head+acc versus acc+head. Floating-point addition is commutative, so those two would produce the same results. Rather, the difference is that they're using an accumulator at all. If you haven't learned about "tail recursion" yet, it's worth your time to look into that, to understand why Erlang's approach is much, much more efficient than yours.

      – Sneftel
      Mar 9 at 10:57











    • @Sneftel I've made an experiment and it seems like it is about the order... I'll definitely look at 'tail recursion' though, the book I'm reading is about those things :)

      – Marcus Vinícius Monteiro
      Mar 9 at 11:39






    • 1





      It is about the order, but in the sense of left-folding versus right-folding, not in the sense of the order of operands. Try changing a + sum(as) to sum(as) + a and you'll see that it has no effect on the result.

      – Sneftel
      Mar 9 at 12:00











    • @Sneftel you're right :) . I think I'm in the right mindset now.

      – Marcus Vinícius Monteiro
      Mar 9 at 12:25







    1




    1





    The difference isn't head+acc versus acc+head. Floating-point addition is commutative, so those two would produce the same results. Rather, the difference is that they're using an accumulator at all. If you haven't learned about "tail recursion" yet, it's worth your time to look into that, to understand why Erlang's approach is much, much more efficient than yours.

    – Sneftel
    Mar 9 at 10:57





    The difference isn't head+acc versus acc+head. Floating-point addition is commutative, so those two would produce the same results. Rather, the difference is that they're using an accumulator at all. If you haven't learned about "tail recursion" yet, it's worth your time to look into that, to understand why Erlang's approach is much, much more efficient than yours.

    – Sneftel
    Mar 9 at 10:57













    @Sneftel I've made an experiment and it seems like it is about the order... I'll definitely look at 'tail recursion' though, the book I'm reading is about those things :)

    – Marcus Vinícius Monteiro
    Mar 9 at 11:39





    @Sneftel I've made an experiment and it seems like it is about the order... I'll definitely look at 'tail recursion' though, the book I'm reading is about those things :)

    – Marcus Vinícius Monteiro
    Mar 9 at 11:39




    1




    1





    It is about the order, but in the sense of left-folding versus right-folding, not in the sense of the order of operands. Try changing a + sum(as) to sum(as) + a and you'll see that it has no effect on the result.

    – Sneftel
    Mar 9 at 12:00





    It is about the order, but in the sense of left-folding versus right-folding, not in the sense of the order of operands. Try changing a + sum(as) to sum(as) + a and you'll see that it has no effect on the result.

    – Sneftel
    Mar 9 at 12:00













    @Sneftel you're right :) . I think I'm in the right mindset now.

    – Marcus Vinícius Monteiro
    Mar 9 at 12:25





    @Sneftel you're right :) . I think I'm in the right mindset now.

    – Marcus Vinícius Monteiro
    Mar 9 at 12:25













    3














    I think the real issue here is that one should never expect to do exact equality when working with floating point numbers, because they are inherently imprecise. If precision is needed, then a module such as Decimal can be used.



    defmodule Homework do
    def sum([]), do: Decimal.new(0)
    def sum([a | as]), do: Decimal.add(a, sum(as))
    end


    Test session:



    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


    What Every Programmer Should Know About Floating-Point Arithmetic provides a nice overview of how to effectively work with floating point numbers.






    share|improve this answer



























      3














      I think the real issue here is that one should never expect to do exact equality when working with floating point numbers, because they are inherently imprecise. If precision is needed, then a module such as Decimal can be used.



      defmodule Homework do
      def sum([]), do: Decimal.new(0)
      def sum([a | as]), do: Decimal.add(a, sum(as))
      end


      Test session:



      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


      What Every Programmer Should Know About Floating-Point Arithmetic provides a nice overview of how to effectively work with floating point numbers.






      share|improve this answer

























        3












        3








        3







        I think the real issue here is that one should never expect to do exact equality when working with floating point numbers, because they are inherently imprecise. If precision is needed, then a module such as Decimal can be used.



        defmodule Homework do
        def sum([]), do: Decimal.new(0)
        def sum([a | as]), do: Decimal.add(a, sum(as))
        end


        Test session:



        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


        What Every Programmer Should Know About Floating-Point Arithmetic provides a nice overview of how to effectively work with floating point numbers.






        share|improve this answer













        I think the real issue here is that one should never expect to do exact equality when working with floating point numbers, because they are inherently imprecise. If precision is needed, then a module such as Decimal can be used.



        defmodule Homework do
        def sum([]), do: Decimal.new(0)
        def sum([a | as]), do: Decimal.add(a, sum(as))
        end


        Test session:



        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


        What Every Programmer Should Know About Floating-Point Arithmetic provides a nice overview of how to effectively work with floating point numbers.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Mar 10 at 1:28









        Adam MillerchipAdam Millerchip

        3,03311730




        3,03311730



























            draft saved

            draft discarded
















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55067009%2felixir-unexpected-floating-point-result-difference-between-enum-sum-and-my-custo%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            1928 у кіно

            Захаров Федір Захарович

            Ель Греко