Skip to content

Instantly share code, notes, and snippets.

@Eisenwave
Last active April 8, 2023 10:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Eisenwave/2a7d7a4e74e99bbb513984107a6c63ef to your computer and use it in GitHub Desktop.
Save Eisenwave/2a7d7a4e74e99bbb513984107a6c63ef to your computer and use it in GitHub Desktop.
C++26 Proposal Draft - <intdiv> Header for Integer Divisions

<intdiv> Header for Integer Divisions

Introduction

C++ currently offers only truncating integer division. As a consequence, the remainder operator's sign is same as the sign of the dividend. Alternative rounding modes and remainder sign behaviour are useful.

This proposal adds an <intdiv> header containing free functions for obtaining the quotient and remainder for different rounding modes, such as rounding towards the nearest integer, towards the infinities, etc.

Motivation & Scope

Alternative rounding modes are frequently required to solve different problems with division. The user has to implement these modes by hand, and naive implementations (e.g.[1][2]) suffer mainly from these issues:

  • additional overflow cases are introduced through:
    • the use of std::abs on std::numeric_limits<Int>::min() or
    • addition/subtraction on the operands prior to division
  • multiple distinct divisions take place
  • negative operands are often not supported, or handled incorrectly
  • (also see a non-exhaustive list of these mistakes being made online)

Sometimes, users also emulate these integer divisions by using float and std::floor, or other floating point types and rounding functions. However, this does not guarantee precise results and is very expensive on platforms without hardware-supported floating point numbers.

Since integer divisions are basic mathematical operations, and implementing them in a robust and performant way is non-trivial, I propose a new <intdiv> header with functions that yield the quotient or remainder for alternative rounding modes.

Wording

Add a new feature-test macro in [version.syn]:

#define __cpp_lib_intdiv   202304L // also in <intdiv>

Add a new subclause 28.9 Integer division functions [intdiv] to clause 28 Numerics library [numerics]

28.9 Integer division functions [intdiv]

28.9.1 General

The header <intdiv> provides components to compute quotients and remainders of divisions between integers, using a variety of rounding modes.

28.9.2 Header <intdiv> synopsis [intdiv.syn]

// all freestanding
namespace std {

// [intdiv.div], integer quotient functions
template <class T>
constexpr T div_down(T dividend, T divisor);

template <class T>
constexpr T div_up(T dividend, T divisor);

template <class T>
constexpr T div_floor(T dividend, T divisor);

template <class T>
constexpr T div_ceil(T dividend, T divisor);

template <class T>
constexpr T div_round_down(T dividend, T divisor);

template <class T>
constexpr T div_round_up(T dividend, T divisor);

template <class T>
constexpr T div_round_floor(T dividend, T divisor);

template <class T>
constexpr T div_round_ceil(T dividend, T divisor);

template <class T>
constexpr T div_round_even(T dividend, T divisor);

template <class T>
constexpr T div_round_odd(T dividend, T divisor);

template <class T>
constexpr T div_euclid(T dividend, T divisor);

// [intdiv.rem], integer remainder functions
template <class T>
constexpr T rem_down(T dividend, T divisor);
  
template <class T>
constexpr T rem_floor(T dividend, T divisor);
  
template <class T>
constexpr T rem_euclid(T dividend, T divisor);

} // namespace std

28.9.3 Integer quotient functions [intdiv.div]

template <class T>
constexpr T div_down(T dividend, T divisor);

1 Constraints: T is an integral type ([basic.fundamental]).

2 Returns: The quotient q of the division dividend / divisor, rounded towards zero. The behaviour is undefined if divisor == 0 or if q is not representable by T.

[Note 1: this is the behaviour of the division operator. [expr.mul] --end note]
[Note 2: This function is intended to allow the user to express the choice of rounding modes explicitly. --end note]


template <class T>
constexpr T div_up(T dividend, T divisor);

3 Constraints: T is an integral type ([basic.fundamental]).

