From: Chad Perrin Date: 2012-02-01T02:23:06+09:00 Subject: Re: Microrant on Ruy's Math Skills 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 ]