May 2, 2021·4 min read·engineeringcstandardsintegersarithmetic

N2709 keeps bit-precise arithmetic from promoting to int

The revised N-bit integer proposal makes width part of a C type. Its exception to integer promotions preserves narrow operations while leaving widening decisions explicit.

Promoting a small integer to int is familiar C behavior, but it becomes a poor default when the integer's width specifies how much hardware an operation requires. A three-bit value described with an ordinary small integer type may participate in a wider operation than its author intended. On hardware synthesized from source, that choice affects the circuit being built, not merely the instructions a processor executes.

The WG14 document log lists N2709 on May 2. The proposal, dated April 23, revises the proposed family of _BitInt(N) types. Aaron Ballman, Melanie Blower, Tommy Hoffner, and Erich Keane identify the revision's main change as correcting integer conversion ranks and explaining their intent. This is proposed language work, not a claim that _BitInt is already standard C.

I like C's ability to describe representation requirements directly, especially where it defines an interface to another system. This proposal makes the intended arithmetic width visible in the type. Its usefulness depends on what happens inside an expression, because a declaration alone cannot prevent the usual conversions from changing the operation's type.

Width becomes part of type identity

The proposed spelling takes an integer constant expression specifying the number of bits, including the sign bit for a signed type. _BitInt(N) is signed by default, and unsigned _BitInt(N) names its unsigned counterpart. Different widths designate distinct types. A value of one width therefore does not become interchangeable with another width merely because both occupy the same amount of storage on a particular implementation.

The paper discusses uses including protocol fields and color representations. Its strongest motivation concerns FPGA compilation, where an unnecessarily wide operation consumes hardware resources. The proposal also allows widths larger than the ordinary integer types. The underlying requirement is to express an exact arithmetic width, whether the implementation maps it to native instructions or emulates it with larger units.

Exact arithmetic width does not imply an object occupies exactly that many physically addressable bits. The ABI discussion describes implementation choices for alignment and storage size, including using a containing basic type. That distinction matters for structures and arrays. A type intended to compute with a narrow range is not by itself a portable serialization layout for a packed network field.

The proposed wording gives implementations a maximum supported width through BITINT_MAXWIDTH. It also sets different minimum widths for signed and unsigned forms because the signed form includes a sign bit. Such limits distinguish this proposal from an unbounded arbitrary-precision arithmetic library. Width is a compile-time type parameter subject to implementation support.

Promotions would defeat the hardware requirement

Ordinary integer promotions can widen operands narrower than int before an operator runs. N2709 exempts bit-precise types from those promotions. The usual arithmetic conversions still determine a common type, but they do so without first turning every small bit-precise operand into an ordinary int.

The paper's example combines a signed twelve-bit value with an unsigned three-bit value. The signed type has greater rank and can represent the unsigned type's values, so the common type is the signed twelve-bit type. There is no preliminary promotion of both operands to int. This keeps the expression's width related to the types the programmer selected.

Mixing a bit-precise type with an ordinary integer still requires care. The ordinary operand can undergo its normal promotion. The paper gives examples involving char, where promotion to int affects the common type. Avoiding promotions for one type family does not suspend C's conversion rules for everything else in the expression.

Conversion rank is therefore central to the revision. A bit-precise signed type ranks above standard integer types of smaller width. A standard type ranks above a bit-precise type of the same width. The rank relative to an extended integer type of the same width remains implementation-defined. These distinctions determine which type controls mixed arithmetic.

Widening must happen before multiplication

The proposal does not automatically choose a result width large enough to contain every mathematical result. Its rationale rejects that approach because expression widths can grow unexpectedly and cause poor performance. The author of an expression must select a wider operation when the narrower type cannot contain the intended result.

Under the proposed language spelling, a multiplication can widen an operand first:

_BitInt(32) multiply(_BitInt(8) left, _BitInt(24) right)
{
    return left * (_BitInt(32))right;
}

This example illustrates the proposal, rather than portable C17 code. The cast changes the type participating in multiplication. Merely assigning a narrow multiplication result to a wider destination would address storage after the operation, which is too late to choose a wider arithmetic operation. The paper uses the same widening principle in its proposed examples.

Signed overflow remains undefined under the proposal. Unsigned arithmetic retains defined wrapping behavior. Bit precision therefore makes a width requirement explicit without turning signed arithmetic into a checked numeric system. A caller still needs a range argument for a signed calculation, just as it does for existing signed types.

I find that separation useful. Choosing a width expresses a domain constraint, while proving that intermediate values fit is a separate responsibility. An implicit promise to widen every expression would hide that responsibility and could change the hardware costs the feature is intended to control.

N2709 presents both language-keyword wording and an alternative annex based on macros in a header. The authors favor a language type, partly because APIs using it must remain intelligible to languages without a C preprocessor. They also discuss the awkwardness of giving the macro-based signed form a spelling that cannot accept the ordinary unsigned specifier.

Several facilities remain future work in the paper, including dedicated formatted I/O support and atomics. Literal suffixes and an operator to query width also appear in that list. This limits what the proposal promises today. Generic selection also matches individual widths as distinct types, without a single association for the entire family. Its central contribution is a family of types with specified conversion behavior, and each additional facility needs rules consistent with that family.