4 Returns: The quotient q of the division dividend / divisor, rounded away from zero. The behaviour is undefined if divisor == 0 or if q is not representable by T.


template <class T>
constexpr T div_floor(T dividend, T divisor);

5 Constraints: T is an integral type ([basic.fundamental]).

6 Returns: The quotient q of the division dividend / divisor, rounded towards negative infinity. The behaviour is undefined if divisor == 0 or if q is not representable by T.


template <class T>
constexpr T div_ceil(T dividend, T divisor);

7 Constraints: T is an integral type ([basic.fundamental]).

8 Returns: The quotient q of the division dividend / divisor, rounded towards positive infinity. The behaviour is undefined if divisor == 0 or if q is not representable by T.


template <class T>
constexpr T div_round_down(T dividend, T divisor);

9 Constraints: T is an integral type ([basic.fundamental]).

10 Returns: The quotient q of the division dividend / divisor, rounded towards the nearest integer. Exact ties are rounded towards zero. The behaviour is undefined if divisor == 0 or if q is not representable by T.

[Note: 3 An exact tie occurs when the quotient is the midpoint of two integers. --end note]


template <class T>
constexpr T div_round_up(T dividend, T divisor);

11 Constraints: T is an integral type ([basic.fundamental]).

12 Returns: The quotient q of the division dividend / divisor, rounded towards the nearest integer. Exact ties are rounded towards zero. The behaviour is undefined if divisor == 0 or if q is not representable by T.


template <class T>
constexpr T div_round_floor(T dividend, T divisor);

13 Constraints: T is an integral type ([basic.fundamental]).

14 Returns: The quotient q of the division dividend / divisor, rounded towards the nearest integer. Exact ties are rounded towards negative infinity. The behaviour is undefined if divisor == 0 or if q is not representable by T.


template <class T>
constexpr T div_round_ceil(T dividend, T divisor);

15 Constraints: T is an integral type ([basic.fundamental]).

16 Returns: The quotient q of the division dividend / divisor, rounded towards the nearest integer. Exact ties are rounded towards positive infinity. The behaviour is undefined if divisor == 0 or if q is not representable by T.


template <class T>
constexpr T div_round_even(T dividend, T divisor);

17 Constraints: T is an integral type ([basic.fundamental]).

18 Returns: The quotient q of the division dividend / divisor, rounded towards the nearest integer. Exact ties are rounded towards the next quotient which is a multiple of two. The behaviour is undefined if divisor == 0 or if q is not representable by T.


template <class T>
constexpr T div_round_odd(T dividend, T divisor);

19 Constraints: T is an integral type ([basic.fundamental]).

20 Returns: The quotient q of the division dividend / divisor, rounded towards the nearest integer. Exact ties are rounded towards the next quotient which is not a multiple of two. The behaviour is undefined if divisor == 0 or if q is not representable by T.

template <class T>
constexpr T div_euclid(T dividend, T divisor);

21 Constraints: T is an integral type ([basic.fundamental]).

22 Returns: The quotient q of the Euclidean division dividend / divisor, i.e. towards negative infinity when divisor is positive, and towards positive infinity when divisor is negative. The behaviour is undefined if divisor == 0 or if q is not representable by T.

28.9.4 Integer remainder functions [intdiv.rem]

template <class T>
constexpr T rem_down(T dividend, T divisor);

1 Constraints: T is an integral type ([basic.fundamental]).

2 Returns: The remainder r of the division q = div_down(dividend, divisor), such that q * divisor + r == dividend. The behaviour is undefined if divisor == 0 or if the quotient dividend / divisor is not representable by T.

[Note 1: The sign of the remainder is equal to the sign of the dividend. --end note]
[Note 2: The result is equal to dividend % divisor where % is the builtin remainder operator. --end note]
[Note 3: This function is intended to let the user express their choice of remainder operation explicitly. --end note]


template <class T>
constexpr T rem_floor(T dividend, T divisor);

3 Constraints: T is an integral type ([basic.fundamental]).

