The floating-point arithmetic in CPython and a decent part of the math module — just a thin wrapper to capabilities, presented in the C language standard. The cmath module was also designed to be compatible with the Annex G of the C standard.
But not to all. For example, C standard provide functions (fegetround, fesetround) to control rounding modes. It also specify returned values for mathematical functions when floating-point exceptions are set.
Consider how now the error handling happens for the cmath module (taken from module comments):
Each of the c_* functions computes and returns the C99 Annex G recommended result and also sets errno as follows: errno = 0 if no floating-point exception is associated with the result; errno = EDOM if C99 Annex G recommends raising divide-by-zero or invalid for this result; and errno = ERANGE where the overflow floating-point signal should be raised.
Then the ValueError raised for EDOM and the OverflowError - for ERANGE, but the Annex G result is hidden from the pure-Python world. Though, it might be helpful for applications. E.g. clog(-0+0i)
returns -∞+πi
and clog(+0+0i)
returns -∞+0i
- correct one-sided limits in the pole of log().
The mpmath and the gmpy2 (per default, if trap_divzero
and/or trap_invalid
context options aren’t enabled) rather return special values per the C standard, not raise exceptions. And the mpmath also uses builtin float’s and math/cmath functions for the fp context. Thus, to override current behavior of the stdlib - we need to catch ValueError from the called function and then process function arguments to return special values, i.e. essentially re-implement handling of special values.
So, why not support custom contexts for floating-point math, like we do in the decimal module? This will offer less controls c.f. the gmpy2 contexts (e.g. we have fixed precision setting), but allow us to customize at least 1) whether functions/operations will raise an exception or return special values, 2) rounding modes.
All this is backed now by the C standard and require just an interface to access such capabilities. E.g. issue Pass return value on ValueError exceptions in the cmath/math modules · Issue #133895 · python/cpython · GitHub shows how easy to attach special values, available at C level to exceptions. All this is for free!
Here few examples of how it’s working from the gmpy2, in part that probably does make sense for the stdlib:
>>> from gmpy2 import *
>>> ctx = get_context()
>>> ctx.trap_divzero = True # current defaults: raise an exception
>>> log(0) # ValueError in CPython
Traceback (most recent call last):
File "<python-input-13>", line 1, in <module>
log(0)
~~~^^^
gmpy2.DivisionByZeroError: division by zero
>>> ctx.trap_divzero = False
>>> log(0) # now get a special value instead
mpfr('-inf')
>>> ctx.round == RoundToNearest # default rounding mode
True
>>> sin(1)
mpfr('0.8414709848078965')
>>> ctx.round = RoundUp
>>> sin(1)
mpfr('0.84147098480789662')