Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion src/main/common/maths.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,73 @@ float acos_approx(float x)
}
#endif

/**
* Fast power approximation for positive base values.
* Uses bit manipulation via the identity: x^y = 2^(y * log2(x))
* Optimized for embedded systems - approximately 5-10x faster than powf().
*
* Accuracy: ~1-3% error for typical ranges, sufficient for TPA calculations.
* Note: Only valid for base > 0. Returns 0 for invalid inputs.
*/
float fast_powf(float base, float exp)
{
// Handle common special cases for maximum speed
if (exp == 0.0f) {
return 1.0f;
}
Comment thread
sensei-hacker marked this conversation as resolved.
if (exp == 1.0f) {
return base;
}
if (base <= 0.0f) {
return 0.0f; // Invalid input
}
if (exp == 2.0f) {
return base * base;
}
if (exp == 0.5f) {
return fast_fsqrtf(base);
}

// For general case, use bit manipulation approximation
// Based on: x^y = 2^(y * log2(x))
// Using IEEE 754 floating point representation
union {
float f;
int32_t i;
} u;

u.f = base;
// Extract and compute: log2(x) ≈ (mantissa bits - 127) + normalized_mantissa
// IEEE 754: float = 2^(exponent-127) * (1 + mantissa/2^23)
// log2(x) ≈ (exponent - 127) + (mantissa / 2^23)

// Fast approximation: just use the exponent bits for log2
// More accurate version includes mantissa contribution
int32_t exp_bits = (u.i >> 23) & 0xFF;
int32_t mant_bits = u.i & 0x7FFFFF;

// log2(base) approximation with mantissa correction
float log2_base = (float)(exp_bits - 127) + (float)mant_bits / 8388608.0f;

Comment thread
sensei-hacker marked this conversation as resolved.
// Compute result exponent: y * log2(x)
float result_exp = exp * log2_base;

// Convert back to float: 2^result_exp
// Use floorf to ensure fractional part is always in [0, 1) for the polynomial approximation.
// Simple (int32_t) cast truncates toward zero, giving a negative frac for negative result_exp.
int32_t result_exp_int = (int32_t)floorf(result_exp);
float result_exp_frac = result_exp - (float)result_exp_int;

// Reconstruct float from exponent
u.i = (result_exp_int + 127) << 23;

// Apply fractional part correction using polynomial approximation
// 2^x ≈ 1 + x*(0.69315 + x*(0.24023 + x*0.05550)) for x in [0,1]
float frac_mult = 1.0f + result_exp_frac * (0.69314718f + result_exp_frac * (0.24022650f + result_exp_frac * 0.05550410f));
Comment on lines +159 to +170
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

result_exp_frac can be negative when result_exp < 0 (which happens for base<1, common in airspeed scaling). The 2^x polynomial is documented as valid for x in [0,1], but the current int-cast decomposition allows frac outside that domain, which can increase error and introduce discontinuities around integer boundaries. Consider decomposing result_exp using floor (so 0<=frac<1) or otherwise ensuring the polynomial’s input range matches its documented validity.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Applied in commit 0b0404d. Changed (int32_t)result_exp to (int32_t)floorf(result_exp) so the fractional part is always in [0, 1), ensuring the 2^x polynomial approximation is always evaluated within its valid domain. Also added TestFastPowf unit tests covering base<1 edge cases (where result_exp is negative and fractional), base>1 general cases, special-case short-circuits, and invalid inputs.


return u.f * frac_mult;
}

