Skip to content

Numbers & numeric types

Mid / Senior

Arbitrary-precision int, IEEE 754 floats, Decimal vs Fraction, floor division, numeric hashing and promotion.

Created 23 October 2026

7 concepts · detail hidden

  1. 01How arbitrary-precision `int` is implemented, and its performance cost

    CPython represents integers internally as an array of fixed-size “digits,” stored in an internal base close to 2^30 (an implementation detail, notably not base-10 — nothing to do with how the number displays) — arithmetic on large integers is implemented as genuine multi-digit operations, conceptually similar to how a person would perform long multiplication by hand across multiple digit “columns,” rather than a single hardware instruction. This means operations on genuinely large Python integers cost measurably more than a single CPU instruction would for a fixed-width C int/long; even small Python integers still structurally pay some of this same general overhead, just operating on a trivially small digit array — part of why Python integer arithmetic is inherently somewhat slower than equivalent raw C integer arithmetic, even for small everyday numbers.

    #c-how-arbitrary-precision-int-is-implemented-and-its-performan
  2. 02Why `0.1 + 0.2 == 0.3` is `False` — IEEE 754 binary representation, mechanically

    Python’s float type is an IEEE 754 double-precision binary floating-point number — a specific, standardized way of representing real numbers in binary with finite precision. Most everyday decimal fractions, including 0.1, have no exact, finite representation in binary (the same underlying reason 1/3 has no exact finite decimal representation) — so 0.1 is actually stored internally as the closest representable binary approximation to it, not the literal exact value. These tiny representation errors don’t cancel out perfectly across subsequent arithmetic, so 0.1 + 0.2 produces a result that’s extremely close to, but not bit-for-bit identical to, the separately-computed closest binary approximation of 0.3 — hence False. The practical fix: decimal.Decimal for genuinely exact decimal arithmetic (essential for financial calculations), or comparing floats using a deliberate tolerance (math.isclose) rather than exact == for general floating-point comparisons.

    #c-why-01-02-03-is-false-ieee-754-binary-representation-mechani
  3. 03`decimal.Decimal` vs `fractions.Fraction` vs `float` — a scenario for each

    decimal.Decimal: financial/monetary calculations specifically needing exact base-10 arithmetic with precisely controllable rounding rules — a plain float silently introduces binary-representation rounding error that genuinely compounds and accumulates across many sequential transactions, which is unacceptable for money. fractions.Fraction: situations genuinely needing exact rational-number arithmetic, with no rounding whatsoever, not merely improved decimal precision — Fraction(1, 3) is stored and manipulated exactly, whereas even Decimal('0.333...') still fundamentally isn’t precisely equal to one-third, no matter how many digits you carry. Plain float: performance-sensitive numeric or scientific computation where small representation error is genuinely acceptable and raw computational speed actually matters — using Decimal inside a genuinely tight numerical loop would introduce a real, measurable performance regression compared to native floating-point arithmetic.

    #c-decimaldecimal-vs-fractionsfraction-vs-float-a-scenario-for-
  4. 04Floor division and modulo with negative operands — Python floors, C truncates

    Python’s // (floor division) always rounds toward negative infinity — genuine mathematical flooring, not truncation toward zero — and % (modulo) is deliberately defined so that the identity (a // b) * b + (a % b) == a always holds exactly. Concretely: -7 // 2 gives -4 (not -3), and -7 % 2 gives 1 (matching the divisor’s sign, per this definition) — this genuinely surprises developers coming from C or Java, where integer division instead truncates toward zero (-7 / 2 gives -3 in C) and the modulo result’s sign can differ from Python’s. This is a real, common source of subtle off-by-one and sign-related bugs specifically when porting numeric code between Python and C-family languages.

    #c-floor-division-and-modulo-with-negative-operands-python-floo
  5. 05Why `hash(1) == hash(1.0) == hash(True)` — and the collapsed-dict-keys gotcha

    Python enforces a strict language-level invariant: any two objects that compare equal via == must also produce the exact same hash value — this is required for dict/set to function correctly at all, since they rely fundamentally on hash values to locate entries efficiently. Since 1 == 1.0 == True all genuinely hold true in Python, they must all hash identically too — meaning {1: "a"}[1.0] and {1: "a"}[True] both correctly, successfully retrieve "a", since these are all treated as fundamentally the same key by the dict’s internal machinery. A genuinely real, surprising gotcha this produces: a dict literal written as {1: "int", 1.0: "float", True: "bool"} does not end up with three separate entries — it ends up with exactly one, since each subsequent key that happens to be equal to an already-seen one simply overwrites that same underlying slot in place.

    #c-why-hash1-hash10-hashtrue-and-the-collapsed-dict-keys-gotcha
  6. 06`complex` numbers — the limited everyday relevance, and the `j` suffix convention

    complex is genuinely rare outside scientific computing, signal processing, or specific numerical algorithms (FFT-based computation being a common example). The basic literal syntax is 3+4j; Python uses j (not i) as its imaginary-unit suffix — a convention borrowed specifically from electrical engineering notation, where i is already reserved for electrical current, rather than from general mathematical convention.

    #c-complex-numbers-the-limited-everyday-relevance-and-the-j-suf
  7. 07Mixing `int` and `float` in arithmetic — numeric promotion via reflected-operator dispatch

    Python automatically promotes the overall result to the “wider”/more general involved type — int + float produces a float — and, genuinely worth knowing, this isn’t implemented via some separate, special-cased “numeric type coercion” subsystem at all: it’s driven by the exact same reflected-operator dispatch machinery that underlies operator overloading generally. int.__add__(some_float) correctly recognizes it doesn’t know how to natively combine with a float operand and returns NotImplemented; float.__radd__ then picks up that fallback and actually performs the real mixed-type arithmetic, correctly returning a float. “Numeric promotion” is really just this general operator-dispatch protocol at work, not a separate, special-cased mechanism.

    #c-mixing-int-and-float-in-arithmetic-numeric-promotion-via-ref