Who can implement a well-behaving frange() function so that frange(0.0, 2.1, 0.7) returns [0.0, 0.7, 1.4] ? Here's one that gives the wrong answer: http://code.activestate.com/recipes/577068-floating-point-range/
23 plus ones
Shared publicly•View activity
View 56 previous comments
- I like Decimal type classes when dealing with money. Most of the time, I don't need fast floating-point operations. As for the other comments, I like syntax support. Call me odd, but I do. When a type is like any other "primitive", people tend to use it more liberally. Require an explicit ctor call and it suddenly becomes "less useful". I guess that is why Guido is in charge of Python and not me. :-)Sep 25, 2011
- , Sure, representing money is practically the only real use of Decimal. (The main other one is implementing calculators.)
The problem is that the difficult about decimal.Decimal is managing context sanely and understanding what you're asking for in terms of precision, rounding, etc. Just having Decimal literals wouldn't really help this, so I think some more thinking about design and syntax would be needed to make things work well.Sep 25, 2011
- The way I see it, frange() suffers from having to make a precise comparison of imprecise values. If the comparison between the value of an iteration and the limit were somehow aware of and tolerant of floating point quirks, I think the results would be able to satisfy the intent of the programmer.
What I mean is this: for the case Guido posed, 2.0999999999999996 is practically 2.1, and frange() can and should know, behave better. If some (good) programmers can make good use of floats in other applications, is there some reason why frange() can't be among them?Sep 25, 2011
- I'll try to explain my point once more, then I'll be silent on this thread; too many people either took the original challenge literally, or propose deep language changes, or propose solutions that will allow experts to get the right answer but won't help the casual programmer avoid the problem of an unexpected extra point for some combinations of arguments.
The problem with frange() is really that it is burdened by behaviors that work beautifully for range(), but don't extend to floats. E.g. range(0, 10, 3) == [0, 3, 6, 9]. We'd like frange(i, j, k) == range(i, j, k) for any three integers (and as long as those ints are representable as floats this is easy). However, that means that the distance from start to stop doesn't have to be a multiple of step, and that's where the trouble begins. Naively you'd expect that frange(i*a, j*a, k*a) == [x*a for x in range(i, j, k)] for any ints i, j, k and any float a != 0, just like range(i*a, j*a, k*a) == [x*a for x in range(i, j, k)] for any ints i, j, k and any int a != 0. Or, at least you'd expect that the vallues produced by frange() would be suitably close to those computed by the list comprehension. You would not expect there to be an extra point! But the requirement that it work even if stop minus start is not a multiple of step (in which case it should effectively round up to the nearest multiple of step) makes this much harder, because now we'd have to decide how close the two values must be before we consider them "equal" and skip the rounding-up.
numpy.linspace() solves this completely by setting expectations differently. The way its API is defined it never has a problem with the distance from stop to start not being a multiple of the step -- the step is computed to be that distance divided by the requested number of points. But what about cases where stop-start is not a multiple of step? The common use cases for frange() probably never have this; if you really needed those semantics, you'd have to compute the rounded-up stop value yourself before passing it to linspace(). In effect, the author of linspace() can concentrate on computing the points to return as accurately as possible (i.e., don't just keep adding step, since that causes too much drift) and doesn't have to guess how many points are expected.
Finally, using Decimal does not solve the problem. It is still a floating-point implementation, and can never represent e.g. 1/3 or 1/7 exactly, so a "drange()" would have to be written with all the same caution and hacks as frange(). Using Fraction can solve the problem, but at an extreme cost -- intermediate results may use huge "bignums", and therefore falls in the experts-only category.Sep 26, 2011
- Perhaps any commenters should ensure they've read all the comments already here; some do seem to be covering old ground and suggestions causing the cycle to continue with rebuttals. Given there's over 20,000 potential readers, every commenter should weigh up the value of an addition to the readership, just like on a large mailing list.Sep 26, 2011
- I thinkis being too skeptical of solutions he classifies as experts only. In this case, the problem is not technical—it's a user expectation problem. To deal best with floats, it's essential to understand that they are for approximate numbers, and resist all urges to do have exact expectations of them. It seems like experienced programmers do this even when for their operation and data they know their floats will be exact. The problem was never that the naïve frange didn't work fine, it's that the user expected it to work differently than it does. (Edited to fix grammar.)Sep 30, 2011
Add a comment...