ANSI, =, hash, Integer, Float

Richard A. O'Keefe ok at cs.otago.ac.nz
Wed Dec 18 00:19:37 UTC 2002


Stephan Rudlof <sr at evolgo.de> asked:
	What about
	- defining #= true and #hash equal
	  applied to numerical equal rational numbers
	  (SmallInteger, LargePositiveInteger, LargeNegativeInteger, Fraction)
	and
	- defining #= as undefined (raising an Exception)
	  for comparisons of all these rationals with Floats
	  (also if numerical equal)?
	
If I understand this proposal, it is
 - = should do what it does now when the receiver and argument are
     both rational numbers or are both floating point numbers;
 - hash should do what it does now for rational numbers and floating point
   numbers;
 - <rational> = <float> and <float> = <rational>
   should both raise an exception whether the receiver and argument
   are numerically eaual or not.

That is certainly a self-consistent proposal.
Never mind the details of ANSI, it clearly violates the INTENT of
ANSI Smalltalk.

*BUT* it creates a library that is significantly harder to understand.
In general,
    if x < y is defined,
    then x <= y, x > y, x >= y, x = y, and x ~= y are also defined
    and their values are mutually consistent.
In particular, we expect
    x < y   iff  y > x
    x <= y  iff  y >= x
    x = y   iff  (x = y) not
    x <= y  iff  x < y or: [x = y]
    x = y   iff  (x < y or: [y < x]) not     {provided RHS is defined}
(Hope I got those right.)
Any definition of comparison operations which violates these laws is
a definition which will seduce programmers into making mistakes, because
the symbols won't _really_ mean what they _look_ as though they mean.

Ada consistently forbids _all_ mixed mode comparisons and no confusion
arises.  Pascal consistently allows all mixed mode comparisons and no
confusion arises.  Allowing _some_ mixed mode comparisons while
forbidding others (which could clearly be derived from the allowed ones)
does not provide a design that is to grasp or use correctly.

In short, I suggest that 1.0 = 1 should be forbidden ONLY if
1.0 <= 1 (and all the others) are _also_ forbidden.

	This would avoid the trap of making loops with bad end
	conditions like e.g.
	
	| anInteger |
	anInteger := 0.
	[self doSomething. anInteger := anInteger + 0.1]
	  whileTrue: [anInteger <= 1.0]
	
I don't understand what this example is supposed to demonstrate.
Calling something anInteger when its value is almost always a Float
is certainly a bad idea.
Writing a block whose value is always a Float in a context
where a block that always returns a Boolean is required
is also a bad idea.

I fail to see how an example that uses <= tells us anything about
what we should do to ensure that #hash and #= are mutually consistent.
I especially fail to see how an example which always compares a
Float with another Float tells us anything about mixed mode comparison.

What's more, undefine <= and people will still be able to write
    0 to: 1.0 by: 0.1 do: [:anInteger | self doSomething]

	Would such a change break something?
	
Yes.
	
I am extremely puzzled here.

I pointed out a problem in Squeak's #hash implementations,
and people suddenly pounce like starving wolves on a lamb
on #= as if there was a problem with #=.

>From a _user's_ perspective, The Simplest Thing That Could Possibly Work
is simply to fix #hash.  I not only know that it can be done, I know how
to do it.  With the proviso that the code is for specification purposes
only (I do NOT propose actually allocating a fresh Float each time):

    LargePositiveInteger>>hash   ^self asFloat hash
    LargeNegativeInteger>>hash   ^self asFloat hash
    Fraction>>hash               ^self asFloat hash
    Float hash
      ^((self between: SmallIntger minVal and: SmallInteger maxval)
        and: self fractionPart = 0.0)
        ifTrue: [self truncated hash]
        ifFalse: ["the current basicAt: bitAnd: ... stuff goes here"]

This ensures consistency by
 - forcing Float to use SmallInteger hash whenever the float
   could be equal to a SmallInteger
 - forcing rationals other than SmallInteger to use Float hash

The change would be hidden entirely inside the Number classes.




More information about the Squeak-dev mailing list