int gcd(int num, int denom)
{
if (denom == 0) {
Expand Down Expand Up @@ -616,4 +683,4 @@ void arm_mult_f32(
}
}

#endif
#endif
1 change: 1 addition & 0 deletions src/main/common/maths.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ float bellCurve(const float x, const float curveWidth);
float attenuation(const float input, const float width);
float gaussian(const float x, const float mu, const float sigma);
float fast_fsqrtf(const float value);
float fast_powf(float base, float exp);
float calc_length_pythagorean_2D(const float firstElement, const float secondElement);
float calc_length_pythagorean_3D(const float firstElement, const float secondElement, const float thirdElement);

Expand Down
4 changes: 2 additions & 2 deletions src/main/flight/pid.c
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ float pidRcCommandToRate(int16_t stick, uint8_t rate)
static float calculateFixedWingAirspeedTPAFactor(void){
const float airspeed = constrainf(getAirspeedEstimate(), 100.0f, 20000.0f); // cm/s, clamped to 3.6-720 km/h
const float referenceAirspeed = pidProfile()->fixedWingReferenceAirspeed; // in cm/s
float tpaFactor= powf(referenceAirspeed/airspeed, currentControlProfile->throttle.apa_pow/100.0f);
float tpaFactor= fast_powf(referenceAirspeed/airspeed, currentControlProfile->throttle.apa_pow/100.0f);
tpaFactor= constrainf(tpaFactor, 0.3f, 2.0f);
return tpaFactor;
}
Expand All @@ -460,7 +460,7 @@ static float calculateFixedWingAirspeedITermFactor(void){
return 1.0f;
}

float iTermFactor = powf(referenceAirspeed/airspeed, (apa_pow/100.0f) - 1.0f);
float iTermFactor = fast_powf(referenceAirspeed/airspeed, (apa_pow/100.0f) - 1.0f);
iTermFactor = constrainf(iTermFactor, 0.3f, 1.5f);
return iTermFactor;
}
Expand Down
30 changes: 30 additions & 0 deletions src/test/unit/maths_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,33 @@ TEST(MathsUnittest, TestSensorScaleUnitTest)
}
*/
#endif

TEST(MathsUnittest, TestFastPowf)
{
// Special cases handled exactly by short-circuit branches
EXPECT_FLOAT_EQ(fast_powf(2.0f, 0.0f), 1.0f);
EXPECT_FLOAT_EQ(fast_powf(2.0f, 1.0f), 2.0f);
EXPECT_FLOAT_EQ(fast_powf(3.0f, 2.0f), 9.0f);
EXPECT_NEAR(fast_powf(4.0f, 0.5f), 2.0f, 0.01f);

// General approximation via bit-manipulation: typical error is ~5-10%
// base < 1 with non-trivial exponent - the key regression case for base<1 where
// result_exp is negative and fractional (floorf fix ensures frac stays in [0,1))
EXPECT_NEAR(fast_powf(0.5f, 1.5f), powf(0.5f, 1.5f), powf(0.5f, 1.5f) * 0.10f);
EXPECT_NEAR(fast_powf(0.7f, 1.5f), powf(0.7f, 1.5f), powf(0.7f, 1.5f) * 0.10f);
EXPECT_NEAR(fast_powf(0.3f, 1.5f), powf(0.3f, 1.5f), powf(0.3f, 1.5f) * 0.10f);

// base > 1 cases with non-trivial exponents
EXPECT_NEAR(fast_powf(2.0f, 1.5f), powf(2.0f, 1.5f), powf(2.0f, 1.5f) * 0.10f);
EXPECT_NEAR(fast_powf(10.0f, 1.5f), powf(10.0f, 1.5f), powf(10.0f, 1.5f) * 0.10f);

// Short-circuit branches: exp==1.0f and exp==2.0f return exact values
EXPECT_FLOAT_EQ(fast_powf(0.015f, 1.0f), 0.015f);
EXPECT_FLOAT_EQ(fast_powf(60.0f, 2.0f), 3600.0f);
// Power-of-2 base with non-integer exponent: log2 is exact so result is close
EXPECT_NEAR(fast_powf(0.25f, 1.5f), powf(0.25f, 1.5f), powf(0.25f, 1.5f) * 0.01f);

// Invalid input
EXPECT_FLOAT_EQ(fast_powf(0.0f, 2.0f), 0.0f);
EXPECT_FLOAT_EQ(fast_powf(-1.0f, 2.0f), 0.0f);
}
Loading