4 Returns: The remainder r of the division q = div_floor(dividend, divisor), such that q * divisor + r == dividend. The behaviour is undefined if divisor == 0 or if the quotient dividend / divisor is not representable by T.

[Note 4: The sign of the remainder is equal to the sign of the divisor. --end note]


template <class T>
constexpr T rem_euclid(T dividend, T divisor);

5 Constraints: T is an integral type ([basic.fundamental]).

6 Returns: The remainder r of the division q = div_euclid(dividend, divisor), such that q * divisor + r == dividend. The behaviour is undefined if divisor == 0 or if the quotient dividend / divisor is not representable by T.

[Note 5: The remainder is non-negative. --end note]

Design Decisions

Why <intdiv>?

The name is modeled after the [utility.intcmp] section. It would have been possible to use an existing header such as <utility>, <numeric>, or<cmath> for these functions, but there are simply too many. There is also growth potential; in the future, this header may contain:

  • division functions that allow mixed signedness operations
  • division functions that yield both the quotient and the remainder simultaneously
  • division utilities such as fast_division

What naming scheme should be used for the rounding functions?

The proposal uses the well-established round, trunc, floor, and ceil names which are also found among the common mathematical functions. However, we deviate from this in the case of trunc, since it has no well-established counterpart and use Java's terminology instead.

Alternative: Explicit description of rounding direction

It has also been commonly suggested to use a scheme such as:

  • div_to_zero
  • div_from_zero
  • div_to_pos_inf
  • div_to_nearest_ties_to_zero
  • ...

The main reason to avoid this scheme is the verbosity. Functions in this header are intended to be used frequently in user's expressions, and extreme verbosity hurts ergonomics.

Why do these div functions exist? Why are some of the rem functions seemingly missing?

Quotient functions

Division towards the infinities as well as division towards/away from zero are commonly useful for programming. The same applies to rounding division and all of its tie-breaking modes.

Rounding to the nearest integer with even/odd tie breaks

These tie breaks are useful because they don't skew the distribution when mapping from a wider range onto a narrower range of values. Rounding to the infinities makes it more likely to obtain greater/lower values. Rounding to zero or away from it makes it more likely to obtain greater/lower magnitudes. Rounding to even/odd quotients may have the most desirable statistical properties.

Also see StackOverflow - What is HALF_EVEN rounding for?.

Euclidean quotient function

Euclidean divison has some uses in theoretical computer science and cryptography, but exists primarily as a counterpart to rem_euclid.

Missing remainder functions

Not every division function has a matching remainder function, because some are trivially implemented and less useful:

  • rem_down results in the same sign as the dividend
  • a hypothetical rem_up would result in the opposite sign and can be trivially implemented by negating the result of rem_down
  • rem_floor results in the same sign as the divisor
  • a hypothetical rem_ceil would result in the opposite sign and can be trivially implemented by negating the result of rem_floor
  • rem_euclid results in non-negative remainder
  • a hypothetical function with a negative remainder could be trivially implemented by negating the result of rem_euclid

Remainder functions for the div_round family of functions would result in a remainder that changes sign based on whether the rounded quotient is closer to the next lower/higher value. This is substantially less useful compared to the other remainder functions.

Should mixed-signedness operations be supported?

The proposal currently reject mixed sign arguments. There are two ways of handling them:

A: Always produce the correct result where possible

This option is very difficult to implement and even if done correctly, would result in runtime penalties. For example, it is possible for an unsigned divisor to not be representible by the dividend's type.

  • for divison towards zero, this always results in zero
  • for division away from zero, this results in the signum of the dividend
  • for flooring division, this results in -1 for all negative numbers, 0 otherwise
  • for ceiling division, this results in:
    • 0 for all negative numbers, except for the minimum, which may result in -1
    • 1 for all positive numbers
  • for rounding division, this requires a comparison between the dividend and half of the divisor
    • the result is one of -1, 0, or 1
    • special cases for the minimum representible dividend may be needed
    • ties need to be broken correctly, which can be difficult for maximum representible dividend

