Skip to content

Commit 42ddd61

Browse files
committed
Optimized Sin()
1 parent 677f7f0 commit 42ddd61

File tree

3 files changed

+42
-18
lines changed

3 files changed

+42
-18
lines changed

Fix64.cs

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,10 @@ public static Fix64 Ceiling(Fix64 value) {
9999
public static Fix64 Round(Fix64 value) {
100100
var decimalPart = value.m_rawValue & 0x00000000FFFFFFFF;
101101
var integralPart = Floor(value);
102-
if (decimalPart < 0x0000000080000000) {
102+
if (decimalPart < 0x80000000) {
103103
return integralPart;
104104
}
105-
if (decimalPart > 0x0000000080000000) {
105+
if (decimalPart > 0x80000000) {
106106
return integralPart + One;
107107
}
108108
// if number is halfway between two values, round to the nearest even number
@@ -426,35 +426,59 @@ public static Fix64 Sqrt(Fix64 x) {
426426
return new Fix64((long)result);
427427
}
428428

429-
429+
/// <summary>
430+
/// Returns the Sine of x.
431+
/// This function is accurate to around 3 * Fix64.Precision for small enough values of x.
432+
/// It may lose accuracy as the value of x grows.
433+
/// Performance: about 25% slower than Math.Sin() in x64, and 200% slower in x86.
434+
/// </summary>
430435
public static Fix64 Sin(Fix64 x) {
431-
432436
var xl = x.m_rawValue;
433-
// FIXME 30-50% of the execution time is these modulos...
434-
// We could eliminate 2 of them, as well as the flipVertical/horizontal logic,
435-
// by making the table go from 0 to 2*pi instead of pi/2;
436-
// that would make it 4 times larger though.
437-
var clamped2Pi = xl % PI_TIMES_2 + (xl < 0 ? PI_TIMES_2 : 0);
437+
438+
// Clamp value to 0 - 2*PI using modulo; this is very slow but there's no better way AFAIK
439+
var clamped2Pi = xl % PI_TIMES_2;
440+
if (xl < 0) {
441+
clamped2Pi += PI_TIMES_2;
442+
}
443+
444+
// The LUT contains values for 0 - PiOver2; every other value must be obtained by
445+
// vertical or horizontal mirroring
438446
var flipVertical = clamped2Pi >= PI;
439-
var flipHorizontal = clamped2Pi % PI >= PI_OVER_2;
440-
var clamped = new Fix64(clamped2Pi % PI_OVER_2);
447+
// obtain (angle % PI) from (angle % 2PI) - much faster than doing another modulo
448+
var clampedPi = clamped2Pi;
449+
while (clampedPi >= PI) {
450+
clampedPi -= PI;
451+
}
452+
var flipHorizontal = clampedPi >= PI_OVER_2;
453+
// obtain (angle % PI_OVER_2) from (angle % PI) - much faster than doing another modulo
454+
var clampedPiOver2 = clampedPi;
455+
if (clampedPiOver2 >= PI_OVER_2) {
456+
clampedPiOver2 -= PI_OVER_2;
457+
}
458+
var clamped = new Fix64(clampedPiOver2);
441459

460+
// Find the two closest values in the LUT and perform linear interpolation
461+
// This is unfortunately very expensive on x86; a "FastSin" could skip most of this
462+
// at the expense of accuracy
442463
var rawIndex = FastMul(clamped, SinInterval);
443464
var roundedIndex = Round(rawIndex);
444465
var indexError = FastSub(rawIndex, roundedIndex);
445466

446467
var nearestValue = new Fix64(SinLut[flipHorizontal ?
447468
SinLut.Length - 1 - (int)roundedIndex :
448469
(int)roundedIndex]);
449-
var nextNearestValue = new Fix64(SinLut[flipHorizontal ?
470+
var secondNearestValue = new Fix64(SinLut[flipHorizontal ?
450471
SinLut.Length - 1 - (int)roundedIndex - Sign(indexError) :
451472
(int)roundedIndex + Sign(indexError)]);
452-
var delta = FastMul(indexError, FastAbs(FastSub(nearestValue, nextNearestValue)));
453-
delta = flipHorizontal ? -delta : delta;
454-
var interpolatedValue = FastAdd(nearestValue, delta);
455473

474+
var delta = FastMul(indexError, FastAbs(FastSub(nearestValue, secondNearestValue))).m_rawValue;
475+
var interpolatedValue = nearestValue.m_rawValue + (flipHorizontal ? -delta : delta);
456476
var finalValue = flipVertical ? -interpolatedValue : interpolatedValue;
457-
return finalValue;
477+
return new Fix64(finalValue);
478+
//var nearestValue = SinLut[flipHorizontal ?
479+
// SinLut.Length - 1 - (int)rawIndex :
480+
// (int)rawIndex];
481+
//return new Fix64(flipVertical ? -nearestValue : nearestValue);
458482
}
459483

460484

Fix64Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ public void Sin() {
352352
Assert.True(Fix64.Sin(-Fix64.PiTimes2) == Fix64.Zero);
353353

354354

355-
for (double angle = -2 * Math.PI; angle <= 2 * Math.PI; angle += 0.00001) {
355+
for (double angle = -2 * Math.PI; angle <= 2 * Math.PI; angle += 0.0001) {
356356
var f = (Fix64)angle;
357357
var actualF = Fix64.Sin(f);
358358
var expected = (decimal)Math.Sin(angle);

FixedMath.NET.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<DefineConstants>TRACE</DefineConstants>
3030
<ErrorReport>prompt</ErrorReport>
3131
<WarningLevel>4</WarningLevel>
32-
<PlatformTarget>x64</PlatformTarget>
32+
<PlatformTarget>x86</PlatformTarget>
3333
</PropertyGroup>
3434
<ItemGroup>
3535
<Reference Include="nunit.framework">

0 commit comments

Comments
 (0)