Skip to content

Per-type pricing

Every flop type's weight, how it is measured, and the operations that count as compositions of the types. The rules say why each price is what it is; the interpretations resolve the gray zones the notes below cite; the measured weight values live with the built-in data and the per-type machine-code pages.

Every weight is a latency weight: the latency difference between two dependent-chain benchmark probes whose loops differ by the operation being priced — or, for the two conversion instructions no probe reaches, a published latency for that instruction. Per-extra-argument weights divide the difference by the number of arguments separating the two probes. Composite prices below add their parts' weights as if the parts chain — the declared bias of rule 4 · decompositions-sum-latencies.

The flop types

Flop type Weight measured as Rule Notes
ABS f_add_absf_add 1
ACOS f_add_sin_acosf_add_sin 2
ACOSH f_add_acoshf_add 2, 4
ADD f_add_addf_add 1
ASIN f_add_sin_asinf_add_sin 2
ASINH f_add_asinhf_add 2, 4
ATAN f_add_atanf_add 2, 4
ATAN2 f_add_atan2f_add 2, 4
ATANH f_add_halfsin_atanhf_add_halfsin 2
CBRT f_add_cbrtf_add 2 rule 2 · measurement-fallbacks: numba's np.cbrt wraps the libm call in NaN/sign handling CPython's math.cbrt never executes, so the probe calls libm through a ctypes binding -- the bare call CPython executes
COMP f_lte_addsubf_add 1 rule 2 · fmax-shares-comp-weight, rule 3 · classifiers-price-their-question: the subtrahend is the ADD/SUB average, and the branchy source compiles branchless -- the weight prices compare-and-select machinery, matching what float comparisons cost in optimized code. math.fmax/fmin reuse this weight: their port -- the IEEE max/min instruction (ARM's fmaxnm/fminnm) -- is one instruction of the same compare-select class, the same reuse as math.fabs -> ABS. They stay a different value function from the builtin min/max (NaN-quieting selection vs a comparison chain returning whichever operand survives, order-dependent under NaN): shared machinery, and so a shared price, not shared semantics
COPYSIGN f_add_copysignf_add 1
COS f_add_cosf_add 2, 4
COSH f_add_acosh_coshf_add_acosh 2, 4
DIST f_add_dist2f_add 2 rule 2 · measurement-fallbacks: hand-rolled overflow-safe port (no libm dist exists); prices the scaled algorithm math.dist executes, not a naive sum of squares
DIST_XARG (f_add_dist8f_add_dist2) / 6 2 per-extra-coordinate slope of the same overflow-safe port as DIST
DIV f_div_divf_div 1
ERF f_add_erff_add 2, 4
ERFC f_add_erfcf_add 2, 4
EXP f_add_log_expf_add_log 2
EXP10 f_add_log10_exp10f_add_log10 2 rule 2 · exp10-is-pow: 10 ** x cannot strength-reduce to an exp10 call -- exp10 is not standard C -- so a port emits pow(10, x), and that is exactly what the weight measures
EXP2 f_add_log2_exp2f_add_log2 2 rule 2 · exp10-is-pow: 2 ** x strength-reduces here because a standard-C port emits C99 exp2; the weight is measured on the real exp2 call
EXPM1 f_add_log1p_expm1f_add_log1p 2
FMA f_fma_fmaf_fma 1 rule 1 · fma-stays-as-written: the one fusion observable from Python, so the one that is counted
FMOD f_add_fmodf_add 2, 4
GAMMA f_add_gammabase_gammaf_add_gammabase 2
HYPOT f_add_hypotf_add 2 rule 2 · measurement-fallbacks: the 2-argument base weight is the real libm call; the hand-rolled scaled probes only supply the per- extra-coordinate slope, validated against this base (within ~10%)
HYPOT_XARG (f_add_hypot_scaled8f_add_hypot_scaled2) / 6 2 rule 2 · measurement-fallbacks: hand-rolled overflow-safe port (numba cannot compile n-ary hypot); deterministic per-coordinate cost, so rule 2 applies to the port
LGAMMA f_add_gammabase_lgammaf_add_gammabase 2
LOG f_add_logf_add 2
LOG10 f_add_log10f_add 2
LOG1P f_add_log1pf_add 2
LOG2 f_add_log2f_add 2
MINUS f_add_minusf_add 1
MUL f_mul_mulf_mul 1
POW f_pow_powf_pow 2
REMAINDER f_add_remainderf_add 2, 4 rule 2 · measurement-fallbacks: numba has no math.remainder, so the probe calls libm through a ctypes binding -- still the bare call CPython executes
RND f_add_roundf_add 1
SIN f_add_sinf_add 2, 4
SINH f_add_asinh_sinhf_add_asinh 2, 4
SQRT f_add_sqrtf_add 1
SUB f_add_subf_add 1
SUMPROD f_add_sumprod2f_add 2 rule 2 · measurement-fallbacks: faithful port of CPython's extended-precision (TripleLength) accumulation, error terms emitted through the llvm.fma intrinsic; the 2-element base includes the close-out
SUMPROD_XELEM (f_add_sumprod8f_add_sumprod2) / 6 2 per-extra-element slope of the same TripleLength port as SUMPROD
TAN f_add_tanf_add 2, 4
TANH f_add_tanhf_add 2, 4
F2I (no benchmark probe — priced from spec sheets and third-party tables) 1 float→int conversion instruction of the port
I2F (no benchmark probe — priced from spec sheets and third-party tables) 1 int→float conversion instruction of the port

Decomposed operations

Operations with no flop type of their own count as compositions of the types above, each citing the rule and interpretation that fix it.

Floored division (rule 1; constant steps fold per the rules page):

  • x // y → DIV + RND (the floor is a float→float round, not an F2I);
  • x % y and divmod(x, y) → DIV + RND + MUL + SUB — divmod shares the quotient's floor with the remainder, so it costs the same as a lone %.

The round family (rule 1; the n ≠ 0 form carries a rule-3 gap):

  • round(x) → F2I, returning int;
  • round(x, 0) → RND, returning CountedFloat;
  • round(x, n ≠ 0) → MUL + RND + DIV — scale into the digit position, round, scale back; the unscale is a true divide (a power-of-ten factor has no exact reciprocal). Two stated gaps: CPython itself computes this via correctly-rounded decimal conversion, whose input-dependent machinery is knowingly not modeled; and the scale-and-unscale port is the price at every n, including magnitudes beyond ~308 where the scale factor is not a finite double and no author would write that port.

Float→int exits (rule 1): int(x), math.floor, math.ceil, math.trunc → F2I each.

The classifiers (rule 3 · classifiers-price-their-question):

  • math.isnan → COMP;
  • math.isinf, math.isfinite → ABS + COMP each;
  • math.isnormal, math.issubnormal → ABS + 2 COMP each;
  • math.signbit → COMP.

Formula-priced calls (rule 3 · formula-price-is-fixed):

  • math.isclose → SUB + 3 ABS + MUL + 3 COMP;
  • math.fsum → (n−1) ADD, where n is the number of elements passed, counted or not.

Sequence calls (rule 1 · loops-do-not-fold): math.prod → (n−1) MUL for n elements, n for a start that is counted or whose value is not 1 — no element folds, because the port's loop body runs once per element whatever it holds. Like every price on this page it applies only where a counted value is involved; a sequence of plain floats counts nothing. The built-in sum is not interceptable and does fold, costing n ADD for n counted values and less when plain elements come first; the workarounds are in known limitations.

Single-instruction respellings (rule 1):

  • math.degrees, math.radians → MUL each, the conversion factor folding to a constant;
  • float.is_integer → RND + COMP, the counted spelling x // 1.0 == x — RND rather than F2I, because no int ever materializes.

Constant operands — a constant exponent, base or logarithm base changes what the port emits. Those ladders are enumerated where they are decided: exponent-chain-bound, exp10-is-pow and log-constant-base-folds.

Arity-scaled operations

At two or more arguments, math.hypot, math.dist and math.sumprod are not decompositions: they count dedicated types measured on the real algorithms the calls execute (rule 2 · measurement-fallbacks). The single-argument forms take the shortcut the call itself takes, which is a composition of ordinary types.

Operation Counts
hypot, n ≥ 2 arguments HYPOT + (n−2) HYPOT_XARG
hypot, 1 argument ABS — the call computes |x|, and a port emits fabs
dist, n ≥ 2 dimensions DIST + (n−2) DIST_XARG
dist, 1-D SUB + ABS — the single-coordinate shortcut, not the scaled machinery
sumprod, n ≥ 2 elements SUMPROD + (n−2) SUMPROD_XELEM
sumprod, 1 element SUMPROD — the base alone

The base weights are measured on two-argument probes, so a one-element sumprod pays a base that includes work a second element would have shared; the slope is not subtracted below the base. hypot and dist avoid the question by taking their own single-argument shortcuts.