Typorithm · easy

Dtypes, promotion and widening before arithmetic

pythonnumpynumerical-computingindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Dtypes, promotion and widening before arithmetic

0%

Concept and contract

Prevent fixed-width integer overflow by choosing the calculation dtype before performing the operation.

An ndarray stores fixed-width values, so its integer arithmetic does not inherit the unlimited precision of a Python int. An int8 element can represent values from -128 through 127. Adding two valid int8 values can exceed that range even though both inputs were accepted. The important boundary is the computation itself: casting a wrapped result afterwards cannot reconstruct the lost mathematical value.

NumPy 2 changed scalar promotion rules. Mixing a Python scalar with an array does not reliably widen the array to the type you hoped for. This lesson deliberately chooses the intermediate and result dtype rather than depending on implicit promotion or on a platform-specific default integer.

Mechanism and worked example

The worked example validates a one-dimensional list of small whole-number counters, stores them as int8 to make the storage limit visible, and widens to int64 before doubling.

The worked example validates a one-dimensional list of small whole-number counters, stores them as int8 to make the storage limit visible, and widens to int64 before doubling. The returned ndarray therefore preserves results such as 200 rather than wrapping them. Tests check int64 explicitly as well as the numerical answer. A wider dtype consumes more bytes per element, so this is a correctness decision with a measurable storage cost.

Keep input validation separate from arithmetic. Values outside the declared storage range are rejected before construction; that rejection is not a technique for preventing overflow in every later expression. The later operation still needs an appropriate intermediate type.

Complete worked example

python

import numpy as np

def inspect_example(values):
    if any(type(value) is not int or not -128 <= value <= 127 for value in values):
        raise ValueError("expected int8-range whole counters")
    stored = np.asarray(values, dtype=np.int8)
    wide = stored.astype(np.int64)
    return wide * 2
Reasoning
The worked example validates a one-dimensional list of small whole-number counters, stores them as int8 to make the storage limit visible, and widens to int64 before doubling. The returned ndarray therefore preserves results such as 200 rather than wrapping them. Tests check int64 explicitly as well as the numerical answer. A wider dtype consumes more bytes per element, so this is a correctness decision with a measurable storage cost. Keep input validation separate from arithmetic. Values outside the declared storage range are rejected before construction; that rejection is not a technique for preventing overflow in every later expression. The later operation still needs an appropriate intermediate type.
Open in local Python playground ↗

Focused decision: context is in the complete example

python

stored = np.asarray(values, dtype=np.int8)
wide = stored.astype(np.int64)
result = wide * 2
Focused decision
Type this short numerical decision after reading its complete context. The complete imports, array construction, validation, and result checks remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

The controlled mistake casts only after multiplication.

The controlled mistake casts only after multiplication. On a fixed-width array, the multiplication may already have wrapped, and the wider destination faithfully stores that wrong value. A passing example with small values is misleading because the defect appears only near the boundary. Another mistake is using object dtype indiscriminately to obtain Python integer behavior while assuming the same numerical performance and memory layout.

Floating-point promotion has different failure modes, including rounding and precision changes. float64 is not exact decimal arithmetic and int64 is still bounded. This exercise uses deliberately small counters so the widened multiplication remains well inside int64 range.

Controlled mistake: inspect the failing numerical contract

python

import numpy as np

def inspect_example(values):
    if any(type(value) is not int or not -128 <= value <= 127 for value in values):
        raise ValueError("expected int8-range whole counters")
    stored = np.asarray(values, dtype=np.int8)
    return (stored * 2).astype(np.int64)
Reasoning
The controlled mistake casts only after multiplication. On a fixed-width array, the multiplication may already have wrapped, and the wider destination faithfully stores that wrong value. A passing example with small values is misleading because the defect appears only near the boundary. Another mistake is using object dtype indiscriminately to obtain Python integer behavior while assuming the same numerical performance and memory layout. Floating-point promotion has different failure modes, including rounding and precision changes. float64 is not exact decimal arithmetic and int64 is still bounded. This exercise uses deliberately small counters so the widened multiplication remains well inside int64 range.
Open in local Python playground ↗

