#1358 Unexpected comparison behaviour

tonsky Thu 9 Dec 2010

For Lists equality there is comparison between their types:

Int[1,2,3] != Int?[1,2,3] != Obj[1,2,3]

I know it was documented, but who are users of that? Who needs to compare list types after content equality? Are there uses more common than plain content comparison?

Another gotcha here:

1.0 != 1.00

Since Decimal is a default floating-point type, I think, equals should be more intuitive, shouldn't it? Scale-aware comparisons may be implemented as a special method, again, if there's somebody who'll ever need them.

What do you think?

DanielFath Thu 9 Dec 2010

About second example. That's not Fantom's fault though. They simply reuse .Net and Java classes which have that sort of implementation. Another thing to have in mind is security. sys::Decimal is made for security. If you don't need it then use sys::Float

fansh> 1.0f == 1.00f
true
fansh> 1.000000000000000000000000000000000000000000001f
1.0

Decimal is used in business system where there is a great chance someone could penny shave bank by saying.

1.000000000000000000000000000000000000000000000000000000000000000000000000001 ==
1.000000000000000000000000000000000000000000000000000000000000000000000000000

As for the first I'm bit confused that Int[1,2,3] != Int?[1,2,3]. It not being null shouldn't affect overall type. Maybe make a new method that checks if it is the same type regardless of null-ability. As for Obj[1,2,3] != Int[1,2,3] that shouldn't surprise you since just by stating it is a regular object your list will be completely different.

If you wonder why 1.00 is Decimal and not float see discussion here:

> Why are decimal considered default? When I type 1.0 I expect it to be Float. So why does it default to Decimal instead? I mean I'm more likely to use Float than Decimal since Decimal is missing a lot of API and has a much lower use case than Float.

I think in normal usage, Decimal is what you want. If its simply an issue of APIs I'm sure we can add to Decimal - were there things you were looking for in particular?

tonsky Thu 9 Dec 2010

I understand what decimals are for, but I didn’t get your example, even for financial applications 0.0 is equal by all means to 0.00. Guys writing financial applications will be very surprised by the fact that 1.0 - 1.00 != 0.0. Sad if both .net and java have such a poor implementation.

For lists, I didn't see an use case for comparing lists’ types. If you see, please tell me. Lists are not important by themselves, their content does. It’s like saying that ArrayList(1,2,3) != LinkedList(1,2,3). Yes, they are working differently, but it’s content what’s important in almost all cases.

vkuzkokov Thu 9 Dec 2010

Int[] and Int?[] are different functionally:

Int?[] x := ...
Int[] y := ...
z := null
x.add(z) // OK
y.add(z) // NullErr

By no means the same.

The fact that y can be casted to Int?[] is sometimes annoying but it's a trade-off for having simple generic types.

DanielFath Thu 9 Dec 2010

even for financial applications 0.0 is equal by all means to 0.00. Guys writing financial applications will be very surprised by the fact that 1.0 - 1.00 != 0.0. Sad if both .net and java have such a poor implementation.

Nope. Those two aren't the same. And neither are 0.0f and 0.0.

fansh> c:=0.0f
fansh> 10.times {echo(c+=0.1f)}
fan.fansh.Var@1e97f9f ?= 10.times {echo(c+
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999

fansh> 10.times {echo(d+=0.1)}
fan.fansh.Var@ef2c60 ?= 10.times {echo(d+
fan.fansh.Var@1e97f9f ?= 10.times {echo(d+
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1.0

Decimal aren't unintuitive either. If you want to compare them properly use compare or its shortcuts. See sys::Decimal.compare and sys::Decimal.equals. Probably reason equals isn't compare==0 is that it must use same fields for comparison as hash.

0.0 < 0.00 //false
0.0 > 0.00 //false
0.0<=>0.00 //0 means equal

@vkuzokov: ArrayList and LinkedList have different functionality yet you can still compare them by their structure and not their function. I'm assuming that type comparison is done to optimize list comparison.

vkuzkokov Thu 9 Dec 2010

ArrayList and LinkedList have different functionality.

From prospective of List interface they are the same. Int[] and Obj[] on the other hand have different behavior in Fantom runtime.

rfeldman Thu 9 Dec 2010

Not to belabor the point, but I don't think adding 0.1 to 0.0 three times and ending up with 0.300000000000000004 is "intuitive" by most standards. :) Mathematically it's plainly wrong as written; you need a solid understanding of how floats are represented under the hood to know why this happens, and I don't think very many developers do. After all, it doesn't come up that often.

That said, I also doubt it's worth doing a separate implementation from how Java and .net do this, especially due to interop concerns, so it's probably not terribly relevant how intuitive it may or may not be.

cheeser Thu 9 Dec 2010

welcome it IEEE floating point specs. It sucks but it's how computers/memory works and is sufficient for most cases with the glaring exception of currency.

DanielFath Thu 9 Dec 2010

@rfeldman: Not to mention this gem:

fansh>((0.1f + 0.1f + 0.1f - 0.3f)*1E17f)
5.551115123125783

@vkuzkokov: I understand that they have different behavior. But they share the same structure and interface. Can't their behavior be synchronized (I'm assuming that you mean that Int?[] and Int[] aren't backed by same structure when implemented on a VM)?

jodastephen Thu 9 Dec 2010

Given the confusion that can happen if equals is not compatible with compare, I've always strived to make them the same. However another approach is to say that if a class implements Comparable then compare() is used for == instead of equals(). Of course this approach doesn't work in Fantom as all classes have a compare() method and there is no Comparable interface to allow the rule to work. Which is a pity.

helium Fri 10 Dec 2010

In C# 1.0m == 1.00m is true, despite being represented differently internally.

tonsky Fri 10 Dec 2010

@vkuzkokov Int[1,2,3].rw and Int[1,2,3].ro have different behaviour too, but are considered equal. That’s because when comparing lists, we are comparing content, not behaviour. Note that still nobody has a type comparison use case. It’s going to be like

list1.add(1)
if(list2 == list1)
  list2.add(1)

but that doesn’t make sense.

@helium Same for python's decimal.Decimal class

>>> from decimal import Decimal
>>> Decimal('1.123') - Decimal('1.123') == Decimal('0')
True

And here are some notes from decimal.py source code:

# Decimal integers must hash the same as the ints
#
# The hash of a nonspecial noninteger Decimal must depend only
# on the value of that Decimal, and not on its representation.
# For example: hash(Decimal('100E-1')) == hash(Decimal('10')).

...

# The value of a nonzero nonspecial Decimal instance is
# faithfully represented by the triple consisting of its sign,
# its adjusted exponent, and its coefficient with trailing
# zeros removed.

It would be much more usable if Fantom’s Decimal will adjust its hash and equals functions to correspond to this rules.

Login or Signup to reply.