Skip to content

Add pure FixedPoint implementations and tests for sin(), cos(), and tan() #1773

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

bytegrrrl
Copy link
Contributor

Per #1543.

Notes:
This uses a power series approximation to calculate sine and cosine. Tangent is just calculated using sin() and cos(). The input value is first shifted into the interval (-pi, pi) for sin() and cos()and (-pi/2, pi/2) for tan(). At the asymptotes for tan, an exception will be thrown because the FixedPoint doesn't implement inf (yet). Near the asymptotes absolute precision is a bit poor.

It's possible to use another expansion for tan(), but it's not as nice as those for sin() and cos(). I'd be happy to implement that down the road if that would be preferred. (It will still have problems at the asymptotes though).

Copy link
Member

@heinezen heinezen left a comment

Choose a reason for hiding this comment

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

Very nice, I only have a few requests

Copy link
Member

@heinezen heinezen left a comment

Choose a reason for hiding this comment

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

Very nice changes with the multiplication/division

return this->get_integral_part();
}
else {
return this->get_integral_part() + one();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return this->get_integral_part() + one();
return this->get_integral_part() + FixedPoint::one();

/**
* Converter to retrieve the integral (pre-decimal) part of the number.
*/
constexpr FixedPoint get_integral_part() const {
Copy link
Member

Choose a reason for hiding this comment

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

Can you add tests for this method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep! I'll try to get those added soon!

/**
* Round to the nearest integer.
*/
constexpr FixedPoint round() const {
Copy link
Member

Choose a reason for hiding this comment

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

Can you add tests for this method?

Comment on lines +388 to +390
if (this->get_fractional_part() < same_type_but_unsigned(0.5)) {
return this->get_integral_part();
}
Copy link
Member

Choose a reason for hiding this comment

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

We should probably consider IEE 754 rounding rules here (see Wikipedia for details). The default one seems to be "round to nearest, ties to even". That would mean there needs to be an additional condition for when the fraction is exactly == 0.5. The rules are a bit counter-intuitive because rounding in real-life is done differently, but apparently it checks out when I test this with Python:

  • -1.5 => -2
  • -0.5 => 0
  • 0.5 => 0
  • 1.5 => 2
  • 2.5 => 2

Copy link
Member

Choose a reason for hiding this comment

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

You should also mention the rounding rule in the docstring.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call, I'll take a look soon!

// special case when integeral part of rhs is 0
if (rhs_upper == 0) {
// It's very likely this upper term is zero, but we should consider it regardless.
FP upper_term = FP::from_raw_value((lhs_upper << (2 * F)) / rhs_lower);
Copy link
Member

Choose a reason for hiding this comment

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

This generates compiler warning because you are shifting by 2 * 32 sometimes when lhs_upper is a 64-Bit signed integer which causes undefined behavior. You should consider using safe_shiftleft.

Comment on lines +798 to +800
FP upper_term = FixedPoint<I, F, Inter>::from_raw_value((lhs_upper << F) / rhs_upper);
FP lower_term = FP::from_raw_value(((lhs_lower << F) / rhs_upper) >> F);
FP mixed_term = FixedPoint<I, F, Inter>::from_raw_value((rhs_lower) / rhs_upper);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
FP upper_term = FixedPoint<I, F, Inter>::from_raw_value((lhs_upper << F) / rhs_upper);
FP lower_term = FP::from_raw_value(((lhs_lower << F) / rhs_upper) >> F);
FP mixed_term = FixedPoint<I, F, Inter>::from_raw_value((rhs_lower) / rhs_upper);
FP upper_term = FP::from_raw_value((lhs_upper << F) / rhs_upper);
FP lower_term = FP::from_raw_value(((lhs_lower << F) / rhs_upper) >> F);
FP mixed_term = FP::from_raw_value((rhs_lower) / rhs_upper);

@@ -127,7 +127,8 @@ void fixed_point() {
using T = FixedPoint<uint16_t, 7U, uint64_t>;

auto a = S::from_int(16U);
TESTNOTEQUALS((a*a).to_int(), 256U);
// This test case is now equal with the improved multiplication algorithm
Copy link
Member

Choose a reason for hiding this comment

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

You should probably remove the comment since no one will know what "improved multiplication algorithm" refers to in a few months :D

@@ -139,7 +140,8 @@ void fixed_point() {
using S = FixedPoint<int32_t, 12U>;
auto a = S::from_int(256);
auto b = S::from_int(8);
TESTNOTEQUALS((a/b).to_int(), 32);
// This test case is now equal with the improved division algorithm
Copy link
Member

Choose a reason for hiding this comment

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

same here

using TrigType = FixedPoint<int64_t, 32, int64_t>;

// Testing sin() and cos()
for (int i = -100'000; i <= 100'000; i++) {
Copy link
Member

Choose a reason for hiding this comment

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

the loop runs for a very long time. Can we go with lower precision for speeding these up a bit?

@heinezen heinezen added improvement Enhancement of an existing component lang: c++ Done in C++ code area: util Utilitis and data structures labels May 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: util Utilitis and data structures improvement Enhancement of an existing component lang: c++ Done in C++ code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants