ANSI, =, hash, Integer, Float

Stephan Rudlof sr at evolgo.de
Thu Dec 19 13:45:25 UTC 2002


First a sorry for making so many mistakes...

Richard A. O'Keefe wrote:
> 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.

Yes.

> 
> 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.

Agreed.

> 
> 	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.

You have caught me here:

> Calling something anInteger when its value is almost always a Float
> is certainly a bad idea.

Definitely true.

> 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.

It's plain wrong (it had been better to test the code).
The intent has been (now tested):
	| aNum |
	aNum := 0.
	[aNum <= 1]
	  whileTrue:
		[Transcript cr; show: aNum printString.
		aNum := aNum + 0.1]

> 
> 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.

True for both old and corrected example.

> I especially fail to see how an example which always compares a
> Float with another Float tells us anything about mixed mode comparison.

I have to agree.

But the corrected example now shows mixed mode arithmetics, which should be
avoided IMO.

> 
> 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 #=.

Apparently I've been confused here.
Sorry for generating confusion for others and making you extra work.

> 
>>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.
> 
> 
> 

May be reasonable or not (currently no opinion here).

But independently of your suggestion I don't like mixed mode arithmetics and
comparisons between rationals (Integers and Fractions) and Floats and would
like to forbid it.


Greetings,

Stephan
-- 
Stephan Rudlof (sr at evolgo.de)
   "Genius doesn't work on an assembly line basis.
    You can't simply say, 'Today I will be brilliant.'"
    -- Kirk, "The Ultimate Computer", stardate 4731.3




More information about the Squeak-dev mailing list