Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use constexprs where possible #4

Merged
merged 9 commits into from
Mar 27, 2024
Merged

Use constexprs where possible #4

merged 9 commits into from
Mar 27, 2024

Conversation

tfry-git
Copy link
Collaborator

@tfry-git tfry-git commented Mar 24, 2024

Work in progress.

Notes to self:

  • inverses?
  • shifting negative numbers. In particular, is there any case, where we expect -1 >> 1 == 0?

@tfry-git tfry-git marked this pull request as draft March 24, 2024 14:46
Copy link

Memory usage change @ 732a621

Board flash % RAM for global variables %
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 💚 -24 - 0 -0.04 - 0.0 0 - 0 0.0 - 0.0
arduino:avr:uno 💚 -776 - 0 -2.41 - 0.0 💚 -8 - 0 -0.39 - 0.0
Click for full report table
Board examples/Basics_Serial
flash
% examples/Basics_Serial
RAM for global variables
% examples/Inverses
flash
% examples/Inverses
RAM for global variables
% examples/SmartRanges
flash
% examples/SmartRanges
RAM for global variables
%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 -20 -0.03 0 0.0 -24 -0.04 0 0.0 0 0.0 0 0.0
arduino:avr:uno -776 -2.41 -8 -0.39 -386 -1.2 -4 -0.2 0 0.0 0 0.0
Click for full report CSV
Board,examples/Basics_Serial<br>flash,%,examples/Basics_Serial<br>RAM for global variables,%,examples/Inverses<br>flash,%,examples/Inverses<br>RAM for global variables,%,examples/SmartRanges<br>flash,%,examples/SmartRanges<br>RAM for global variables,%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8,-20,-0.03,0,0.0,-24,-0.04,0,0.0,0,0.0,0,0.0
arduino:avr:uno,-776,-2.41,-8,-0.39,-386,-1.2,-4,-0.2,0,0.0,0,0.0