Independent practice

Change the multiplier from two to three without changing the accepted input range or result dtype.

Change the multiplier from two to three without changing the accepted input range or result dtype. Make the edit after locating the widening operation. The task should remain one arithmetic policy change, and every pre-existing validation check should survive.

Predict the result for 100, -100 and zero. These cases make both overflow direction and sign handling visible. Do not reduce the input range to avoid the interesting examples. That would change the public input contract rather than implement the requested computation.

Step 1: Triple the validated counters while preserving int64 arithmetic. Step 2: Retain the working imports, input validation and array shapes. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

import numpy as np

def inspect_example(values):
    if any(type(value) is not int or not -128 <= value <= 127 for value in values):
        raise ValueError("expected int8-range whole counters")
    stored = np.asarray(values, dtype=np.int8)
    wide = stored.astype(np.int64)
    return wide * 2

# Independent task: Triple the validated counters while preserving int64 arithmetic.
Reasoning
Triple the validated counters while preserving int64 arithmetic.
Open in local Python playground ↗
  • Hint 1: Keep stored.astype(np.int64) before the multiplication.
  • Hint 2: The only numerical policy change is the literal multiplier.

Solution, tests and trade-offs

The solution keeps the early int64 conversion and changes only the multiplier.

The solution keeps the early int64 conversion and changes only the multiplier. The practice suite includes values near the int8 edges, so moving the cast back after multiplication is still rejected. It also includes an empty vector, whose shape remains (0,) and whose explicit dtype is still meaningful even without any elements.

Distinguish a storage dtype from a calculation dtype in your explanation. Some reductions select a wider accumulator by default, but elementwise multiplication is a different operation. State which API behavior your code relies on instead of generalizing from one successful sum to all numerical expressions.

Practice solution and acceptance checks

python

import numpy as np

def inspect_example(values):
    if any(type(value) is not int or not -128 <= value <= 127 for value in values):
        raise ValueError("expected int8-range whole counters")
    stored = np.asarray(values, dtype=np.int8)
    wide = stored.astype(np.int64)
    return wide * 3
Reasoning
The solution keeps the early int64 conversion and changes only the multiplier. The practice suite includes values near the int8 edges, so moving the cast back after multiplication is still rejected. It also includes an empty vector, whose shape remains (0,) and whose explicit dtype is still meaningful even without any elements. Distinguish a storage dtype from a calculation dtype in your explanation. Some reductions select a wider accumulator by default, but elementwise multiplication is a different operation. State which API behavior your code relies on instead of generalizing from one successful sum to all numerical expressions.
Open in local Python playground ↗

Interview reasoning and next step

Explain why astype after overflow is too late, what iinfo tells you, and what NumPy 2 promotion rules changed for mixed scalar expressions.

Explain why astype after overflow is too late, what iinfo tells you, and what NumPy 2 promotion rules changed for mixed scalar expressions. Use a concrete wrapped result to make the loss visible. If asked to handle arbitrary counters, discuss validated bounds and a suitable wider representation rather than promising that int64 cannot overflow.

Type the short widening expression on the PC and recall its order later. The native questions test the same decision without executing code. Next, use indexing to select a meaningful portion of an array without silently changing its dimensions or ownership.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/numpy-lab-20261106.zip. Install pinned dependencies in an isolated Python environment. Run verify.py, then change the working exercise baseline and run --practice np-dtypes. The real NumPy arrays, numerical contracts, random state checks and temporary numeric files use owned fixtures only. No production database, feed, account or paid service is used. This lab executes Python; the PC typing trainer and native reading progress do not execute it.

Prevent fixed-width integer overflow by choosing the calculation dtype before performing the operation.