Math patching semantics¶
While a FlopCountingContext is active, math module functions that require
counting (sqrt, log2, pow, ...) are temporarily replaced by counting
equivalents. This page documents the exact contract of that patching.
Nothing is patched at import time¶
Merely importing counted_float leaves the process's math module completely
untouched. The math module is only patched while at least one
FlopCountingContext is active: patches are applied when the first context
enters and removed when the last context exits, so nested contexts behave
correctly.
The snapshot/restore contract¶
The patching contract mirrors unittest.mock.patch / pytest monkeypatch
conventions:
- at first context entry, the current
mathfunctions are snapshotted (whatever they are, including other packages' patches) and the counting replacements delegate through them; - at last context exit, that snapshot is restored, unconditionally — so
math.*ends up exactly as it was when the first context entered.
The snapshot is (re)captured at patch time, not at import time: another
package may have applied its own math patches after counted_float was
imported, and the library delegates through — and later restores — whatever is
current, rather than silently wiping those patches.
Composing with third-party patches¶
Well-nested (LIFO) third-party patching composes correctly: a patch applied before the first context entered is delegated through while counting and still in place after the last context exits.
Mis-nested patching is unsupported: a patch applied inside an active counting context but not removed before the last context exits is discarded — the library simply restores its snapshot.
What the replacements count¶
The counting replacements only count operations touching CountedFloat
values; on plain floats they delegate straight through with no counting (see
the counting model).
Two functions carry extra classification logic, applying the same
constant-detection heuristic as the ** operator (int operand = hardcoded
constant in the source):
math.pow(x, y)classifies likex ** y:x**2counts MUL,2**xcounts EXP2,10**xcounts EXP10, other cases count POW (plus I2F for an int operand).math.log(x, base)classifies per log variant: base omitted → LOG; int base 2 / 10 → LOG2 / LOG10 (a compiled port callslog2/log10directly); other int base → LOG + MUL (a port computeslog(x) * CwithC = 1/log(base)folded at compile time); float base → a port computeslog(x)/log(base): LOG per counted operand + DIV.
The full per-operation counting rules are in the FLOP types reference.
Which functions are patched is also the boundary of what gets counted — see Known limitations.