In C++-11 a constexpr function may not even contain local const's. To avoid turning the implementation into an unreadable mess,
I did not go for a literal "translation". Rather:
- moved (an equivalent of) uFullRange() into UFix as maxRange()
- moved (an equivalent of) neededNIExtra() into UFix as a typedef NIadjusted_t (returning "this type, but with NI adjusted according to RANGE)

The end result actually looks quite a bit simpler to me. I hope that isn't because I'm overlooking something.

NOTE: The return type of worktype::asRaw() is the same integer type as previously defined explicitly as return_type.

NOTE: This is a draft, and I only converted a single operator, for now. Once finished, it should be possible to remove at least needed(S)NIExtra(), but
probably also uFullRange() and sFullRange().
Copy link

Memory usage change @ 172610b

Board flash % RAM for global variables %
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 💚 -24 - 0 -0.04 - 0.0 0 - 0 0.0 - 0.0
arduino:avr:uno 💚 -776 - -154 -2.41 - -0.48 ❔ -8 - +4 -0.39 - +0.2
Click for full report table
Board examples/Basics_Serial
flash
% examples/Basics_Serial
RAM for global variables
% examples/Inverses
flash
% examples/Inverses
RAM for global variables
% examples/SmartRanges
flash
% examples/SmartRanges
RAM for global variables
%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 -20 -0.03 0 0.0 -24 -0.04 0 0.0 0 0.0 0 0.0
arduino:avr:uno -776 -2.41 -8 -0.39 -386 -1.2 -4 -0.2 -154 -0.48 4 0.2
Click for full report CSV
Board,examples/Basics_Serial<br>flash,%,examples/Basics_Serial<br>RAM for global variables,%,examples/Inverses<br>flash,%,examples/Inverses<br>RAM for global variables,%,examples/SmartRanges<br>flash,%,examples/SmartRanges<br>RAM for global variables,%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8,-20,-0.03,0,0.0,-24,-0.04,0,0.0,0,0.0,0,0.0
arduino:avr:uno,-776,-2.41,-8,-0.39,-386,-1.2,-4,-0.2,-154,-0.48,4,0.2

@tfry-git
Copy link
Collaborator Author

@tomcombriat : This is still a work in progress, but I'd like to get a first look from your side on this, before I continue to apply the same pattern for further operators:

  • The first commit (c'tors) is quite straight-forward. I suspect those massive flash-savings are probably just an artifact that is unlikely to turn up in real use (maybe in the Serial.print code).
  • The second commit has taken me many hours, today, and this is where I'd really like you to second-check what I did so far. Note that I only adjusted a single operator+ for now, all others would be adjusted according to the same pattern - if it seems correct. See my commit message on 172610b for some more detail.

Note that once all this is finished, I intend to implement auto-checks using static_asserts(), as outlined, some time ago (while I was still thinking, this was easy to do...).

@tomcombriat
Copy link
Owner

Hei!

That looks quite neat! This code is quite hard to read and I think this suggestion actually makes it more readable (I doubt a bit it will ever become ultra readable anyway).

I made some comments but they do not appear in the thread, in case you did not received a notifications, they are here.

Looking very fast yesterday, I though that you were extensively using auto for the signature of the operators (for instance):

constexpr auto operator+ (const UFix<_NI,_NF,_RANGE>& op) const

instead of

constexpr typename UFix<FixMathPrivate::FM_max(NI,_NI), FixMathPrivate::FM_max(NI,_NI), FixMathPrivate::rangeAdd(NF,_NF,RANGE,_RANGE)>::NIadjusted_t operator+ (const UFix<_NI,_NF,_RANGE>& op) const

which I remember to have found very smart as the return type can be inferred by what is returned, but I see now that you removed that. Out of curiosity, why does not this work?

Repository owner deleted a comment from github-actions bot Mar 25, 2024
Copy link

Memory usage change @ 485e9ff

Board flash % RAM for global variables %
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 💚 -24 - 0 -0.04 - 0.0 0 - 0 0.0 - 0.0
arduino:avr:uno 💚 -776 - 0 -2.41 - 0.0 💚 -8 - 0 -0.39 - 0.0
Click for full report table
Board examples/Basics_Serial
flash
% examples/Basics_Serial
RAM for global variables
% examples/Inverses
flash
% examples/Inverses
RAM for global variables
% examples/SmartRanges
flash
% examples/SmartRanges
RAM for global variables
%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 -20 -0.03 0 0.0 -24 -0.04 0 0.0 0 0.0 0 0.0
arduino:avr:uno -776 -2.41 -8 -0.39 -386 -1.2 -4 -0.2 0 0.0 0 0.0
Click for full report CSV
Board,examples/Basics_Serial<br>flash,%,examples/Basics_Serial<br>RAM for global variables,%,examples/Inverses<br>flash,%,examples/Inverses<br>RAM for global variables,%,examples/SmartRanges<br>flash,%,examples/SmartRanges<br>RAM for global variables,%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8,-20,-0.03,0,0.0,-24,-0.04,0,0.0,0,0.0,0,0.0
arduino:avr:uno,-776,-2.41,-8,-0.39,-386,-1.2,-4,-0.2,0,0.0,0,0.0

@tfry-git
Copy link
Collaborator Author

I made some comments but they do not appear in the thread, in case you did not received a notifications, they are here.

Replies a the same place. While addressing your points, I also went ahead and added some first static asserts, which would have immediately caught my mixup of NI and NF.

Looking very fast yesterday, I though that you were extensively using auto for the signature of the operators (for instance):

[...]

which I remember to have found very smart as the return type can be inferred by what is returned, but I see now that you removed that. Out of curiosity, why does not this work?

Apparently, this is not available before C++14. Compilation failed on the Uno for that reason.

Copy link

Memory usage change @ 8b92b62

Board flash % RAM for global variables %
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 💚 -44 - -20 -0.07 - -0.03 💚 -4 - 0 -0.02 - 0.0
arduino:avr:uno 💚 -776 - 0 -2.41 - 0.0 💚 -8 - 0 -0.39 - 0.0
Click for full report table
Board examples/Basics_Serial
flash
% examples/Basics_Serial
RAM for global variables
% examples/Inverses
flash
% examples/Inverses
RAM for global variables
% examples/SmartRanges
flash
% examples/SmartRanges
RAM for global variables
%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 -20 -0.03 0 0.0 -24 -0.04 0 0.0 -44 -0.07 -4 -0.02
arduino:avr:uno -776 -2.41 -8 -0.39 -386 -1.2 -4 -0.2 0 0.0 0 0.0
Click for full report CSV
Board,examples/Basics_Serial<br>flash,%,examples/Basics_Serial<br>RAM for global variables,%,examples/Inverses<br>flash,%,examples/Inverses<br>RAM for global variables,%,examples/SmartRanges<br>flash,%,examples/SmartRanges<br>RAM for global variables,%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8,-20,-0.03,0,0.0,-24,-0.04,0,0.0,-44,-0.07,-4,-0.02
arduino:avr:uno,-776,-2.41,-8,-0.39,-386,-1.2,-4,-0.2,0,0.0,0,0.0

Repository owner deleted a comment from github-actions bot Mar 27, 2024
@tfry-git tfry-git marked this pull request as ready for review March 27, 2024 13:40
Copy link

Memory usage change @ a780467

Board flash % RAM for global variables %
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 💚 -44 - -20 -0.07 - -0.03 💚 -4 - 0 -0.02 - 0.0
arduino:avr:uno 💚 -776 - 0 -2.41 - 0.0 💚 -8 - 0 -0.39 - 0.0
Click for full report table
Board examples/Basics_Serial
flash
% examples/Basics_Serial
RAM for global variables
% examples/Inverses
flash
% examples/Inverses
RAM for global variables
% examples/SmartRanges
flash
% examples/SmartRanges
RAM for global variables
%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 -20 -0.03 0 0.0 -24 -0.04 0 0.0 -44 -0.07 -4 -0.02
arduino:avr:uno -776 -2.41 -8 -0.39 -386 -1.2 -4 -0.2 0 0.0 0 0.0
Click for full report CSV
Board,examples/Basics_Serial<br>flash,%,examples/Basics_Serial<br>RAM for global variables,%,examples/Inverses<br>flash,%,examples/Inverses<br>RAM for global variables,%,examples/SmartRanges<br>flash,%,examples/SmartRanges<br>RAM for global variables,%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8,-20,-0.03,0,0.0,-24,-0.04,0,0.0,-44,-0.07,-4,-0.02
arduino:avr:uno,-776,-2.41,-8,-0.39,-386,-1.2,-4,-0.2,0,0.0,0,0.0

@tfry-git
Copy link
Collaborator Author

tfry-git commented Mar 27, 2024

Ok, so here's the complete PR. We could certainly add a lot more static checks, but I think it's a start.

While doing this, I ran into two issues:

  • My test for invAccurate() doesn't work. Am I doing it wrong?
  • Turns out, bit-shifts of negative numbers are not formally defined behavior (before C++-20). According to my reading that won't cause a real-world problem, as it has been the de-facto standard on virtually all platforms, but prevents use of anything involving such shifts inside static_assert(). One small surprise, I came to learn is that -2 >> 5 == -1. I don't think we strongly rely on it being 0 anywhere, though.

Repository owner deleted a comment from github-actions bot Mar 27, 2024
@tomcombriat
Copy link
Owner

Wow, nice! I'll try to have a closer look at it very soon!

Regarding the issues:

My test for invAccurate() doesn't work. Am I doing it wrong?

No, this is because of the way the inverse is computed. Let's take a UFix<4,0> a = 2;. It's accurate inverse will be a UFix<0,4> and is computed by dividing the maximum number that can be hold in the resulting type by the raw value of a. Hence, here, it will effectively be computed as (15/4) / 16 (the last division is done by the shifting) and not (16/4)/16 to avoid touching one more bit. It follows that .invAccurate() (and all the other inversions) are never exact, even when the resulting number could be represented in the destination type. .invAccurate() only guarantees that, in most case, two successive values will not have the same inverse. One way to boost the precision (but never reaching the exact number) is to use .inv<N>() which will effectively use more bits for computation. I hope this is more or less clear ;).

Turns out, bit-shifts of negative numbers are not formally defined behavior (before C++-20).
Yes, but as you said, it is a de facto standard that should be present on all compilers this code may concern. Note that, following you comment on the surprise, this is also connected to the +1 in typename IntegerType<FixMathPrivate::sBitsToBytes(NI+1)>::signed_type asInt() const and is caused by two's complement representation if I got everything correct. Basically, the sign bit cannot change with a shift, and this representation does not provide a negative 0, hence shifting a negative number saturates at -1 as you observed. I do not think we should be too concern about this but it is good to keep in mind!

Copy link
Owner

@tomcombriat tomcombriat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! No testing done but I am working these days on a code that uses FixMath extensively so a big mistake won't stay unnoticed for very long.

I added a few comments here and there, mostly to make sure I understand, so feel free to ignore them.

Once merged, I suggest waiting a couple of weeks to let an obvious bug show its face and then make a new release with that!

I agree that the code looks quite neater! Thanks!

src/FixMath.h Show resolved Hide resolved
src/FixMath.h Outdated Show resolved Hide resolved
src/FixMath.h Show resolved Hide resolved
// multiplication
static_assert(c * UFix<36, 5>(3ll << 31) == UFix<58,0>(33ll*(3ll << 31))); // NOTE: The exact values are aribrary, but we want something that would overflow the initial type range
static_assert(a * UFix<0, 2>(3, true) == UFix<17, 8>(24)); // 32 * .75 == 24
// static_assert(a * UFix<5, 0>(4).invAccurate() == UFix<17, 8>(8)); // 32 * (1/4) == 8 // TODO: FAIL. Isn't this supposed to work?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment in the thread. This is a limitation because of the way it is computed. Note that we know exactly how far it can be (hence do a static_assert with a range of the output instead of a fixed value).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see that now. That cues two further questions (neither of which I'd intend to address within this PR):

  • Maybe add a convenience function for ranged comparisons?
  • I see what you mean, now, but invAccurate() sounded like - it would be accurate... I guess that's mostly a naming thing. If you don't deem it too late to change that, I'd suggest renaming as perhaps invFull()? And maybe a new function that works closer to what I expected would have a value, too (for small numbers, the difference can become quite noticeable).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't deem it too late to change that, I'd suggest renaming as perhaps invFull()?

Sounds like a good idea! I will try to change that (and add a real accurate) soon. Note that in all cases, if the value cannot be represented in the destination type well, you are done for… If find a bit fun that division are the only operations which lose precision in fixed point math!
I think now is the time for these kind of things, I think I used one or two invAccurate in Mozzi (where speed is usually more important than precision) that can easily be changed into invFull

src/FixMath_Autotests.h Show resolved Hide resolved
src/FixMath_Autotests.h Show resolved Hide resolved
Compilers _might_ turn this into smarter code that e.g. will not check all bytes for equality, if a first byte mismatches.
Copy link

Memory usage change @ 281fde9

Board flash % RAM for global variables %
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 💚 -44 - -20 -0.07 - -0.03 💚 -4 - 0 -0.02 - 0.0
arduino:avr:uno 💚 -776 - 0 -2.41 - 0.0 💚 -8 - 0 -0.39 - 0.0
Click for full report table
Board examples/Basics_Serial
flash
% examples/Basics_Serial
RAM for global variables
% examples/Inverses
flash
% examples/Inverses
RAM for global variables
% examples/SmartRanges
flash
% examples/SmartRanges
RAM for global variables
%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 -20 -0.03 0 0.0 -24 -0.04 0 0.0 -44 -0.07 -4 -0.02
arduino:avr:uno -776 -2.41 -8 -0.39 -386 -1.2 -4 -0.2 0 0.0 0 0.0
Click for full report CSV
Board,examples/Basics_Serial<br>flash,%,examples/Basics_Serial<br>RAM for global variables,%,examples/Inverses<br>flash,%,examples/Inverses<br>RAM for global variables,%,examples/SmartRanges<br>flash,%,examples/SmartRanges<br>RAM for global variables,%
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8,-20,-0.03,0,0.0,-24,-0.04,0,0.0,-44,-0.07,-4,-0.02
arduino:avr:uno,-776,-2.41,-8,-0.39,-386,-1.2,-4,-0.2,0,0.0,0,0.0

@tfry-git tfry-git merged commit f36fc97 into main Mar 27, 2024
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants