Skip to content

Commit e91c744

Browse files
committed
Updated to address feedback.
1 parent f706cff commit e91c744

File tree

1 file changed

+56
-26
lines changed

1 file changed

+56
-26
lines changed

text/0041-fixed-point.md

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,42 +25,56 @@ TODO
2525
## Reference-level explanation
2626
[reference-level-explanation]: #reference-level-explanation
2727

28-
This RFC proposes a library addition `amaranth.lib.fixedpoint` with the following contents:
28+
This RFC proposes a library addition `amaranth.lib.fixed` with the following contents:
2929

30-
`FixedPoint` is a `ShapeCastable` subclass.
30+
`fixed.Shape` is a `ShapeCastable` subclass.
3131
The following operations are defined on it:
3232

33-
- `FixedPoint(f_width, /, *, signed)`: Create a `FixedPoint` with `f_width` fractional bits.
34-
- `FixedPoint(i_width, f_width, /, *, signed)`: Create a `FixedPoint` with `i_width` integer bits and `f_width` fractional bits.
35-
- `FixedPoint.cast(shape)`: Cast `shape` to a `FixedPoint` instance.
33+
- `fixed.Shape(f_width, /, *, signed)`: Create a `fixed.Shape` with zero integer bits and `f_width` fractional bits.
34+
- `fixed.Shape(i_width, f_width, /, *, signed)`: Create a `fixed.Shape` with `i_width` integer bits and `f_width` fractional bits.
35+
- The sign bit is not included in `i_width` or `f_width`, so a `fixed.Shape(7, 8, signed=True)` will be 16 bits wide.
36+
- `fixed.Shape.cast(shape, f_width=0)`: Cast `shape` to a `fixed.Shape` instance.
3637
- `.i_width`, `.f_width`, `.signed`: Width and signedness properties.
37-
- `.const(value)`: Create a fixed point constant from an `int` or `float`, rounded to the closest representable value.
38+
- `.const(value)`: Create a `fixed.Const` from `value`.
3839
- `.as_shape()`: Return the underlying `Shape`.
39-
- `.__call__(target)`: Create a `FixedPointValue` over `target`.
40+
- `.__call__(target)`: Create a `fixed.Value` over `target`.
4041

41-
`Q(*args)` is an alias for `FixedPoint(*args, signed=True)`.
42+
`SQ(*args)` is an alias for `fixed.Shape(*args, signed=True)`.
4243

43-
`UQ(*args)` is an alias for `FixedPoint(*args, signed=False)`.
44+
`UQ(*args)` is an alias for `fixed.Shape(*args, signed=False)`.
4445

45-
`FixedPointValue` is a `ValueCastable` subclass.
46+
`fixed.Value` is a `ValueCastable` subclass.
4647
The following operations are defined on it:
4748

48-
- `FixedPointValue(shape, target)`: Create a `FixedPointValue` with `shape` over `target`.
49-
- `FixedPointValue.cast(value)`: Cast `value` to a `FixedPointValue`.
49+
- `fixed.Value(shape, target)`: Create a `fixed.Value` with `shape` over `target`.
50+
- `fixed.Value.cast(value, f_width=0)`: Cast `value` to a `fixed.Value`.
5051
- `.i_width`, `.f_width`, `.signed`: Width and signedness properties.
51-
- `.shape()`: Return the `FixedPoint` this was created from.
52+
- `.shape()`: Return the `fixed.Shape` this was created from.
5253
- `.as_value()`: Return the underlying value.
5354
- `.eq(value)`: Assign `value`.
54-
- If `value` is a `FixedPointValue`, the precision will be extended or rounded as required.
55-
- If `value` is an `int` or `float`, the value will be rounded to the closest representable value.
5655
- If `value` is a `Value`, it'll be assigned directly to the underlying `Value`.
57-
- `.round(f_width=0)`: Return a new `FixedPointValue` with precision changed to `f_width`, rounding as required.
56+
- If `value` is an `int` or `float`, it'll be cast to a `fixed.Const` first.
57+
- If `value` is a `fixed.Value`, the precision will be extended or rounded as required.
58+
- `.round(f_width=0)`: Return a new `fixed.Value` with precision changed to `f_width`, rounding as required.
5859
- `.__add__(other)`, `.__radd__(other)`, `.__sub__(other)`, `.__rsub__(other)`, `.__mul__(other)`, `.__rmul__(other)`: Binary arithmetic operations.
59-
- If `other` is a `Value` or an `int`, it'll be cast to a `FixedPointValue` first.
60+
- If `other` is a `Value`, it'll be cast to a `fixed.Value` first.
61+
- If `other` is an `int`, it'll be cast to a `fixed.Const` first.
6062
- If `other` is a `float`: TBD
61-
- The result will be a new `FixedPointValue` with enough precision to hold any resulting value without rounding or overflowing.
63+
- The result will be a new `fixed.Value` with enough precision to hold any resulting value without rounding or overflowing.
64+
- `.__lshift__(other)`, `.__rshift__(other)`: Bit shift operations.
6265
- `.__neg__()`, `.__pos__()`, `.__abs__()`: Unary arithmetic operations.
6366

