counted-float 1.0.3__tar.gz → 1.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {counted_float-1.0.3 → counted_float-1.0.5}/.gitignore +4 -1
  2. {counted_float-1.0.3 → counted_float-1.0.5}/PKG-INFO +34 -8
  3. {counted_float-1.0.3 → counted_float-1.0.5}/README.md +33 -7
  4. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/counting/_counted_float.py +93 -8
  5. {counted_float-1.0.3 → counted_float-1.0.5}/pyproject.toml +1 -1
  6. {counted_float-1.0.3 → counted_float-1.0.5}/LICENSE +0 -0
  7. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/__init__.py +0 -0
  8. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/__init__.py +0 -0
  9. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/_cli.py +0 -0
  10. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/benchmarking/__init__.py +0 -0
  11. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/benchmarking/counted_float/__init__.py +0 -0
  12. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/benchmarking/counted_float/_counted_float_benchmark.py +0 -0
  13. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/benchmarking/flops/__init__.py +0 -0
  14. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/benchmarking/flops/_array_generator.py +0 -0
  15. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/benchmarking/flops/_flops_benchmark_suite.py +0 -0
  16. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/benchmarking/flops/_flops_micro_benchmark.py +0 -0
  17. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/benchmarking/micro/__init__.py +0 -0
  18. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/benchmarking/micro/_micro_benchmark.py +0 -0
  19. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/compatibility/__init__.py +0 -0
  20. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/compatibility/_numba.py +0 -0
  21. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/counting/__init__.py +0 -0
  22. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/counting/_builtin_data.py +0 -0
  23. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/counting/_context_managers.py +0 -0
  24. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/counting/_global_counter.py +0 -0
  25. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/counting/config/__init__.py +0 -0
  26. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/counting/config/_config.py +0 -0
  27. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/counting/config/_defaults.py +0 -0
  28. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/__init__.py +0 -0
  29. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/_base.py +0 -0
  30. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/_flop_counts.py +0 -0
  31. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/_flop_type.py +0 -0
  32. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/_flop_weights.py +0 -0
  33. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/_flops_benchmark_meta_data.py +0 -0
  34. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/_flops_benchmark_result.py +0 -0
  35. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/_flops_benchmark_type.py +0 -0
  36. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/_instruction_latencies.py +0 -0
  37. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/models/_micro_benchmark_result.py +0 -0
  38. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/utils/__init__.py +0 -0
  39. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/utils/_cpu_freq.py +0 -0
  40. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/utils/_formatting.py +0 -0
  41. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/utils/_geo_mean.py +0 -0
  42. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/utils/_latency.py +0 -0
  43. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/utils/_missing_data.py +0 -0
  44. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/utils/_rounding.py +0 -0
  45. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/_core/utils/_timer.py +0 -0
  46. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/benchmarking/__init__.py +0 -0
  47. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/config/__init__.py +0 -0
  48. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v8_x/benchmarks/apple_m1_github_actions.json +0 -0
  49. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v8_x/benchmarks/apple_m3_max_mbp16.json +0 -0
  50. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v8_x/benchmarks/apple_m3_mba15.json +0 -0
  51. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v8_x/benchmarks/aws_graviton_2_neoverse_n1_ec2_m6g_xlarge.json +0 -0
  52. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v8_x/benchmarks/aws_graviton_3_neoverse_v1_ec2_m7g_xlarge.json +0 -0
  53. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v8_x/specs/arm_cortex_a76.json +0 -0
  54. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v8_x/specs/arm_cortex_x1.json +0 -0
  55. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v8_x/specs/arm_neoverse_n1.json +0 -0
  56. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v8_x/specs/arm_neoverse_v1.json +0 -0
  57. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_0/benchmarks/aws_graviton_4_neoverse_v2_ec2_m8g_xlarge.json +0 -0
  58. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_0/benchmarks/azure_cobalt_100_neoverse_n2_github_actions.json +0 -0
  59. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_0/specs/arm_cortex_x2.json +0 -0
  60. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_0/specs/arm_cortex_x3.json +0 -0
  61. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_0/specs/arm_neoverse_n2.json +0 -0
  62. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_0/specs/arm_neoverse_v2.json +0 -0
  63. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_2/benchmarks/apple_m4_pro_mbp16.json +0 -0
  64. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_2/specs/arm_cortex_x4.json +0 -0
  65. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_2/specs/arm_cortex_x925.json +0 -0
  66. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/arm/v9_2/specs/arm_neoverse_v3.json +0 -0
  67. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2017_zen1/benchmarks/amd_ryzen_1700x.json +0 -0
  68. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2017_zen1/other/analysis_uops_info_zen1+.json +0 -0
  69. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2020_zen3/benchmark/amd_epyc_7763_linux_github.json +0 -0
  70. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2020_zen3/benchmark/amd_epyc_7763_windows_github.json +0 -0
  71. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2020_zen3/other/analysis_agner_fog_r7_5800x.json +0 -0
  72. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2020_zen3/other/analysis_uops_info_zen3.json +0 -0
  73. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2022_zen4/benchmark/amd_epyc_9r14_ec2_m7a_xlarge.json +0 -0
  74. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2022_zen4/other/analysis_agner_fog_r9_7900x.json +0 -0
  75. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2022_zen4/other/analysis_uops_info_zen4.json +0 -0
  76. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2022_zen4/other/specs_amd.json +0 -0
  77. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2024_zen5/benchmark/amd_epyc_9r45_ec2_m8a_xlarge.json +0 -0
  78. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2024_zen5/other/analysis_agner_fog_r7_9800x3d.json +0 -0
  79. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/amd/2024_zen5/other/specs_amd.json +0 -0
  80. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/benchmarks/intel_i7_8550U_windows.json +0 -0
  81. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/benchmarks/intel_i7_8700B_macos_github_actions.json +0 -0
  82. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/other/analysis_agner_fog_coffee_lake.json +0 -0
  83. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/other/analysis_uops_info_coffee_lake.json +0 -0
  84. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/benchmarks/intel_xeon_8375c_ice_lake_ec2_m6i_xlarge.json +0 -0
  85. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/other/analysis_agner_fog_ice_lake.json +0 -0
  86. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/other/analysis_uops_info_ice_lake.json +0 -0
  87. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/other/analysis_uops_info_tiger_lake.json +0 -0
  88. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2021_golden_cove_gen_12/benchmarks/intel_xeon_8488c_sapphire_rapids_ec2_m7i_xlarge.json +0 -0
  89. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2021_golden_cove_gen_12/other/analysis_uops_info_alder_lake_p.json +0 -0
  90. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2021_golden_cove_gen_12/other/specs_intel.json +0 -0
  91. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2022_raptor_cove_gen_13_14/benchmarks/intel_xeon_8559c_emerald_rapids_ec2_i7i_xlarge.json +0 -0
  92. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2022_raptor_cove_gen_13_14/other/specs_intel.json +0 -0
  93. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2023_redwood_cove_ultra_1/benchmarks/intel_xeon_6975p_granite_rapids_ec2_m8i_xlarge.json +0 -0
  94. {counted_float-1.0.3 → counted_float-1.0.5}/counted_float/data/x86/intel/2023_redwood_cove_ultra_1/other/specs_intel.json +0 -0