As can be seen, the added logic can be non-trivial. If a divisor is overly large, the result is always representible, but not in the case of an overly large dividend. Dividing by -1 would always result in a value which is too large to fit the signed type.

We also need to consider whether the resulting type should be signed or unsigned:

  • a signed type could never fit the result of a division of an overly large dividend by 1, unless it is the minimum signed value
  • an unsigned type could never fit the result of a division of an integer by -1, unless the dividend is zero

Neither option is obviously more correct than the other.

B: Declare cases where one operand has a value not representible by the other as undefined behaviour

In this case, the result type can be the common type, or its std::make_signed counterpart if exactly one of the argument types is signed.

While this solution appears elegant, it declares many valid divisions such as -1 / UINT_MAX as undefined behaviour, which is intuitively wrong.

C: Only allow divisions between signed types, don't even allow unsigned types

While this option is by far the simplest, the implementation of different rounding modes is greatly simplified for unsigned types. A function template may perform better (even on debug builds) with a special case for unsigned types. It also makes no sense to restrict division to just signed integers, since it is a fundamental operation that can be performed between all integers.

Division between unsigned types is also useful, e.g. ceiling division may be used to determine how many chunks of size n are required to fit a quantity x: std::div_ceil(n, x). The types involved in this operation are frequently unsigned, so this restriction would inconvenience the user.

D: Constrain division functions to only same-signedness (this proposal)

Aforementioned edge cases like huge divisors and dividends are relatively uncommon. The user of these functions should know how they want to handle them if needed, and which resulting type to choose. This choice may be specific to the user's domain and must not be mandated by the standard library. The <division> header can be used as a basis which correctly handles all of the regular cases, and typically it is sufficient.

Disallowing mixed sign operations also makes it possible to implement option A or B in the future, as a mostly non-breaking change.

Problems with Naive Implementations

Common Mistakes

A common way[1] to implement ceiling division is by adjusting the dividend before division:

(x + y - 1) / y  // ceiling division
(x + y / 2) / y  // rounding division

However, this technique can easily overflow near the minimum and maximum representible values for the divisor. It also does not handle negative quotients correctly: div_ceil(-3, -2) -> 3 != ceil(-3. / -2.) -> 2 for the above code.

A naive modulus function[2] can also suffer from multiple issues:

int positive_modulo(int i, int n) {
    return (i % n + n) % n;
}

Just as in the first example, we may overflow for very large n. This implementation also requires two distinct divisions, which can be very expensive on some architectures.

Known Problems with Widespread Implementations

q = (x + y - 1) / y;
// or avoiding overflow in x+y
q = 1 + ((x - 1) / y); // if x != 0

ceiling division by Ganesh Kamath - 'Code Frenzy', accessed 2023-04-06

Problems:

  • integer overflow when subtracting from x
  • no support for signed operands

Found via: First Google search result for "c++ ceiling integer division", most upvoted answer


q = x/y + (x % y != 0);

ceiling division by Miguel Figueiredo, accessed 2023-04-06

Problems:

  • no support for negative operands

Found via: First Google search result for "c++ ceiling integer division"


q = (x % y) ? x / y + 1 : x / y;

ceiling division by Tatsuyuki Ishi, accessed 2023-04-06

Problems:

  • incorrect handling of negative operands (div_ceil(-1, 2) => 1)

Found via: First Google search result for "c++ ceiling integer division"


int div_ceil(int numerator, int denominator)
{
    std::div_t res = std::div(numerator, denominator);
    return res.rem ? (res.quot + 1) : res.quot;
}

ceiling division by Nathan Ernst, accessed 2023-04-06

Problems:

  • incorrect handling of negative operands (div_ceil(-1, 2) => 1)

Found via: First Google search result for "c++ ceiling integer division"


int div_ceil(int x, int y) {
    return x / y + (x % y > 0);
}

ceiling division by cubuspl42, accessed 2023-04-06

