On Tue, Jan 31, 2012 at 05:47:08PM +0900, Robert Klemme wrote:
> On Mon, Jan 30, 2012 at 6:27 PM, Chad Perrin wrote:
> >
> > From a usability perspective, leaving it up to the programmer, assuming
> > all programmers are aware of the difficulties involved in floating point
> > accuracy and expecting them to implement work-arounds is a really stinky
> > language design smell, in my opinion.
> 
> Most popular programming languages have that smell.  It is also not
> uncommon for complex tools to require reading a manual and making
> oneself familiar with the features of the tool in order to be able to
> use it properly.  Heck, there are even tools which require mandatory
> training (usually to avoid hurting yourself or someone else).

The fact remains that using it properly consists of having to write
work-arounds.  There's a difference between "I can get this to do what I
want simply by knowing what output it produces," on one hand, and "To get
this to do what I want I have to implement a work-around," on the other.

The first is a complex tool, which may or may not be well-designed.  The
second is a tool that actually falls short of useful simply because
people refuse to consider that it might be a good idea to design the tool
in such a way designed to maximize its utility.  Once again, I'm not
saying Float#== is wrong; I'm saying that Float#== is insufficient, by
itself (see below).


(I've edited a typo in the following quote for clarity going forward.)

> >
> > Just to be perfectly clear, I'm not suggesting we replace Float#==;
> > I'm suggesting we supplement it. ?Semantically speaking, the way
> > Float#== works now (as I understand it) makes perfect sense (note
> > that I have not actually read the source for Ruby's Float#==, so I'm
> > not sure it doesn't make less sense than I think). ?What doesn't make
> > sense to me is that being the only option in the class.
> 
> Thanks for the clarification!  Somehow it seemed == should be replaced.
> 
> So you want something like this in std lib
> 
> class Numeric
>   def delta_eql? x, delta
>     (self - x).abs <= delta
>   end
> 
>   def factor_eql? x, delta
>     ((self / x) - 1).abs <= delta
>   end
> 
>   def log10_eql? x
>     Math.log10(self).floor == Math.log10(x).floor
>   end
> ...
> end
> 
> The user would still have to decide which to choose and what parameter
> to pick for the delta.  For that he needs to understand the matter of
> numeric math.  Good documentation could help though.  Maybe these
> methods should rather go into Math or Math::Eql - there could be
> several more.  Question is though whether it would be more confusing
> than not to have these methods.  A user would still be required to
> understand the issues and pick a combination of method and values
> appropriate for his use case.  It would certainly not prevent these
> types of discussions. :-)

The following is, of course, only my evaluation of the situation and how
best to handle it; I'm sure wiser heads than mine in the realm of
language design could find deficiencies in my suggestions.  I'd like to
know what's deficient, though, so if someone has something to say, please
share.

1. No "require" should be . . . required . . . to make use of an
alternative to Float#==, to maximize availability to people who might
lack the understanding to really grasp the importance of avoiding the
pitfalls of Float#==, thus minimizing the proliferation of programs out
there in the world that have subtle errors in them.

2. Keeping the number of comparison methods to a minimum would help
reduce confusion.  I'd say that no more than two additional comparison
methods should be available by default, resulting in a maximum of three
options as "standard" comparison methods.  With too many options
available on roughly equal availability footing, some of the programmers
who would ignorantly fail to use a comparison method that depends on a
"require" expression (from point 1) might just default to using Float#==
even when it is suboptimal to do so to avoid having to learn about the
different comparison methods and choose one.  Beyond a total of two or
three comparison methods (including Float#==), any additionals should
probably be stuck in something in stdlib to be called with "require" as
needed.

3. At least one additional comparison method should have syntax roughly
as succinct as Float#==, though without sacrificing clarity, thus
requiring some careful thought about how to name such a beast.  I think a
succinct option less prone to naive error in assumptions about how Float
values are compared is less likely to be passed over as unnecessary by
someone who doesn't really grasp the differences in favor of the more
succinct Float#==.

4. As you say, good documentation helps.  Part of this, of course, is a
succinct and clear description in the ri documentation for the alternate
comparison method(s) for how it/they differ(s) from Float#==, but part of
it would also be mentioning something about the potential problems of
Float#== in its ri documentation as well.  In fact, I think adding
something like that to the Float#== documentation regardless of whether
any additional comparison methods are ever added would be a good idea, if
only to reduce the volume of complaints that Ruy can't do math by getting
people who notice subtle precision bugs in their programs to realize that
a work-around is needed.

5. I think I had a point 5 in mind, but I've forgotten it.


> >
> > Can someone please explain to me in clear, direct terms why there is
> > opposition to the simple expedient of adding at least one additional
> > comparison method to Float to address the vast majority of casual use
> > cases without having to resort to reimplementing such comparison methods
> > over and over again or bringing in additional libraries with onerous
> > syntactic verbosity?
> 
> There is no method which does not require an additional data item
> (what I called "level of imprecision"), what makes it harder to make a
> proper choice.  See also:
> http://www.ruby-forum.com/topic/3481172#1042651

That argument seems, to me, to only really dispute the suggestion that it
might be better to replace Float#==, and not the suggestion to supplement
it with something well-defined.  The goal I wish to reach here is not to
have something as "standard" and self-evidently correct on an engineering
level as Float#==, but to have something that solves 98% of the problem
for 98% of casual use cases, well-defined so that people who use it know
what to expect.  When I say "know what to expect", of course, I'm not
talking about knowing that it fails sometimes to meet the expectations of
decimal arithmetic; I'm talking about rules for how it works that are
clear and simple enough that anyone who reads a succinct description of
the method should never get a result that produces exactly what the
person expects.  This means ensuring it's easy to figure out to what
level of precision it *consistently* matches the expectations of how the
comparison would work with an actual decimal implementation behind the
number rather than a binary implementation.

That may even be a sliding scale, as in the case of a ratio comparison,
as long as the rules for how it slides are simple, clear, and easy to
evaluate in one's head while writing code.

-- 
Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]