ML Foundations
When a NumPy sum wraps in a narrow integer dtype
NumPy reductions can return a wrapped integer when the accumulator dtype is too small; check the range and choose the accumulator deliberately.
Evidence and testing
Review record: Tensor Almanac automated evidence gate (no independent human review). Example versions, recorded outputs and limitations appear below.
The dtype sets the representable range
NumPy integer dtypes have fixed widths. An `int8` value can represent only -128 through 127, while Python’s built-in `int` can grow to represent larger integers. A result that crosses a NumPy dtype’s range can therefore look like a plausible number even though it no longer represents the mathematical result. [1]
This is easy to miss when arrays store small counters, bucket totals, or encoded values to save memory. The storage choice also sets arithmetic limits, so the dtype should be checked against the largest intermediate and total the code can produce. This is an engineering consequence of fixed-width storage, rather than a claim that every narrow array will overflow. [1]
References: [1]
A reduction uses an accumulator dtype
`numpy.sum` uses its `dtype` argument for both the returned value and the accumulator. If an integer input is narrower than the platform integer, the default sum promotes the accumulator; an explicitly supplied narrow dtype can instead keep accumulation in that small range. NumPy documents integer summation as modular arithmetic, with no error raised on overflow. [2]
The example sums two `int8` values of 100. The default reduction uses `int64` on this environment and returns 200, while requesting `dtype=np.int8` returns -56. These two outputs come from different accumulator choices; the wrapped value is not evidence that the original values were negative. [2]
Executed with Python 3.12.14 and NumPy 2.3.5; exact stdout and source-byte SHA-256 are preserved in private research evidence.
import numpy as np
values = np.array([100, 100], dtype=np.int8)
limits = np.iinfo(np.int8)
print(f"input dtype: {values.dtype}")
print(f"int8 range: {limits.min} to {limits.max}")
print(f"default sum: {np.sum(values)} ({np.sum(values).dtype})")
print(f"narrow int8 sum: {np.sum(values, dtype=np.int8)}")
print(f"wide int64 sum: {np.sum(values, dtype=np.int64)}")
Recorded output
input dtype: int8 int8 range: -128 to 127 default sum: 200 (int64) narrow int8 sum: -56 wide int64 sum: 200
Choose headroom before the reduction
Use `numpy.iinfo(dtype)` to inspect the minimum and maximum values for a fixed-width integer type, then choose an accumulator that can hold the largest possible intermediate total. In this example, `np.sum(values, dtype=np.int64)` makes the intended wider accumulator explicit and returns 200. [1, 2]
The accumulator only helps if the values have not already wrapped in an earlier operation. If a calculation occurs before the sum, promote the operands before that calculation and test a boundary case as well as the ordinary case. The appropriate width depends on the data and maximum aggregate size; a wider integer is not an unlimited range. [1]