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;
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
add a comment |
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
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
add a comment |
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
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
floating-point erlang elixir
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
add a comment |
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
add a comment |
2 Answers
2
active
oldest
votes
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.
1
The difference isn'thead+acc
versusacc+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 changinga + sum(as)
tosum(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
add a comment |
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.
add a comment |
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
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
1
The difference isn'thead+acc
versusacc+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 changinga + sum(as)
tosum(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
add a comment |
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.
1
The difference isn'thead+acc
versusacc+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 changinga + sum(as)
tosum(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
add a comment |
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.
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.
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'thead+acc
versusacc+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 changinga + sum(as)
tosum(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
add a comment |
1
The difference isn'thead+acc
versusacc+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 changinga + sum(as)
tosum(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
add a comment |
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.
add a comment |
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.
add a comment |
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.
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.
answered Mar 10 at 1:28
Adam MillerchipAdam Millerchip
3,03311730
3,03311730
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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