Problems:

  • incorrect handling of negative operands (div_ceil(-1, 2) => 1)

Found via: First Google search result for "c++ ceiling integer division"


q = (x > 0)? 1 + (x - 1)/y: (x / y);

ceiling division by Ben Voigt, accessed 2023-04-06

Problems:

  • no support for negative y

Found via: First Google search result for "c++ ceiling integer division"


q = x/y + !!(x % y);

ceiling division by Greg Kramida, accessed 2023-04-06

Problems:

  • no support for negative operands

Found via: First Google search result for "c++ ceiling integer division"


q = x/y + !!(x % y);

ceiling division by Greg Kramida, accessed 2023-04-06

Problems:

  • no support for negative operands

Found via: First Google search result for "c++ ceiling integer division"


int ceili(int numerator, int denominator)
{
    return (numerator / denominator + (numerator % denominator != 0));
}

ceiling division by Ken Gregg, accessed 2023-04-06

Problems:

  • incorrect handling of negative operands (div_ceil(-1, 2) => 1)

Found via: 2nd Google search result for "c++ ceiling integer division"


q = x / y;
if (q * y < x)
    q++;

ceiling division by Shubh Pachori, accessed 2023-04-06

Problems:

  • incorrect handling of negative operands (div_ceil(1, -2) => 1)

Found via: 3rd Google search result for "c++ ceiling integer division"


long floordiv (long num, long den)
{
  if (0 < (num^den))
    return num/den;
  else
    {
      ldiv_t res = ldiv(num,den);
      return (res.rem)? res.quot-1 
                      : res.quot;
    }
}

flooring division by Ichthyo, accessed 2023-04-06

Problems:

  • GCC and clang emit call ldiv or call div, preventing further optimizations

Found via: 1st Google search result for "c++ flooring integer division"


static int div_floor(int a, int b) {
    return (a ^ b) < 0 && a ? (1 - abs(a)) / abs(b) - 1 : a / b;
}

static int div_round(int a, int b) {
    return (a ^ b) < 0 ? (a - b / 2) / b : (a + b / 2) / b;
}

static int div_ceil(int a, int b) {
    return (a ^ b) < 0 || !a ? a / b : (abs(a) - 1) / abs(b) + 1;
}

Rounding Modes For Integer Division by njohnny84, accessed 2023-04-06

Problems:

  • div_floor uses abs(a) and abs(b) which is undefined behaviour if either is INT_MIN
  • div_round overflows on a + b / 2 for a=INT_MAX, b >= 2
  • it is unclear to the reader how exact ties are handled
  • div_ceil uses abs(a) and abs(b) which is undefined behaviour if either is INT_MIN

Found via: 4th Google search result for "rounding integer division c++"

Conclusion

There are some correct implementations online, but they are very difficult to discover. The most popular solutions typically don't support negative values or support them incorrectly. Having a reliable implementation in the standard library would be of great benefit.

