Division with different rounding

It’s a restart of relatively recent thread, asking for integer ceil division. We have a similar request for round-to-nearest case (issue #76159).

One possible approach is to add several functions to the math.integer module. For instance, the gmpy2 module has c_div(), c_mod() and c_divmod() for division with rounding toward +Inf (ceiling rounding) and similar sets of functions for floor rounding and for rounding toward 0 (there is no case for round-to-nearest mode).

Alternatively, we could use few functions (say, math.integer.div(), etc) with optional keyword argument, say rounding or round, to specify the rounding mode. Such option could be useful for some other integer functions, like isqrt(), see e.g. issue #90345.

On another hand, maybe different rounding modes could be helpful for non-integer objects too (like datetime.timedelta) in the context of integer division (see use case). In this case we might consider to add a new keyword argument to divmod()/__divmod__() functions.

2 Likes

I’m on board with ceildiv!

I’ve replied in >>91269 to keep all the messages / quotes / replies together because I don’t see a rule against necroposting :sweat_smile:

I think that all rounding modes, at least mentioned in the C standard, do make sense in some applications. The CPython internals reinvented several times division with round-to-nearest rounding, see referenced issue.

But I doubt that bunch of new functions in the math.integer module is a good idea. Rounding also make sense for some other integer functions, not just division. Other than mentioned: integer logarithm or nth root.

BTW, I proposed a patch for adding isqrt_rem(), based on the discussion in issue thread. But Raymods idea seems clearer for me.

It’s still discouraged here. Just undefined.

I’m not sure if it’s relevant for the math / math.integer API, but FYI Python has an internal <pycore_time.h> API which takes a rounding argument:

typedef enum {
    // Round towards minus infinity (-inf).
    // For example, used to read a clock.
    _PyTime_ROUND_FLOOR=0,

    // Round towards infinity (+inf).
    // For example, used for timeout to wait "at least" N seconds.
    _PyTime_ROUND_CEILING=1,

    // Round to nearest with ties going to nearest even integer.
    // For example, used to round from a Python float.
    _PyTime_ROUND_HALF_EVEN=2,

    // Round away from zero
    // For example, used for timeout. _PyTime_ROUND_CEILING rounds
    // -1e-9 to 0 milliseconds which causes bpo-31786 issue.
    // _PyTime_ROUND_UP rounds -1e-9 to -1 millisecond which keeps
    // the timeout sign as expected. select.poll(timeout) must block
    // for negative values.
    _PyTime_ROUND_UP=3,

    // _PyTime_ROUND_TIMEOUT (an alias for _PyTime_ROUND_UP) should be
    // used for timeouts.
    _PyTime_ROUND_TIMEOUT = _PyTime_ROUND_UP
} _PyTime_round_t;

There are 12 functions taking a _PyTime_round_t argument. It’s better to have 12 functions, than having 12x4 functions for each rounding mode.


The decimal module gets the rounding mode from the current decimal context, with the ability to pass an explicit context. Extract of Lib/_pydecimal.py:

class Decimal(object):
    def __add__(self, other, context=None):
        if context is None:
            context = getcontext()
        ...

Context notion is an option, which I didn’t mention. It can be used for arithmetic with builtin types, including integers. That notion could customize a lot of things: rounding, behavior of true division for integer, trap floating-point errors or not, etc.

For floating-point math it was discussed here. It’s a big change: in a lot of places (especially, optimization-related) we assume that mathematical functions (including arithmetic) are pure, while it’s not true.

We can, however, consider the context as an alternative name for option (with a different meaning, of course). I like design of the bigfloat package, which has predefined contexts with custom rounding modes, new contexts could be combined by addition like precision(1000) + RoundTowardNegative or explicitly constructed.

The problem of context is that it can affect optimizations on integers such as the peephole bytecode optimizer, AST optimizer, JIT compiler. It would no longer be possible to compute values ahead of time if a context can change the result. So maybe it’s a bad idea.

1 Like