<p>In most statically typed languages, integers are offered in 8-bit types and increasing powers of 2. However, C has a new keyword <code>_BitInt</code> which as I understand allows the creation of integers with arbitrary bit widths. And Ada allows the programmer to specify the range of types, which implicitly defines the number of bits used.</p>
<p>A big reason for having fixed size power of 2 integer types is those are the native integer types on processors. But allowing the programmer to define the exact number of bits to use is more versatile and expressive. Are there any other advantages of having fixed power of 2 integers, and what does implementing a custom width integer take?</p>

<types><integers>
What are the advantages of having a set number of fixed sized integers versus defining the exact number of bits in every integer?
CC BY-SA 4.0
2023-07-12T08:17:45.787Z
Think of HLS applications, where arbitrary bit width makes sense.
SK-logic
CC BY-SA 4.0
2023-07-12T08:54:13.857Z
Related: [Why don't many languages have integer range types?](https://langdev.stackexchange.com/q/1802/1149)
user23013
CC BY-SA 4.0
2023-07-12T16:08:08.050Z
This goes way back, PL/I and COBOL allow you to specify integer sizes. `FIXED BIN(10)`
Barmar
<h1>Pro: Extracting bit fields</h1>
<p>One advantage of ints with arbitrary sizes in bits is that non-power-of-two fields can be more easily extracted from blobs of binary data. E.g., a 6-byte piece of binary data could be represented as an <code>i48</code>, and if this represented a packet with two 4-bit fields and two 20-bit fields, this could easily be represented as <code>(i4, i4, i20, i20)</code>.</p>
<p>Some languages might allow a simple cast from the former to the latter, while others might require <code>union</code>s or a library to do the translation, but regardless, having types for non-power-of-two integers allows representing these fields in a more precise way that avoids encoding invalid states in the way that, e.g., <code>(i8, i8, i32, i32)</code> would (even if this would have the same representation in memory).</p>

CC BY-SA 4.0
2023-07-12T03:14:29.083Z
2023-07-12T03:14:29.083Z
rydwolf
CC BY-SA 4.0
2023-07-12T04:17:39.183Z
I’ll note that these aren’t always handled well. Swift’s Unicode.Scalar type was changed from i21 to i32 very early on — before 1.0, iinm — because LLVM didn’t handle it very well.
Bbrk24
<p>Pro (enumarate all widths)</p>
<ul>
<li>implementing the compiler is more, but a lot easier work</li>
<li>the total number is usually small and reflects what hardware can do</li>
<li>C++-style bitfields are usually sufficient if needed at all; most code dealing with bits can be expressed with bit-wise operations adequately</li>
<li>Java is successful with essentially having only 32bit signed int (as of today, arrays are still restricted to int indices)</li>
<li>programmers may not understand that 21bit integers are still represented as 32bit integers because the hardware or memory layouting algorithm has no means to optimize out the dead space</li>
</ul>
<p>Contra (allow all widths)</p>
<ul>
<li>getting the compiler to translate the first tiny program is tricky, because you have to start with templates if you do it like <a href="https://github.com/tyr-lang/stdlib/blob/37ccedca493791c2dfd0ab1940b3fbbdff5f6cbd/lang/src/integer.tyr#L9C10-L9C10" rel="nofollow noreferrer">Tyr</a> and expose Integer as template or you need to expose the width as special syntax in your language which might be fine if you declare <code>i\d+</code> an integer keyword.</li>
<li>if size is exposed as template, templates can abstract over size</li>
<li>LLVM can create code for such integers, so there is no real extra compiler implementation cost if you use an LLVM back-end anyway</li>
<li>code operating on uncommon bit-widths is a lot easier to read (note that such code is very uncommon in most domains)</li>
</ul>

CC BY-SA 4.0
2023-07-12T08:08:00.780Z
2023-07-12T08:08:00.780Z
feldentm
<h2>Advantage</h2>
<p>Packing data is possible, both to the compiler and the user. An extreme example of this are <code>u1</code> ints, a.k.a, booleans (as one bit unsigned integer has only two possible representations).</p>
<p>So if your language is focused or has an option to optimize to size (code or memory), this can be relevant. You can possibly pack 8 sequential booleans in a octec, instead of 8 sequential <a href="https://www.boost.org/doc/libs/1_45_0/libs/integer/doc/html/boost_integer/integer.html" rel="nofollow noreferrer"><code>int_fast_t</code></a> (the performante choice) that in some machines will be wasteful as 8x64 = 512 bits.</p>
<h3>Disadvantage 1: slow</h3>
<p>It will be <em>slow</em>. To the point that the users complain about it.</p>
<p>Modern languages gives integers that are power of two, multiple of octets <strong>because</strong> modern CPUs have them. And because CPUs have then, they are <em>fast</em>. As in hardware fast. As in <em>there is nothing more fast than this is possible</em> fast. And users like that.</p>
<h3>Disadvantage 2: trashed on multithread</h3>
<p>You may encounter situations where your compiler (or someone compilers) do the packing sometimes, but sometimes not. The problem with packed data is that CPUs may <em>don't have how to write on packed data in an atomic way</em>. The CPU will need to load the whole word, bitfidle it to simulate the partial write, and write back the whole memory world.</p>
<p>So, sometimes an <code>a.b = true;</code> in one thread, <code>a.c = false;</code> in another, in a packed struct, will result in impossible to reason results, another name for undefined behaviour.</p>

CC BY-SA 4.0
2023-07-12T13:43:38.673Z
2023-07-12T13:43:38.673Z
André L F S Bacci
<p>TL;DR: Nothing prohibits a hybrid approach.</p>
<p>Having few choices has an obvious advantage in guiding selection. Those few pervade all the code, leading to re-use and better testing. And they can be chosen to be reasonably efficient in hardware.</p>
<p>Having free choice has the advantage of allowing you to choose exactly what you want. The biq question is whether it is also what you need, and whether being more deliberate would not be an advantage (exact vs. least vs. fast).</p>
<p>As an example, C (and C++) has a set of common sizes (with guaranteed minimal limits), and got a set of guaranteed sizes (using aliases), with exact, least, and fast variants.<br />
C++ could add a template for selecting them generically. And it would not be that hard to synthesize bigger sizes as needed.</p>
<p>So, why not have a way to freely choose, preferably with aliases to request the fast/least option, and either add aliases for the common ones, or just recommend them in the docs?</p>

CC BY-SA 4.0
2023-07-13T19:47:54.950Z
2023-07-13T19:47:54.950Z
Deduplicator
<p>It simplifies the language and the compiler. The types offered are typically those that the CPU can directly work with using single instructions. Having those basic types available in the language means it is trivial to compile operations on those types to efficient machine code.</p>
<p>Most languages allow you to create custom types, so in most cases you can create a custom type for arbitrary wide integers. Thus, there is no need for the language to have explicit support for them. It also avoids the issue mentioned by Martin Kochanski, and leaves it up to you to decide how integers that don't exactly fit the built-in types behave and are stored.</p>

CC BY-SA 4.0
2023-07-12T12:21:23.103Z
2023-07-12T12:21:23.103Z
G. Sliepen
<p>Most of the things in the world appear in arbitrary numbers but it may be cases when the number is known and fixed, like the number days in a week, the number of values in quaternion (4) or the number of dimensions in the Universe (3). In such cases range specification is informative.</p>
<p>It may also be cases when specifying the number of bits adds to the clarity. For instance, if we write a simulator of some hardware containing various registers, it is nice to define the number of bits as used by these registers.</p>
<p>So generally a useful feature, and I remember programming tasks relevant to both cases.</p>

CC BY-SA 4.0
2023-07-12T18:02:09.703Z
2023-07-12T18:02:09.703Z
Audrius Meškauskas
<p>If you don't have predefined widths, then you can't have predefined maximum and minimum values of a type. You also can't have a predefined identity value, or a predefined zero value used for field initialisation.</p>
<p>Furthermore, while variables of arbitrary size (i.e. number of bits, number of digits etc.) if the language allows them to be packed for storage it might result in problems passing them by reference, or- in the general case- taking their address.</p>
<p>So while they are extremely useful, they might not be a good choice for the base language types.</p>

CC BY-SA 4.0
2023-07-13T07:44:18.647Z
2023-07-13T07:44:18.647Z
Mark Morgan Lloyd
CC BY-SA 4.0
2023-07-13T09:18:53.287Z
_"If you don't have predefined widths, then you can't have predefined maximum and minimum values of a type [...]"_ => What? There's no reason you can't have `i7::MIN` and `i7::MAX`, 0 is always 0, etc... are you talking about a different concept altogether?
Matthieu M.
CC BY-SA 4.0
2023-07-13T10:25:31.963Z
@MatthieuM. I would suggest that things like that are precomputed rather than predefined, i.e. are compile-time evaluation of expression to appear in constants.
Mark Morgan Lloyd
CC BY-SA 4.0
2023-07-13T11:25:44.687Z
Isn't that an implementation detail? The compiler will put the value either way.
Matthieu M.
<p>You need to distinguish between two meanings of “5-bit integer”:</p>
<ol>
<li>An integer which occupies 5 bits of storage, so that three of them will fit into a 16-bit word.</li>
<li>An integer which can hold values between 0 and 31 (or -16 and +15).</li>
</ol>
<p><strong>1</strong> is always going to be slow and cumbersome, a headache for the compiler and a bore for the CPU. Its positive qualities are that it saves memory if you have a million of these things, and that the CPU has a built-in instruction saying “copy three [or twelve] of these at once”. If you can guarantee “no overflow, ever”, then the CPU also has a built-in “copy three or twelve at once” instruction.</p>
<p><strong>2</strong> is easily achieved at no cost or complication. Assuming 16-bit words for ease of exposition, encode 0 as 0000, 1 as 0800, 2 as 1000, and so on up to 31 as F800. Then all arithmetic and comparison will work using the built-in instructions. Moreover, it is impossible to have a value outside the range 0 to 31.</p>

CC BY-SA 4.0
2023-07-12T07:08:44.953Z
2023-07-12T07:08:44.953Z
Martin Kochanski
CC BY-SA 4.0
2023-07-12T08:03:55.903Z
I'd argue what you're talking about relates more to storage than the type itself. It would seem perfectly resonable to store 5-bit integers byte-aligned by default, just like bitfield structs are, but occasionally pack them tighter, like a bitset does
abel1502
CC BY-SA 4.0
2023-07-12T08:20:38.213Z
As for "will always be slow", CPUs are not the only possible target of compilation.
SK-logic
CC BY-SA 4.0
2023-07-12T14:01:19.827Z
"*achieved at no cost*" — a shift will be needed to convert the 32-bit result of a multiplication, and division will require a shift and a mask.
Ray Butterworth
CC BY-SA 4.0
2023-07-12T15:21:24.587Z
did you mean "_add_ three or twelve at once" in that second one?
ilkkachu
CC BY-SA 4.0
2023-07-12T16:09:16.990Z
"a bore for the CPU". Is the robot uprising going to happen sooner if we don't keep giving them interesting things to do?
Barmar
CC BY-SA 4.0
2023-07-12T22:55:15.773Z
I don't get your encoding. Are you using little endian for those hex representations or something?
Bergi
CC BY-SA 4.0
2023-08-06T17:57:25.053Z
Re: "slow and cumbersome", this depends on the processor architecture. I expect that many GPUs are designed to do fast packing and unpacking for certain shapes of packed structs, since it's quite common to e.g. pass vectors in formats like `INT_2_10_10_10_REV` where the x, y and z components have ten bits each and the w component has two bits. So supporting 2-bit or 10-bit ints ─ or 5-bit or 9-bit ints for `INT_5_9_9_9_REV` ─ would make sense in a language targeting GPUs.
kaya3
# What are the advantages of having a set number of fixed sized integers versus defining the exact number of bits in every integer?
Posted by: username_0 on 2023-07-12
In most statically typed languages, integers are offered in 8-bit types and increasing powers of 2. However, C has a new keyword `_BitInt` which as I understand allows the creation of integers with arbitrary bit widths. And Ada allows the programmer to specify the range of types, which implicitly defines the number of bits used.

A big reason for having fixed size power of 2 integer types is those are the native integer types on processors. But allowing the programmer to define the exact number of bits to use is more versatile and expressive. Are there any other advantages of having fixed power of 2 integers, and what does implementing a custom width integer take?
**Tags:** types integers
> **Comments:**
> **username_1**: Think of HLS applications, where arbitrary bit width makes sense.  
> **username_2**: Related: [Why don't many languages have integer range types?](https://langdev.stackexchange.com/q/1802/1149)  
> **username_3**: This goes way back, PL/I and COBOL allow you to specify integer sizes. `FIXED BIN(10)`  
---
## 8 answers
### Answer by username_13 on 2023-07-12. Score: 11
You need to distinguish between two meanings of “5-bit integer”:

1. An integer which occupies 5 bits of storage, so that three of them will fit into a 16-bit word.
2. An integer which can hold values between 0 and 31 (or -16 and +15).

**1** is always going to be slow and cumbersome, a headache for the compiler and a bore for the CPU. Its positive qualities are that it saves memory if you have a million of these things, and that the CPU has a built-in instruction saying “copy three [or twelve] of these at once”. If you can guarantee “no overflow, ever”, then the CPU also has a built-in “copy three or twelve at once” instruction.

**2** is easily achieved at no cost or complication. Assuming 16-bit words for ease of exposition, encode 0 as 0000, 1 as 0800, 2 as 1000, and so on up to 31 as F800. Then all arithmetic and comparison will work using the built-in instructions. Moreover, it is impossible to have a value outside the range 0 to 31.
> **Comments:**
> **username_14** (2023-07-12): I'd argue what you're talking about relates more to storage than the type itself. It would seem perfectly resonable to store 5-bit integers byte-aligned by default, just like bitfield structs are, but occasionally pack them tighter, like a bitset does  
> **username_1** (2023-07-12): As for "will always be slow", CPUs are not the only possible target of compilation.  
> **username_15** (2023-07-12): "*achieved at no cost*" — a shift will be needed to convert the 32-bit result of a multiplication, and division will require a shift and a mask.  
> **username_16** (2023-07-12): did you mean "_add_ three or twelve at once" in that second one?  
> **username_3** (2023-07-12): "a bore for the CPU". Is the robot uprising going to happen sooner if we don't keep giving them interesting things to do?  
> **username_17** (2023-07-12): I don't get your encoding. Are you using little endian for those hex representations or something?  
> **username_18** (2023-08-06): Re: "slow and cumbersome", this depends on the processor architecture. I expect that many GPUs are designed to do fast packing and unpacking for certain shapes of packed structs, since it's quite common to e.g. pass vectors in formats like `INT_2_10_10_10_REV` where the x, y and z components have ten bits each and the w component has two bits. So supporting 2-bit or 10-bit ints ─ or 5-bit or 9-bit ints for `INT_5_9_9_9_REV` ─ would make sense in a language targeting GPUs.  
---
### Answer by username_4 on 2023-07-12. Score: 4
# Pro: Extracting bit fields

One advantage of ints with arbitrary sizes in bits is that non-power-of-two fields can be more easily extracted from blobs of binary data. E.g., a 6-byte piece of binary data could be represented as an `i48`, and if this represented a packet with two 4-bit fields and two 20-bit fields, this could easily be represented as `(i4, i4, i20, i20)`.

Some languages might allow a simple cast from the former to the latter, while others might require `union`s or a library to do the translation, but regardless, having types for non-power-of-two integers allows representing these fields in a more precise way that avoids encoding invalid states in the way that, e.g., `(i8, i8, i32, i32)` would (even if this would have the same representation in memory).
> **Comments:**
> **username_5** (2023-07-12): I’ll note that these aren’t always handled well. Swift’s Unicode.Scalar type was changed from i21 to i32 very early on — before 1.0, iinm — because LLVM didn’t handle it very well.  
---
### Answer by username_6 on 2023-07-12. Score: 4
Pro (enumarate all widths)

* implementing the compiler is more, but a lot easier work
* the total number is usually small and reflects what hardware can do
* C++-style bitfields are usually sufficient if needed at all; most code dealing with bits can be expressed with bit-wise operations adequately
* Java is successful with essentially having only 32bit signed int (as of today, arrays are still restricted to int indices)
* programmers may not understand that 21bit integers are still represented as 32bit integers because the hardware or memory layouting algorithm has no means to optimize out the dead space

Contra (allow all widths)

* getting the compiler to translate the first tiny program is tricky, because you have to start with templates if you do it like [Tyr](https://github.com/tyr-lang/stdlib/blob/37ccedca493791c2dfd0ab1940b3fbbdff5f6cbd/lang/src/integer.tyr#L9C10-L9C10) and expose Integer as template or you need to expose the width as special syntax in your language which might be fine if you declare `i\d+` an integer keyword.
* if size is exposed as template, templates can abstract over size
* LLVM can create code for such integers, so there is no real extra compiler implementation cost if you use an LLVM back-end anyway
* code operating on uncommon bit-widths is a lot easier to read (note that such code is very uncommon in most domains)
---
### Answer by username_7 on 2023-07-12. Score: 3
## Advantage

Packing data is possible, both to the compiler and the user. An extreme example of this are `u1` ints, a.k.a, booleans (as one bit unsigned integer has only two possible representations).

So if your language is focused or has an option to optimize to size (code or memory), this can be relevant. You can possibly pack 8 sequential booleans in a octec, instead of 8 sequential [`int_fast_t`](https://www.boost.org/doc/libs/1_45_0/libs/integer/doc/html/boost_integer/integer.html) (the performante choice) that in some machines will be wasteful as 8x64 = 512 bits.

### Disadvantage 1: slow

It will be *slow*. To the point that the users complain about it.

Modern languages gives integers that are power of two, multiple of octets **because** modern CPUs have them. And because CPUs have then, they are *fast*. As in hardware fast. As in *there is nothing more fast than this is possible* fast. And users like that.

### Disadvantage 2: trashed on multithread

You may encounter situations where your compiler (or someone compilers) do the packing sometimes, but sometimes not. The problem with packed data is that CPUs may *don't have how to write on packed data in an atomic way*. The CPU will need to load the whole word, bitfidle it to simulate the partial write, and write back the whole memory world.

So, sometimes an `a.b = true;` in one thread, `a.c = false;` in another, in a packed struct, will result in impossible to reason results, another name for undefined behaviour.
---
### Answer by username_8 on 2023-07-13. Score: 2
TL;DR: Nothing prohibits a hybrid approach.

Having few choices has an obvious advantage in guiding selection. Those few pervade all the code, leading to re-use and better testing. And they can be chosen to be reasonably efficient in hardware.

Having free choice has the advantage of allowing you to choose exactly what you want. The biq question is whether it is also what you need, and whether being more deliberate would not be an advantage (exact vs. least vs. fast).

As an example, C (and C++) has a set of common sizes (with guaranteed minimal limits), and got a set of guaranteed sizes (using aliases), with exact, least, and fast variants.  
C++ could add a template for selecting them generically. And it would not be that hard to synthesize bigger sizes as needed.

So, why not have a way to freely choose, preferably with aliases to request the fast/least option, and either add aliases for the common ones, or just recommend them in the docs?
---
### Answer by username_9 on 2023-07-12. Score: 2
It simplifies the language and the compiler. The types offered are typically those that the CPU can directly work with using single instructions. Having those basic types available in the language means it is trivial to compile operations on those types to efficient machine code.

Most languages allow you to create custom types, so in most cases you can create a custom type for arbitrary wide integers. Thus, there is no need for the language to have explicit support for them. It also avoids the issue mentioned by Martin Kochanski, and leaves it up to you to decide how integers that don't exactly fit the built-in types behave and are stored.
---
### Answer by username_10 on 2023-07-12. Score: 1
Most of the things in the world appear in arbitrary numbers but it may be cases when the number is known and fixed, like the number days in a week, the number of values in quaternion (4) or the number of dimensions in the Universe (3). In such cases range specification is informative.

It may also be cases when specifying the number of bits adds to the clarity. For instance, if we write a simulator of some hardware containing various registers, it is nice to define the number of bits as used by these registers.

So generally a useful feature, and I remember programming tasks relevant to both cases.
---
### Answer by username_11 on 2023-07-13. Score: 0
If you don't have predefined widths, then you can't have predefined maximum and minimum values of a type. You also can't have a predefined identity value, or a predefined zero value used for field initialisation.

Furthermore, while variables of arbitrary size (i.e. number of bits, number of digits etc.) if the language allows them to be packed for storage it might result in problems passing them by reference, or- in the general case- taking their address.

So while they are extremely useful, they might not be a good choice for the base language types.
> **Comments:**
> **username_12** (2023-07-13): _"If you don't have predefined widths, then you can't have predefined maximum and minimum values of a type [...]"_ => What? There's no reason you can't have `i7::MIN` and `i7::MAX`, 0 is always 0, etc... are you talking about a different concept altogether?  
> **username_11** (2023-07-13): @MatthieuM. I would suggest that things like that are precomputed rather than predefined, i.e. are compile-time evaluation of expression to appear in constants.  
> **username_12** (2023-07-13): Isn't that an implementation detail? The compiler will put the value either way.  
---