// Possible implementation of the functions in the <intdiv> header
#include <concepts>
#include <type_traits>
#include <algorithm>
template <std::integral T>
constexpr T div_down(T dividend, T divisor) noexcept
{
return dividend / divisor;
}
template <std::integral T>
constexpr T div_up(T dividend, T divisor) noexcept
{
const T quotient_sign(T(dividend < 0 ? -1 : 1) * T(divisor < 0 ? -1 : 1));
return dividend / divisor
+ (dividend % divisor != 0) * quotient_sign;
}
template <std::integral T>
constexpr T div_floor(T dividend, T divisor) noexcept
{
const bool quotient_negative{(dividend < 0) != (divisor < 0)};
return dividend / divisor
- (dividend % divisor != 0 && quotient_negative);
}
template <std::integral T>
constexpr T div_ceil(T dividend, T divisor) noexcept
{
const bool quotient_positive{(dividend < 0) == (divisor < 0)};
return dividend / divisor
+ (dividend % divisor != 0 && quotient_positive);
}
// exposition only
enum class tie_break {
down,
up,
floor,
ceil,
even,
odd
};
// exposition only
template <typename>
inline constexpr bool always_false = false;
// exposition only
template <tie_break Break, std::integral T>
constexpr T div_round(T dividend, T divisor) noexcept
{
const T dividend_sign(dividend < 0 ? -1 : 1);
const T divisor_sign(divisor < 0 ? -1 : 1);
const T quotient_sign(dividend_sign * divisor_sign);
// remainder can be never the most negative value, so taking the absolute is safe
const T abs_remainder(dividend % divisor * dividend_sign);
const T abs_half_divisor(divisor / 2 * divisor_sign);
if constexpr (Break == tie_break::trunc) {
return dividend / divisor
+ (abs_remainder > abs_half_divisor)
* quotient_sign;
}
else if constexpr (Break == tie_break::magnify) {
return dividend / divisor
+ (abs_remainder >= abs_half_divisor + bool(divisor % T{2}))
* quotient_sign;
}
else if constexpr (Break == tie_break::floor) {
return dividend / divisor
+ (abs_remainder >= abs_half_divisor + (divisor % T{2} || quotient_sign >= 0))
* quotient_sign;
}
else if constexpr (Break == tie_break::ceil) {
return dividend / divisor
+ (abs_remainder >= abs_half_divisor + (divisor % T{2} || quotient_sign < 0))
* quotient_sign;
}
else if constexpr (Break == tie_break::even) {
const T quotient(dividend / divisor);
return quotient
+ (abs_remainder >= abs_half_divisor + (divisor % T{2} || quotient % 2 == 0))
* quotient_sign;
}
else if constexpr (Break == tie_break::odd) {
const T quotient(dividend / divisor);
return quotient
+ (abs_remainder >= abs_half_divisor + (divisor % T{2} || quotient % 2))
* quotient_sign;
}
else {
static_assert(always_false<T>, "Unknown tie break");
}
}
template <std::integral T>
constexpr T div_round_down(T dividend, T divisor) noexcept
{
return div_round<tie_break::trunc>(dividend, divisor);
}
template <std::integral T>
constexpr T div_round_up(T dividend, T divisor) noexcept
{
return div_round<tie_break::magnify>(dividend, divisor);
}
template <std::integral T>
constexpr T div_round_floor(T dividend, T divisor) noexcept
{
return div_round<tie_break::floor>(dividend, divisor);
}
template <std::integral T>
constexpr T div_round_ceil(T dividend, T divisor) noexcept
{
return div_round<tie_break::ceil>(dividend, divisor);
}
template <std::integral T>
constexpr T div_round_even(T dividend, T divisor) noexcept
{
return div_round<tie_break::even>(dividend, divisor);
}
template <std::integral T>
constexpr T div_round_odd(T dividend, T divisor) noexcept
{
return div_round<tie_break::even>(dividend, divisor);
}
template <std::integral T>
constexpr T div_euclid(T dividend, T divisor) noexcept
{
return divisor < 0 ? div_ceil(dividend, divisor)
: div_floor(dividend, divisor);
}
template <std::integral T>
constexpr T rem_down(T n, T mod) noexcept
{
return n % mod;
}
template <std::integral T>
constexpr T rem_floor(T n, T mod) noexcept
{
T remainder(n % mod);
return remainder + ((remainder < 0) != (mod < 0) ? mod : T{0});
}
template <std::integral T>
constexpr T rem_euclid(T n, T mod) noexcept
{
using U = std::make_unsigned_t<T>;
T remainder(n % mod);
// We temporarily use unsigned arithmetic to avoid overflowing on negation of mod.
// This happens when dividing by the minimum representible value.
// By using an unsigned type, we are then normally subtracting the negative remainder from a large positive mod.
return T(remainder + U(remainder < 0 ? U(mod < 0 ? -U(mod) : U(mod)) : U{0}));
}
// also see https://godbolt.org/z/TPqEqfP5G
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment