Integer Boundaries: C23 vs Java SE 25 vs Python 3.14
By FreePare Team · Mon Jul 27 2026 · 9 min read
A practical guide to integer boundaries in C23, Java SE 25, and Python 3.14
Integers look harmless. They arrive wearing sensible names such as count, price, index, and timeout. Then one of them reaches a boundary, flips sign, refuses to fit, or quietly changes type.
That is when the tiny accountant inside the CPU throws the ledger out of the window.
This article replaces scary folklore with code you can run. We will compare language guarantees, implementation details, and failure modes without pretending that C, Java, and Python use the same rules.
What this article covers
- The difference between a mathematical integer and a machine integer.
- Minimum-width C integer types versus exact-width types from
<stdint.h>. - Java's fixed-width signed integer types.
- Python's arbitrary-size integer semantics.
- C integer promotions before arithmetic.
- C signed-overflow undefined behavior.
- C unsigned arithmetic modulo \(2^N\).
- Java's deterministic two's-complement wraparound.
- Java's checked arithmetic methods.
- Python integer growth and its real resource limit.
- Narrowing conversions and why they are not overflow.
- Division edge cases involving the smallest signed value.
- Shift behavior and shift-distance rules.
- Boundary-oriented testing.
- Safe integer choices for files, networks, counters, and money.
1. Three languages, three contracts
The number 42 is mathematical. The expression that stores it is a programming-language contract.
C23
C defines minimum ranges for its standard integer types. It does not promise that int is exactly 32 bits on every implementation. If a format or protocol needs an exact width, use the optional exact-width types when the implementation provides them:
#include <stdint.h>
int32_t temperature_milli_celsius;
uint64_t packet_sequence;Before assuming that an exact-width type exists, portable library code can check:
#include <stdint.h>
#ifdef INT32_MAX
/* int32_t exists on this implementation. */
#endifint32_t is especially useful for an in-memory value with a required width. It does not automatically solve byte order or serialization. A 32-bit value still needs an agreed network/file encoding.
Java SE 25
Java gives its integral primitives fixed widths:
| Type | Width | Minimum | Maximum |
|---|---|---|---|
byte | 8 | -128 | 127 |
short | 16 | -32,768 | 32,767 |
int | 32 | -2,147,483,648 | 2,147,483,647 |
long | 64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
Java also has char, an unsigned 16-bit UTF-16 code unit. It is not a small signed integer and should not be used as one.
Python 3.14
Python's int represents integers of arbitrary size:
small = 42
large = 2**10_000
print(small.bit_length()) # 6
print(large.bit_length()) # 10001There is no fixed INT_MAX for Python integers. The practical limits are available memory, execution time, and safeguards on certain conversions such as extremely long integer-to-string operations.
How those values are stored is an implementation matter. Statements about "30-bit digits" describe common CPython builds, not the Python language contract.
2. The promotion trap: a short may not do short arithmetic
Conversion is the neighbouring question and it has its own rules: "Copy, Cast, or Alias?" covers what a cast does to the value, while this article covers what the type can hold.
Consider this C program:
#include <limits.h>
#include <stdio.h>
int main(void) {
short raw = SHRT_MIN;
int promoted_result = raw * 2;
short narrowed_result = raw * 2;
printf("SHRT_MIN: %d\n", SHRT_MIN);
printf("promoted result: %d\n", promoted_result);
printf("narrowed result: %d\n", narrowed_result);
}On a common implementation with a 32-bit int, raw is promoted to int before multiplication. The mathematical result, -65536, fits in int, so the multiplication itself does not overflow.
The interesting operation is the later conversion back to short. Because -65536 does not fit, converting it to a signed short produces an implementation-defined result or raises an implementation-defined signal.
That distinction matters:
- Arithmetic signed overflow can be undefined behavior.
- A representable promoted calculation is defined.
- Converting an out-of-range integer to a signed target is implementation-defined.
- A compiler target where
intcannot represent allshortvalues may follow a different promotion route.
Compile with warnings and sanitizers:
gcc -std=c2x -O2 -Wall -Wextra -Wconversion \
-fsanitize=undefined integer_promotion.cAlways state the compiler version, target architecture, flags, and type widths when publishing a result.
3. Overflow: chaos, wraparound, or a larger integer
C signed overflow
#include <limits.h>
int x = INT_MAX;
x = x + 1; /* Undefined behavior. */The C abstract machine does not define a wrapped result for signed overflow. Optimizers may assume that a valid program does not perform it.
This is why the following is not a reliable overflow check:
if (x + 1 < x) {
/* Too late: evaluating x + 1 may already be undefined. */
}Check before calculating:
#include <limits.h>
#include <stdbool.h>
bool add_int(int a, int b, int *out) {
if ((b > 0 && a > INT_MAX - b) ||
(b < 0 && a < INT_MIN - b)) {
return false;
}
*out = a + b;
return true;
}GCC and Clang also provide checked-arithmetic built-ins:
int result;
if (__builtin_add_overflow(a, b, &result)) {
/* Handle overflow. */
}That built-in is useful, but it is a compiler extension rather than portable ISO C.
C unsigned wraparound
Unsigned arithmetic is reduced modulo \(2^N\), where N is the width of the type:
#include <limits.h>
#include <stdio.h>
int main(void) {
unsigned int x = UINT_MAX;
printf("%u\n", x + 1U); /* 0 */
}Defined wraparound is useful for bit masks, hash functions, and ring counters. It is dangerous when a wrapped value represents an allocation size, security boundary, or account balance.
Also watch mixed signed/unsigned comparisons:
printf("%d\n", -1 < 0U); /* Prints 0 on common implementations. */The usual arithmetic conversions convert -1 to an unsigned value before comparison.
Java wraparound and checked arithmetic
Java defines wraparound for int and long arithmetic:
public class IntegerBoundaryLab {
public static void main(String[] args) {
int wrapped = Integer.MAX_VALUE + 1;
System.out.println(wrapped); // -2147483648
try {
int checked = Math.addExact(Integer.MAX_VALUE, 1);
System.out.println(checked);
} catch (ArithmeticException ex) {
System.out.println("Overflow detected");
}
}
}Useful checked methods include:
Math.addExactMath.subtractExactMath.multiplyExactMath.incrementExactMath.decrementExactMath.negateExactMath.toIntExact
Use them when a wrapped result would violate the domain model. Do not assume they are "too slow" without measuring the real workload.
Python grows
x = 2**63 - 1
x += 1
print(x) # 9223372036854775808Python does not wrap the value. It allocates enough storage to represent the result. Arithmetic on larger values generally requires more work, but there is no magical single boundary where a program suddenly becomes slow. Benchmark the actual operand sizes and operations.
4. The smallest signed integer is a tiny trapdoor
For a two's-complement 32-bit signed integer, the range is asymmetric:
minimum = -2,147,483,648
maximum = 2,147,483,647The positive counterpart of the minimum does not fit.
C
For int, both of these can be undefined when the result is not representable:
abs(INT_MIN);
INT_MIN / -1;Java
Java preserves its wraparound rules:
System.out.println(Math.abs(Integer.MIN_VALUE));
// -2147483648
System.out.println(Integer.MIN_VALUE / -1);
// -2147483648For checked negation:
Math.negateExact(Integer.MIN_VALUE); // ArithmeticExceptionPython
Python produces the mathematical result:
x = -(2**31)
print(abs(x)) # 2147483648
print(x // -1) # 21474836485. Shifts have rules too
In Java, only part of the right operand is used as the shift distance:
System.out.println(1 << 32); // 1, because 32 is masked to 0 for intFor an int, Java uses the low five bits of the distance. For a long, it uses the low six.
Python permits arbitrarily large non-negative shift counts:
print(1 << 100)A negative Python shift count raises ValueError.
C is stricter and more dangerous. A negative shift count or one greater than or equal to the promoted left operand's width is undefined. Left-shifting a signed value can also become undefined when the result is not representable. Use unsigned types for deliberate bit manipulation and validate the count.
6. A boundary-first test table
For every fixed-width signed operation, test at least:
| Case | Why |
|---|---|
0 | Additive identity and division boundary |
1 | Smallest positive value |
-1 | Sign conversion and division edge |
MAX | Positive overflow boundary |
MAX - 1 | One operation before overflow |
MIN | Negation, absolute-value, and division trap |
MIN + 1 | One operation above the lower boundary |
| Powers of two | Shift and bit-mask behavior |
| Values around target-width limits | Narrowing conversions |
| Mixed signed/unsigned values | C conversion surprises |
A test is stronger when it states the expected language-level result:
- Exact numeric result.
- Exception.
- Implementation-defined outcome.
- Undefined behavior that must never be executed.
- Resource-dependent Python operation.
7. Picking the right representation
Counters
Use a type whose maximum cannot be reached during the counter's lifetime, and define rollover behavior explicitly.
Money
Prefer integer minor units such as paise or cents when the scale is fixed. Check multiplication and aggregation. Arbitrary precision does not automatically solve rounding rules or currency-scale errors.
File and network formats
Use declared widths and byte order. Validate before converting into a smaller local type.
Array and allocation sizes
Use size_t in C for object sizes, but check multiplication:
if (count > SIZE_MAX / sizeof(*items)) {
/* Allocation size would overflow. */
}Database values
Do not assume the database column and application type share a range. Validate values at the boundary where they cross systems.
Try it yourself
1. Run the C promotion example with -O0 and -O2. Record sizeof(short), sizeof(int), and the compiler target.
2. Add INT_MAX and 1 using unchecked and checked code in all three languages.
3. Test MIN / -1, abs(MIN), and negation.
4. Compare Java's (int) 1e20, C's conversion of an out-of-range double, and Python's int(1e20).
5. Serialize 0x01020304 and inspect the byte order rather than guessing it.
For the layer below this one — where a value becomes bytes, and where allocation arithmetic can overflow before a single byte is written — see Bytes, Bounds, and Unicode Gremlins.
Exit test
You understand this topic when you can classify an integer operation before executing it:
1. Which types do the operands have after promotion?
2. Is the mathematical result representable?
3. Does the language define wrapping, checking, conversion, or undefined behavior?
4. Is an implementation detail being mistaken for a language guarantee?
5. What boundary tests prove the code's intended policy?
Final byte
Integers are not villains. They are extremely literal coworkers.
Tell them the width, the range, the overflow policy, and the conversion rules, and they will behave perfectly. Leave those details implicit, and they may still behave perfectly - just according to a contract you never read.
Tags: how-to-study-better, smart-study-tips, technical-interview-preparation, coding-interview-tips, coding-fundamentals, study-tips-for-students, computer-science-fundamentals, programming-fundamentals