math.nroot [was Re: A brief question.]
Michael Hudson
mwh at python.net
Mon Jul 11 08:09:50 EDT 2005
Tim Peters <tim.peters at gmail.com> writes:
> [Tim Peters]
> >>>> All Python behavior in the presence of infinities, NaNs, and signed
> >>>> zeroes is a platform-dependent accident, mostly inherited from that
> >>>> all C89 behavior in the presence of infinities, NaNs, and signed
> >>>> zeroes is a platform-dependent crapshoot.
>
> [Michael Hudson]
> >>> As you may have noticed by now, I'd kind of like to stop you saying
> >>> this :) -- at least on platforms where doubles are good old-fashioned
> >>> 754 8-byte values.
>
> [Tim]
> >> Nope, I hadn't noticed! I'll stop saying it when it stops being true,
> >> though <wink>. Note that since there's not even an alpha out for 2.5
> >> yet, none of the good stuff you did in CVS counts for users yet.
>
> [Michael]
> > Well, obviously. OTOH, there's nothing I CAN do that will be useful
> > for users until 2.5 actually comes out.
>
> Sure. I was explaining why I keep saying what you say you don't want
> me to say: until 2.5 actually comes out, what purpose would it serve
> to stop warning people that 754 special-value behavior is a x-platform
> crapshoot? Much of it (albeit less so) will remain a crapshoot after
> 2.5 comes out too.
Well, OK, I phrased my first post badly. Let me try again:
I want to make this situation better, as you may have noticed.
> >>> But first, I'm going to whinge a bit, and lay out some stuff that Tim
> >>> at least already knows (and maybe get some stuff wrong, we'll see).
> >>>
> >>> Floating point standards lay out a number of "conditions": Overflow
> >>> (number too large in magnitude to represent), Underflow (non-zero
> >>> number to small in magnitude to represent), Subnormal (non-zero number
> >>> to small in magnitude to represent in a normalized way), ...
>
> >> The 754 standard has five of them: underflow, overflow, invalid
> >> operation, inexact, and "divide by 0" (which should be understood more
> >> generally as a singularity; e.g., divide-by-0 is also appropriate for
> >> log(0)).
>
> > OK, the decimal standard has more, which confused me for a bit
> > (presumably it has more because it doesn't normalize after each
> > operation).
>
> The "conditions" in IBM's decimal standard map, many-to-one, on to a
> smaller collection of "signals" in that standard. It has 8 signals:
> the 5 I named above from 754, plus "clamped", "rounded", and
> "subnormal". Distinctions are excruciatingly subtle; e.g., "rounded"
> and "inexact" would be the same thing in 754, but, as you suggest, in
> the decimal standard a result can be exact yet also rounded (if it
> "rounds away" one or more trailing zeroes), due to the unnormalized
> model.
Right, yes, that last one confused me for a while.
Why doesn't 754 have subnormal? Actually, I think I'm confused about
when Underflow is signalled -- is it when a denormalized result is
about to be returned or when a genuine zero is about to be returned?
> >>> For each condition, it should (at some level) is possible to trap each
> >>> condition, or continue in some standard-mandated way (e.g. return 0
> >>> for Underflow).
>
> >> 754 requires that, yes.
>
> >>> While ignoring the issue of allowing the user to control this, I do
> >>> wish sometimes that Python would make up it's mind about what it does
> >>> for each condition.
>
> >> Guido and I agreed long ago that Python "should", by default, raise an
> >> exception on overflow, invalid operation, and divide by 0, and "should
> >> not", by default, raise an exception on underflow or inexact.
>
> And, I'll add, "should not" on rounded, clamped and subnormal too.
Sure, but we already have a conforming implementation of 854 with
settable traps and flags and rounding modes and all that jazz.
Maybe we should just implement floats in Python.
> >> Such defaults favor non-expert use. Experts may or may not be happy
> >> with them, so Python "should" also allow changing the set.
>
> > Later :)
>
> That's a problem, though. 754 subsets are barely an improvement over
> what Python does today:
Well, my contention is that the consistent application of one
particular 754 subset would be an improvement. Maybe I'm wrong!
> > (In the mean time can we just kill fpectl, please?)
>
> Has it been marked as deprecated yet (entered into the PEP for
> deprecated modules, raises deprecation warnings, etc)? I don't know.
> IMO it should become deprecated, but I don't have time to push that.
A bit of googling suggests that more people pass --with-fpectl to
configure than I expected, but I doubt more than 1% of those actually
use the features thus provided (of course, this is a guess).
> >>> There are a bunch of conditions which we shouldn't and don't trap by
> >>> default -- Underflow for example. For the conditions that probably should
> >>> result in an exception, there are inconsistencies galore:
> >>>
> >>> >>> inf = 1e300 * 1e300 # <- Overflow, no exception
> >>> >>> nan = inf/inf # <- InvalidOperation, no exception
>
> >> Meaning you're running on a 754 platform whose C runtime arranged to
> >> disable the overflow and invalid operation traps.
>
> > Isn't that the standard-mandated start up environment?
>
> The 754 standard mandates "non-stop" mode (all traps disabled) at
> startup, but who in this chain is _claiming_ to implement the 754
> standard? Your platform C may or may not, and your OS may or may not.
Well, fair point, but it's a convenient stick to beat unconforming
platform vendors with.
I only really care about platforms that make at least a half-hearted
effort towards 754 compliance. I'm not looking to improve behaviour
on a PDP-10 running TOPS (or whatever).
> >> You're seeing native HW fp behavior then.
>
> > But anyway, shouldn't we try to raise exceptions in these cases?
>
> I believe Python should raise exceptions in these cases by default,
> because, as above, they correspond to the overflow and
> invalid-operation signals respectively, and Python should raise
> exceptions on the overflow, invalid-operation, and divide-by-0 signals
> by default. But I also believe Python _dare not_ do so unless it also
> supplies sane machinery for disabling traps on specific signals (along
> the lines of the relevant standards here).
Um. Python raises OverflowError more often that it used to, it seems
to me, and already _dares_ to raise it from (e.g.) math.exp with no
way of turning this off I can see.
> Many serious numeric programmers would be livid, and justifiably so,
> if they couldn't get non-stop mode back.
Maybe I'm missing something (and writing this reply in multiple stages
makes this more likely) but they don't have it now, do they?
> The most likely x-platfrom accident so far is that they've been
> getting non-stop mode in Python since its beginning.
I know I'm repeating myself, but didn't this changeset:
http://fisheye.cenqua.com/changelog/python?cs=MAIN:tim_one:20010905223655
change that rather? (It affects 2.2 and later, I think).
> > I don't think it's a particularly good idea to try to utilize the fp
> > hardware's ability to do this at this stage, btw, but to add some kind
> > of check after each operation.
>
> That's possible, anyway. Doing it in software is slow, but if you
> look at the horrid contortions fpectlmodule.c endures to try to
> exploit HW facilities, it's not clear that the latter is any faster.
I think it might well be slow, too, at least some of the time; the
conclusion of this rather long thread:
http://lists.freebsd.org/pipermail/freebsd-stable/2005-May/014575.html
was "turn --with-fpectl off".
> >>> >>> pow(1e100, 100) <- Overflow, exception
> >>> Traceback (most recent call last):
> >>> File "<stdin>", line 1, in ?
> >>> OverflowError: (34, 'Numerical result out of range')
> >>> >>> math.sqrt(-1) # <- InvalidOperation, exception
> >>> Traceback (most recent call last):
> >>> File "<stdin>", line 1, in ?
> >>> ValueError: math domain error
>
> >> Unlike the first two examples, these call libm functions.
>
> > And the user cares about this why?
>
> Didn't say the user did care.
I know you didn't, and I also knew the reason for the different
behaviours, but that doesn't make me feel that much better.
> Why doesn't Python already supply a fully 754-conforming arithmetic
> on 754 boxes? It's got almost everything to do with implementation
> headaches, and very little to do with what users care about.
> Because all the C facilities are a x-platform mess, the difference
> between calling and not calling libm can be the difference between
> using the platform libm or Python needing to write its own libm.
> For example, there's no guarantee that math.sqrt(-1) will raise
> ValueError in Python, because Python currently relies on the
> platform libm sqrt to detect _and report_ errors. The C standards
> don't require much of anything there.
Can't we use the stuff defined in Appendix F and header <fenv.h> of
C99 to help here? I know this stuff is somewhat optional, but it's
available AFAICT on the platforms I actually use (doesn't mean it
works, of course).
I'm thinking something like this:
fexcept_t flags;
feclearexcept(FE_ALL_EXCEPT);
/* stuff, e.g. r = exp(PyFloat_AS_DOUBLE(x)) */
fegetexceptflag(&flags, FE_ALL_EXCEPT)
/* inspect flags to see if any of the flags we're currently trapping
are set */
[...]
> >> Then it's a x-platform crapshoot whether and when the libm functions
> >> set errno to ERANGE or EDOM, and somewhat of a mystery whether it's
> >> better to reproduce what the native libm considers to be "an error",
> >> or try to give the same results across platforms. Python makes a
> >> weak attempt at the latter.
>
> > Well, you can at least be pretty sure that an infinite result is the
> > result of an overflow condition, I guess.
>
> There are at least two other causes: some cases of divide-by-0 (like
> 1/0 returns +Inf), and non-exceptional production of an infinite
> result from infinite operands (like sqrt(+Inf) should return +Inf, and
> there's nothing exceptional about that).
Yeah, but I think those can be dealt with (if we really wanted to).
OTOH, I think Inexact would be pain itself to detect without hw
support, and Underflow would be tedious.
> >>> At least we're fairly consistent on DivisionByZero...
>
> >> When it's a division by 0, yes. It's cheap and easy to test for that.
> >> However, many expert uses strongly favor getting back an infinity
> >> then instead, so it's not good that Python doesn't support a choice
> >> about x/0.
>
> > Indeed. But I'd rather work on non-settable predictability first.
>
> OTOH, I bet I could find people willing to pay for the ability to
> "turn off" divide-by-0 exceptions (or the people talking my ear off at
> PyCon this year were just bluffing <wink>).
Noted.
> BTW, since there's so little the HW can help us with here in reality
> (since there's no portable way to get at it),
In what way does C99's fenv.h fail? Is it just insufficiently
available, or is there some conceptual lack?
> any predictability is going to be supplied by software, and software
> for that will work better over the long run if it's designed from
> the start realizing that it needs to consult some form of context to
> decide what is and is not "an exception".
Also noted.
[...]
> > This make me sad.
> >
> > Whether my code should have returned an infinite value or raised an
> > exception for "1e1000" is an open question, but I'd like the answer to
> > be less accidental.
>
> It's a delicate house of cards right now for sure. In some ways,
> experts have learned to live with-- and exploit --it: when they
> _want_ an overflow exception they'll use pow(), and when they don't
> they'll just multiply.
Another question: where can I find these experts? How come they
haven't tried to reduce the mess before?
I guess "academia" is an answer to both those questions...
[...]
> >> and overflow doesn't always result in an infinity either (depends on
> >> the rounding mode in effect)).
>
> > If we're allowing the user to alter rounding mode, I imagine we'll
> > have a much better idea about this sort of thing. For now, I think we
> > can assume a default rounding that doesn't do that, and someone who
> > has a platform like where it has his or her own problem.
>
> IME the rounding modes in 754 are almost never changed from the
> default to-nearest-even, and I can live with that. The rounding modes
> in the Decimal module will be changed a lot, though (due to the
> insanity of politically mandated rounding rules in currency
> calculations).
Decimal is a solved problem, here (and also an inspiration for
design).
> >>> On the issue of platforms that start up processes with traps enabled,
> >>> I think the correct solution is to find the incantation to turn them
> >>> off again and use that in Py_Initialize(), though that might upset
> >>> embedders.
>
> >> Hard to know. Python currently has a hack to disable traps on
> >> FreeBSD, in python.c's main().
>
> > Oh, right. Well, I don't much care about embedding, anyway.
>
> So far, I think FreeBSD remains the only known Python platform that
> enables some FP traps by default. So it's OK if you _just_ don't care
> much about embedding on FreeBSD <wink>.
Even less to worry about!
Cheers,
mwh
--
I never realized it before, but having looked that over I'm certain
I'd rather have my eyes burned out by zombies with flaming dung
sticks than work on a conscientious Unicode regex engine.
-- Tim Peters, 3 Dec 1998
More information about the Python-list
mailing list