67+
`fixed.Const` is a `fixed.Value` subclass.
68+
The following additional operations are defined on it:
69+
70+
- `fixed.Const(value, shape=None)`: Create a `fixed.Const` from `value`. `shape` must be a `fixed.Shape` if specified.
71+
- If `value` is an `int` and `shape` is not specified, the smallest shape that will fit `value` will be selected.
72+
- If `value` is a `float` and `shape` is not specified, the smallest shape that gives a perfect representation will be selected.
73+
If `shape` is specified, `value` will be rounded to the closest representable value first.
74+
- `.as_integer_ratio()`: Return the value represented as an integer ratio `tuple`.
75+
- `.as_float()`: Return the value represented as a `float`.
76+
- Operators are extended to return a `fixed.Const` if all operands are constant.
77+
6478
## Drawbacks
6579
[drawbacks]: #drawbacks
6680

@@ -69,18 +83,23 @@ TBD
6983
## Rationale and alternatives
7084
[rationale-and-alternatives]: #rationale-and-alternatives
7185

72-
- `FixedPointValue.eq()` could cast a `Value` to a `FixedPointValue` first, and thereby shift the value to the integer part instead of assigning directly to the underlying value.
73-
However, `Value.eq()` would always grab the underlying value of a `FixedPointValue`, and for consistency both `.eq()` operations should behave in the same manner.
86+
- `fixed.Value.eq()` could cast a `Value` to a `fixed.Value` first, and thereby shift the value to the integer part instead of assigning directly to the underlying value.
87+
However, `Value.eq()` would always grab the underlying value of a `fixed.Value`, and for consistency both `.eq()` operations should behave in the same manner.
7488
- If we wanted to go the other way, this RFC could be deferred until another RFC allowing `ValueCastable` to override reflected `.eq()` have been merged.
7589
However, having to explicitly do `value.eq(fp_value.round())` when rounding is desired is arguably preferable to having `value.eq(fp_value)` do an implicit rounding.
7690

77-
- Unlike `.eq()`, it makes sense for arithmetic operations to cast a `Value` to `FixedPointValue`.
91+
- Unlike `.eq()`, it makes sense for arithmetic operations to cast a `Value` to `fixed.Value`.
7892
Multiplying an integer with a fixedpoint constant and rounding the result back to an integer is a reasonable and likely common thing to want to do.
7993

94+
- There's two slightly different [Q notation](https://en.wikipedia.org/wiki/Q_(number_format)) definitions, namely whether the bit counts includes the sign bit or not.
95+
Not having the sign bit included seems more common, and has the advantage that a number has the same fractional precision whether `i_width` is 0 or not.
96+
97+
- While Q notation names the signed type `Q`, it's more consistent for Amaranth to use `SQ` since other Amaranth types defaults to unsigned.
98+
8099
## Prior art
81100
[prior-art]: #prior-art
82101

83-
[Q notation](https://en.wikipedia.org/wiki/Q_(number_format)) is a common and convenient way to specify floating point types.
102+
[Q notation](https://en.wikipedia.org/wiki/Q_(number_format)) is a common and convenient way to specify fixed point types.
84103

85104
## Unresolved questions
86105
[unresolved-questions]: #unresolved-questions
@@ -91,12 +110,23 @@ TBD
91110
- We could use the same width for `other` as for `self`, adjusted to the appropriate exponent for the value.
92111
- We could outright reject it, requiring the user to explicitly specify precision like e.g. `value * Q(15).const(1 / 3)`.
93112

94-
- There's two slightly different [Q notation](https://en.wikipedia.org/wiki/Q_(number_format)) definitions, namely whether the bit counts includes the sign bit or not.
95-
`UQ(15)` and `UQ(7, 8)` would be 15 bits in either convention, but `Q(15)` and `Q(7, 8)` would be 15 or 16 bits depending on the convention. Which do we pick?
96-
97113
- Are there any other operations that would be good to have?
98114

99-
- Bikeshed all the names.
115+
- Are there any operations that would be good to *not* have?
116+
- This API (`fixed.Shape.cast()`) seems confusing and difficult to use. Should we expose it at all? (@whitequark)
117+
118+
- `Decimal` and/or `Fraction` support?
119+
- This could make sense to have, but both can represent values that's not representable as binary fixed point.
120+
On the other hand, a Python `float` can perfectly represent any fixed point value up to a total width of 53 bits and any `float` value is perfectly representable as fixed point.
121+
122+
- Name all the things.
123+
- Library name:
124+
- Bikeshed: `lib.fixed`, `lib.fixnum`. (@whitequark)
125+
- Type names:
126+
- `fixed.Shape` and `fixed.Value` are one option, though I can see why others may object to it. (@whitequark)
127+
- I feel like the `i_width` and `f_width` names are difficult enough to read that it's of more importance than bikeshedding to come up with something more readable. (@whitequark)
128+
- `.int_bits`, `.frac_bits`?
129+
- cursed option: `int, frac = x.width`?
100130

101131
## Future possibilities
102132
[future-possibilities]: #future-possibilities

0 commit comments

Comments
 (0)