Skip to content

Commit ef75103

Browse files
committed
RFC #17: update to address review comments.
Instead of removing `log2_int` without replacement, add `ceil_log2` and `exact_log2` functions to stand in for its former use-cases.
1 parent f9d61ec commit ef75103

File tree

1 file changed

+66
-19
lines changed

1 file changed

+66
-19
lines changed

text/0000-remove-log2-int.md

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
## Summary
88
[summary]: #summary
99

10-
Remove the `log2_int(n, need_pow2=True)` function.
10+
Replace `log2_int` with two functions: `ceil_log2` and `exact_log2`.
1111

1212
## Motivation
1313
[motivation]: #motivation
@@ -20,31 +20,57 @@ It behaves like so:
2020
* if `n == 0`, it returns `0`;
2121
* if `n != 0`, it returns `(n - 1).bit_length()`.
2222

23-
Its differences with the `math.log2` function are summarized in the following table:
23+
#### Differences with `math.log2`
2424

25-
| `n` | `math.log2(n)` | `log2_int(n)` | `log2_int(n, need_pow2=False)` |
26-
| ----- | ---------------- | ---------------- | ------------------------------ |
27-
| `4` | `2.0` | `2` | `2` |
28-
| `3` | `1.58496250...` | `ValueError` | `2` |
29-
| `0.5` | `-1.0` | `AttributeError` | `AttributeError` |
30-
| `0` | `ValueError` | `0` | `0` |
31-
| `-1` | `ValueError` | `ValueError` | `2` |
25+
In practice, `log2_int` differs from `math.log2` in the following ways:
3226

27+
1. its implementation is restricted to integers only;
28+
2. if `need_pow2` is false, the result is rounded up to the nearest integer;
29+
3. it doesn't raise an exception for `n == 0`;
30+
4. if `need_pow2` is false, it doesn't raise an exception for `n < 0`.
3331

34-
In practice, developers seem to use `log2_int` as a variant of `math.log2` whose result is rounded-up to an integer, in order to know how many bits are needed to represent a number.
32+
#### Observations
3533

36-
However, the fact that it transparently accepts `0` and rejects non-integer values such as `0.5` makes it inadequate for use cases outside bit manipulation.
34+
* *1)* is a desirable property; coercing integers into floating-point numbers is fraught with peril, as the latter have limited precision.
35+
* *2)* has common use-cases in digital design, such as address decoders.
36+
* *3)* and *4)* are misleading at best. Despite being advertised as a logarithm, `log2_int` doesn't exclude 0 or negative integers from its domain.
3737

38-
## Explanation
39-
[explanation]: #explanation
38+
## Guide-level explanation
39+
[guide-level-explanation]: #guide-level-explanation
4040

41-
The `log2_int` function is deprecated and removed in favor of `math.log2`.
41+
Amaranth provides two log2 functions for integer arithmetic:
4242

43-
The following suggestions are made to help migration:
43+
* `ceil_log2(n)`, where `n` is assumed to be any positive integer
44+
* `exact_log2(n)`, where `n` is assumed to be an integer power-of-2
4445

45-
* `int(log2(n))` can be used instead of `log2_int(n)`;
46-
* `ceil(log2(n))` can be used instead of `log2_int(n, need_pow2=False)`;
47-
* if `n == 0` or `n < 0` are valid, the caller must handle them explicitly.
46+
For example:
47+
48+
```python3
49+
ceil_log2(8) # 3
50+
ceil_log2(5) # 3
51+
ceil_log2(4) # 2
52+
53+
exact_log2(8) # 3
54+
exact_log2(5) # raises a ValueError
55+
exact_log2(4) # 2
56+
```
57+
58+
## Reference-level explanation
59+
[reference-level-explanation]: #reference-level-explanation
60+
61+
Use of the `log2_int` function is deprecated.
62+
63+
A `ceil_log2(n)` function is added, that:
64+
65+
* returns the integer log2 of the smallest power-of-2 greater than or equal to `n`;
66+
* raises a `TypeError` if `n` is not an integer;
67+
* raises a `ValueError` if `n` is lesser than or equal to 0.
68+
69+
An `exact_log2(n)` function is added, that:
70+
71+
* returns the integer log2 of `n`;
72+
* raises a `TypeError` if `n` is not an integer;
73+
* raises a `ValueError` if `n` is not a power-of-two.
4874

4975
## Drawbacks
5076
[drawbacks]: #drawbacks
@@ -54,7 +80,22 @@ This is a breaking change.
5480
## Rationale and alternatives
5581
[rationale-and-alternatives]: #rationale-and-alternatives
5682

57-
We could keep `log2_int`. The author believes that the suggested replacements are more effective at communicating intent.
83+
The following alternatives have been considered:
84+
85+
1. Do nothing. Callers of `log2_int` may still need to restrict its domain to positive integers.
86+
2. Restrict `log2_int` to positive integers. Downstream code relying on the previous behavior may silently break.
87+
3. Remove `log2_int`, and use `math.log2` as replacement:
88+
* `log2_int(n)` would be replaced with `math.log2(n)`
89+
* `log2_int(n, need_pow2=False)` would be replaced with `math.ceil(math.log2(n))`
90+
91+
Option *3)* will give incorrect results, as `n` is coerced from `int` to `float`:
92+
93+
```
94+
>>> log2_int((1 << 64) + 1, need_pow2=False)
95+
65
96+
>>> math.ceil(math.log2((1 << 64) + 1))
97+
64
98+
```
5899

59100
## Prior art
60101
[prior-art]: #prior-art
@@ -70,3 +111,9 @@ None.
70111
[future-possibilities]: #future-possibilities
71112

72113
None.
114+
115+
## Acknowledgements
116+
117+
[@mwkmwkmwk] provided valuable feedback while this RFC was being drafted.
118+
119+
[@mwkmwkmwk]: https://github.com/mwkmwkmwk

0 commit comments

Comments
 (0)