Numbers, Booleans, None & Numeric Precision
Numeric calculations in Python involve distinct memory models: arbitrary-precision integers, IEEE 754 double-precision binary floats, exact decimal representations, and rational fractions. Choosing the right numeric type is a correctness decision before it is a performance optimization.
This chapter details CPython’s integer PyLongObject architecture, IEEE 754 floating-point mechanics, fixed-point Decimal contexts, and the historical implementation of bool as an integer subclass.
1. CPython Arbitrary-Precision Integers (PyLongObject)
Unlike C, Java, or Rust—where integers overflow at 32 or 64 bits—CPython integers (int) have arbitrary precision. A Python integer grows dynamically in memory as large as available system RAM allows.
CPython represents integers using PyLongObject:
// CPython 3.11+ Internal Representation
struct _longobject {
PyObject_HEAD
size_t long_value_header; // Stores sign and digit count
digit ob_digit[1]; // Array of 30-bit digits (uint32_t)
};On 64-bit systems, CPython allocates integers using base-$2^30$ digits:
- Numbers fitting in 30 bits ($< 1,073,741,824$) use 1 digit payload (28 bytes total).
- Larger numbers allocate additional 30-bit
ob_digitarray slots dynamically.
PyLongObject Memory Layout (64-bit CPython):
[ ob_refcnt (8B) | ob_type (8B) | long_value_header (8B) ]
|
v
[ ob_digit: 30-bit digit array ]
[ Slot 0: 2^0..2^29 ] [ Slot 1: 2^30..2^59 ] ...2. IEEE 754 Floating-Point Mechanics (float)
Python’s float is implemented directly as a C double (64-bit IEEE 754 binary floating-point number):
64-Bit IEEE 754 Binary Float Structure:
[ 1 Bit Sign | 11 Bits Exponent | 52 Bits Mantissa / Fraction ]Because binary floating-point cannot represent base-10 fractions (like $0.1$ or $0.2$) exactly in binary powers of two, standard floating-point arithmetic introduces representation error:
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False!Floating-Point Comparison Protocol:
Never test computed floats using ==. Always use math.isclose() with explicit relative and absolute tolerances (rel_tol, abs_tol).
3. Financial Exactness: decimal.Decimal & fractions.Fraction
When domain requirements demand exact base-10 arithmetic (such as financial transactions or tax calculations), use decimal.Decimal:
- Construct from Strings: Always instantiate
Decimalfrom strings or integers (Decimal("0.1")), never from a float (Decimal(0.1)), which would copy the binary approximation into the Decimal context. - Configurable Rounding Context: Set explicit precision and rounding rules (e.g.
ROUND_HALF_UPorROUND_HALF_EVEN).
from decimal import Decimal, ROUND_HALF_UP
subtotal = Decimal("19.99")
tax = (subtotal * Decimal("0.0825")).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
print(tax) # 1.65 (Exact financial calculation)4. Historical Subclassing: bool and None
boolis a Subclass ofint: In CPython’s C source code,PyBool_Type.tp_baseis set to&PyLong_Type.TrueandFalseare singletonPyLongObjectinstances with values1and0. Consequently,True + Trueevaluates to2in arithmetic contexts, though relying on this is an anti-pattern.NoneTypeSingleton:Noneis a singleton instance ofNoneType(PyNone). Identity checkx is Nonedirectly compares the raw memory pointer to&_Py_NoneStruct.