@@ -33,4 +33,7 @@ Thumbs.db
33
33
  .vscode
34
34
 
35
35
  # other local files
36
- .local
36
+ .local
37
+ # Only working_docs/README.md is committed; everything else here stays out of git.
38
+ working_docs/*
39
+ !working_docs/README.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: counted-float
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: Count floating-point operations in Python code & benchmark relative flop costs.
5
5
  Project-URL: Source, https://github.com/bertpl/counted-float
6
6
  Project-URL: ChangeLog, https://github.com/bertpl/counted-float/blob/main/CHANGELOG.md
@@ -20,9 +20,9 @@ Requires-Dist: numba>=0.50; extra == 'numba'
20
20
  Description-Content-Type: text/markdown
21
21
 
22
22
  ![shields.io-python-versions](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)
23
- ![genbadge-test-count](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.3/badge-test-count.svg)
24
- ![genbadge-test-coverage](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.3/badge-coverage.svg)
25
- ![counted_float logo](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.3/splash.webp)
23
+ ![genbadge-test-count](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.5/badge-test-count.svg)
24
+ ![genbadge-test-coverage](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.5/badge-coverage.svg)
25
+ ![counted_float logo](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.5/splash.webp)
26
26
 
27
27
  # counted-float
28
28
 
@@ -90,7 +90,33 @@ s = math.sqrt(cf1) # s = CountedFloat(0.9)
90
90
  is_float = isinstance(s, float) # True
91
91
  ```
92
92
 
93
- ## 2.2. FLOP counting context managers
93
+ ### The counting model: what gets counted and why
94
+
95
+ The counting model is a contract with two sides:
96
+
97
+ - **Your side:** wrap every *runtime input* of the algorithm you want to measure in `CountedFloat`
98
+ at its boundary. Contagion does the rest — everything derived from those inputs stays counted
99
+ automatically.
100
+ - **The library's side:** count every FLOP that a compiled (C/Rust/...) port of your algorithm
101
+ would execute *on data derived from those inputs*.
102
+
103
+ From this contract follows a clean rule for everything else: **constants are free.** Any plain
104
+ float encountered mid-computation is, by the contract, not an input — so it must be a constant of
105
+ the algorithm (a literal, a coefficient, a tolerance), and operations purely among constants are
106
+ work a compiled port would fold at compile time or precompute. This is why e.g. `math.sqrt(3)`
107
+ counts nothing: the port ships `sqrt(3)` as a precomputed constant.
108
+
109
+ The library detects constants through two mechanisms, applying the same rule:
110
+
111
+ - **Unwrapped values** (plain floats): constants by the wrapping contract, as above.
112
+ - **`int` operands**: evidence of a *hardcoded* constant — ints don't fall out of floating-point
113
+ computations, so an int operand almost certainly appears literally in your source. This enables
114
+ counting the strength reductions a compiled port would apply: `x**2` counts MUL (i.e. `x*x`),
115
+ `2**x` counts EXP2, `math.log(x, 10)` counts LOG10 — while `x**2.0`, with a float that *could*
116
+ be a runtime value, conservatively counts a generic POW.
117
+
118
+ The flip side: an unwrapped runtime input is invisible to the counter — that is a wrapping error
119
+ at your algorithm's boundary, not something the library can detect. When in doubt, wrap.
94
120
 
95
121
  Once we use the `CountedFloat` class, we can use the available context managers to count the number of
96
122
  flops performed by `CountedFloat` objects.
@@ -636,21 +662,21 @@ This appendix provides detailed information about how each floating-point operat
636
662
  - Relevant CPU instructions
637
663
  - **ARM:** (software)
638
664
  - **x86:** (software)
639
- - **Counted Python operations:** `math.log(x)` for `CountedFloat`
665
+ - **Counted Python operations:** `math.log(x)` for `CountedFloat`; `math.log(x, base)` for `CountedFloat` decomposes per the constant-detection heuristic (int base 2/10 -> LOG2/LOG10; other int base -> LOG+MUL; float base -> LOG per counted operand + DIV)
640
666
  - **Not counted:** `numpy.log`, log on non-CountedFloat
641
667
 
642
668
  ### FlopType.LOG2 (`log2(x)`)
643
669
  - Relevant CPU instructions
644
670
  - **ARM:** (software)
645
671
  - **x86:** (software)
646
- - **Counted Python operations:** `math.log2(x)` for `CountedFloat`
672
+ - **Counted Python operations:** `math.log2(x)` for `CountedFloat`; `math.log(x, 2)` (int base) for `CountedFloat`
647
673
  - **Not counted:** `numpy.log2`, log2 on non-CountedFloat
648
674
 
649
675
  ### FlopType.LOG10 (`log10(x)`)
650
676
  - Relevant CPU instructions
651
677
  - **ARM:** (software)
652
678
  - **x86:** (software)
653
- - **Counted Python operations:** `math.log10(x)` for `CountedFloat`
679
+ - **Counted Python operations:** `math.log10(x)` for `CountedFloat`; `math.log(x, 10)` (int base) for `CountedFloat`
654
680
  - **Not counted:** `numpy.log10`, log10 on non-CountedFloat
655
681
 
656
682
  ### FlopType.POW (`x^y`)
@@ -1,7 +1,7 @@
1
1
  ![shields.io-python-versions](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)
2
- ![genbadge-test-count](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.3/badge-test-count.svg)
3
- ![genbadge-test-coverage](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.3/badge-coverage.svg)
4
- ![counted_float logo](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.3/splash.webp)
2
+ ![genbadge-test-count](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.5/badge-test-count.svg)
3
+ ![genbadge-test-coverage](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.5/badge-coverage.svg)
4
+ ![counted_float logo](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.5/splash.webp)
5
5
 
6
6
  # counted-float
7
7
 
@@ -69,7 +69,33 @@ s = math.sqrt(cf1) # s = CountedFloat(0.9)
69
69
  is_float = isinstance(s, float) # True
70
70
  ```
71
71
 
72
- ## 2.2. FLOP counting context managers
72
+ ### The counting model: what gets counted and why
73
+
74
+ The counting model is a contract with two sides:
75
+
76
+ - **Your side:** wrap every *runtime input* of the algorithm you want to measure in `CountedFloat`
77
+ at its boundary. Contagion does the rest — everything derived from those inputs stays counted
78
+ automatically.
79
+ - **The library's side:** count every FLOP that a compiled (C/Rust/...) port of your algorithm
80
+ would execute *on data derived from those inputs*.
81
+
82
+ From this contract follows a clean rule for everything else: **constants are free.** Any plain
83
+ float encountered mid-computation is, by the contract, not an input — so it must be a constant of
84
+ the algorithm (a literal, a coefficient, a tolerance), and operations purely among constants are
85
+ work a compiled port would fold at compile time or precompute. This is why e.g. `math.sqrt(3)`
86
+ counts nothing: the port ships `sqrt(3)` as a precomputed constant.
87
+
88
+ The library detects constants through two mechanisms, applying the same rule:
89
+
90
+ - **Unwrapped values** (plain floats): constants by the wrapping contract, as above.
91
+ - **`int` operands**: evidence of a *hardcoded* constant — ints don't fall out of floating-point
92
+ computations, so an int operand almost certainly appears literally in your source. This enables
93
+ counting the strength reductions a compiled port would apply: `x**2` counts MUL (i.e. `x*x`),
94
+ `2**x` counts EXP2, `math.log(x, 10)` counts LOG10 — while `x**2.0`, with a float that *could*
95
+ be a runtime value, conservatively counts a generic POW.
96
+
97
+ The flip side: an unwrapped runtime input is invisible to the counter — that is a wrapping error
98
+ at your algorithm's boundary, not something the library can detect. When in doubt, wrap.
73
99
 
74
100
  Once we use the `CountedFloat` class, we can use the available context managers to count the number of
75
101
  flops performed by `CountedFloat` objects.
@@ -615,21 +641,21 @@ This appendix provides detailed information about how each floating-point operat
615
641
  - Relevant CPU instructions
616
642
  - **ARM:** (software)
617
643
  - **x86:** (software)
618
- - **Counted Python operations:** `math.log(x)` for `CountedFloat`
644
+ - **Counted Python operations:** `math.log(x)` for `CountedFloat`; `math.log(x, base)` for `CountedFloat` decomposes per the constant-detection heuristic (int base 2/10 -> LOG2/LOG10; other int base -> LOG+MUL; float base -> LOG per counted operand + DIV)
619
645
  - **Not counted:** `numpy.log`, log on non-CountedFloat
620
646
 
621
647
  ### FlopType.LOG2 (`log2(x)`)
622
648
  - Relevant CPU instructions
623
649
  - **ARM:** (software)
624
650
  - **x86:** (software)
625
- - **Counted Python operations:** `math.log2(x)` for `CountedFloat`
651
+ - **Counted Python operations:** `math.log2(x)` for `CountedFloat`; `math.log(x, 2)` (int base) for `CountedFloat`
626
652
  - **Not counted:** `numpy.log2`, log2 on non-CountedFloat
627
653
 
628
654
  ### FlopType.LOG10 (`log10(x)`)
629
655
  - Relevant CPU instructions
630
656
  - **ARM:** (software)
631
657
  - **x86:** (software)
632
- - **Counted Python operations:** `math.log10(x)` for `CountedFloat`
658
+ - **Counted Python operations:** `math.log10(x)` for `CountedFloat`; `math.log(x, 10)` (int base) for `CountedFloat`
633
659
  - **Not counted:** `numpy.log10`, log10 on non-CountedFloat
634
660
 
635
661
  ### FlopType.POW (`x^y`)
@@ -182,7 +182,15 @@ class CountedFloat(float):
182
182
  return CountedFloat(super().__rtruediv__(other))
183
183
 
184
184
  def __pow__(self, other) -> CountedFloat:
185
- """x**other"""
185
+ """
186
+ x**other
187
+
188
+ Counting heuristic: an `int` operand is taken as evidence of a hardcoded constant in the
189
+ source (ints don't fall out of floating-point computations), so `x**2` counts as MUL —
190
+ the strength reduction (x*x) a compiled port would apply. A float operand may just as well
191
+ be a runtime variable that happens to hold that value, where a port would compile a
192
+ generic pow, so `x**2.0` counts as POW.
193
+ """
186
194
  if isinstance(other, int) and other == 2:
187
195
  GLOBAL_COUNTER.incr_mul() # x^2 = x*x
188
196
  else:
@@ -192,7 +200,14 @@ class CountedFloat(float):
192
200
  return CountedFloat(super().__pow__(other))
193
201
 
194
202
  def __rpow__(self, other) -> CountedFloat:
195
- """other**x"""
203
+ """
204
+ other**x
205
+
206
+ Same constant-detection heuristic as __pow__, applied to the base: an `int` base 2 or 10
207
+ is taken as a hardcoded constant, counting as EXP2 / EXP10 (the strength reduction a
208
+ compiled port would apply); a float base may be a runtime variable, so it counts as
209
+ generic POW.
210
+ """
196
211
  if isinstance(other, int) and other == 2:
197
212
  GLOBAL_COUNTER.incr_exp2()
198
213
  elif isinstance(other, int) and other == 10:
@@ -214,10 +229,15 @@ original_math_log2 = math.log2
214
229
  original_math_log10 = math.log10
215
230
  original_math_exp = math.exp
216
231
  original_math_exp2 = math.exp2
232
+ original_math_pow = math.pow
217
233
  original_math_sin = math.sin
218
234
  original_math_cos = math.cos
219
235
  original_math_tan = math.tan
220
236
 
237
+ # sentinel for math_log's optional base argument; the stdlib signature is math.log(x[, base]),
238
+ # where omitting base is not the same as passing any real value (and None is rejected)
239
+ _NO_BASE = object()
240
+
221
241
 
222
242
  def math_sqrt(x: float) -> float | CountedFloat:
223
243
  if isinstance(x, CountedFloat):
@@ -235,12 +255,50 @@ def math_cbrt(x: float) -> float | CountedFloat:
235
255
  return original_math_cbrt(x)
236
256
 
237
257
 
238
- def math_log(x: float) -> float | CountedFloat:
239
- if isinstance(x, CountedFloat):
240
- GLOBAL_COUNTER.incr_log()
241
- return CountedFloat(original_math_log(x))
258
+ def math_log(x: float, base=_NO_BASE) -> float | CountedFloat:
259
+ """
260
+ Patched math.log: stdlib contract (optional base), with flop classification for the base
261
+ following the same constant-detection heuristic as CountedFloat.__pow__ / __rpow__
262
+ (int operand = hardcoded constant in the source):
263
+ - base omitted -> LOG
264
+ - base int 2 / 10 -> LOG2 / LOG10 (a compiled port calls log2/log10 directly)
265
+ - base other int -> LOG + MUL (a port computes log(x) * C, with C = 1/log(base) folded
266
+ at compile time)
267
+ - base float -> a port computes log(x)/log(base): LOG per CountedFloat operand + DIV
268
+ As everywhere in the counting model, only operations touching CountedFloat values are counted:
269
+ any runtime input to the counted algorithm should itself be a CountedFloat; whatever remains a
270
+ plain float is by definition not part of the core algorithm and/or precomputable.
271
+ """
272
+ if base is _NO_BASE:
273
+ if isinstance(x, CountedFloat):
274
+ GLOBAL_COUNTER.incr_log()
275
+ return CountedFloat(original_math_log(x))
276
+ else:
277
+ return original_math_log(x)
242
278
  else:
243
- return original_math_log(x)
279
+ # computed first: raises per stdlib contract before anything is counted
280
+ result = original_math_log(x, base)
281
+ if isinstance(base, int) and base == 2:
282
+ if isinstance(x, CountedFloat):
283
+ GLOBAL_COUNTER.incr_log2()
284
+ elif isinstance(base, int) and base == 10:
285
+ if isinstance(x, CountedFloat):
286
+ GLOBAL_COUNTER.incr_log10()
287
+ elif isinstance(base, int):
288
+ if isinstance(x, CountedFloat):
289
+ GLOBAL_COUNTER.incr_log()
290
+ GLOBAL_COUNTER.incr_mul()
291
+ else:
292
+ if isinstance(x, CountedFloat):
293
+ GLOBAL_COUNTER.incr_log()
294
+ if isinstance(base, CountedFloat):
295
+ GLOBAL_COUNTER.incr_log()
296
+ if isinstance(x, CountedFloat) or isinstance(base, CountedFloat):
297
+ GLOBAL_COUNTER.incr_div()
298
+ if isinstance(x, CountedFloat) or isinstance(base, CountedFloat):
299
+ return CountedFloat(result)
300
+ else:
301
+ return result
244
302
 
245
303
 
246
304
  def math_log2(x: float) -> float | CountedFloat:
@@ -276,7 +334,34 @@ def math_exp2(x: float) -> float | CountedFloat:
276
334
 
277
335
 
278
336
  def math_pow(x: float, y: float) -> float | CountedFloat:
279
- return x**y
337
+ """
338
+ Patched math.pow: stdlib contract (always-float result, ValueError on domain errors), with
339
+ flop classification identical to the x**y form — including the constant-detection heuristic
340
+ documented on CountedFloat.__pow__ / __rpow__ (int operand = hardcoded constant).
341
+ """
342
+ if isinstance(x, CountedFloat) or isinstance(y, CountedFloat):
343
+ # computed first: math.pow raises ValueError on domain errors (e.g. negative base with
344
+ # fractional exponent) and then nothing should be counted
345
+ result = original_math_pow(x, y)
346
+ if isinstance(x, CountedFloat):
347
+ if isinstance(y, int) and y == 2:
348
+ GLOBAL_COUNTER.incr_mul() # x^2 = x*x
349
+ else:
350
+ if isinstance(y, int):
351
+ GLOBAL_COUNTER.incr_i2f()
352
+ GLOBAL_COUNTER.incr_pow()
353
+ else:
354
+ if isinstance(x, int) and x == 2:
355
+ GLOBAL_COUNTER.incr_exp2()
356
+ elif isinstance(x, int) and x == 10:
357
+ GLOBAL_COUNTER.incr_exp10()
358
+ else:
359
+ if isinstance(x, int):
360
+ GLOBAL_COUNTER.incr_i2f()
361
+ GLOBAL_COUNTER.incr_pow()
362
+ return CountedFloat(result)
363
+ else:
364
+ return original_math_pow(x, y)
280
365
 
281
366
 
282
367
  def math_sin(x: float) -> float | CountedFloat:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "counted-float"
3
- version = "1.0.3"
3
+ version = "1.0.5"
4
4
  description = "Count floating-point operations in Python code & benchmark relative flop costs."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
File without changes