Skip to content

Commit 6dff437

Browse files
Bartlasik
authored andcommitted
Add Pow and Ln functions
1 parent bac5181 commit 6dff437

File tree

4 files changed

+268
-14
lines changed

4 files changed

+268
-14
lines changed

LICENSE.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,16 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
1919

2020
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2121

22-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
24+
This project uses code from the log2fix library, which is under the following license:
25+
26+
The MIT License (MIT)
27+
28+
Copyright (c) 2015 Dan Moulding
29+
30+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
31+
32+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
33+
34+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

src/Fix64.cs

Lines changed: 152 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
using System.IO;
33
using System.Runtime.CompilerServices;
44

5-
namespace FixMath.NET
6-
{
5+
namespace FixMath.NET {
76

87
/// <summary>
98
/// Represents a Q31.32 fixed-point number.
@@ -25,6 +24,9 @@ public partial struct Fix64 : IEquatable<Fix64>, IComparable<Fix64> {
2524
public static readonly Fix64 PiTimes2 = new Fix64(PI_TIMES_2);
2625
public static readonly Fix64 PiInv = (Fix64)0.3183098861837906715377675267M;
2726
public static readonly Fix64 PiOver2Inv = (Fix64)0.6366197723675813430755350535M;
27+
static readonly Fix64 Log2Max = new Fix64(LOG2MAX);
28+
static readonly Fix64 Log2Min = new Fix64(LOG2MIN);
29+
static readonly Fix64 Ln2 = new Fix64(LN2);
2830

2931
static readonly Fix64 LutInterval = (Fix64)(LUT_SIZE - 1) / PiOver2;
3032
const long MAX_VALUE = long.MaxValue;
@@ -35,6 +37,9 @@ public partial struct Fix64 : IEquatable<Fix64>, IComparable<Fix64> {
3537
const long PI_TIMES_2 = 0x6487ED511;
3638
const long PI = 0x3243F6A88;
3739
const long PI_OVER_2 = 0x1921FB544;
40+
const long LN2 = 0xB17217F7;
41+
const long LOG2MAX = 0x1F00000000;
42+
const long LOG2MIN = -0x2000000000;
3843
const int LUT_SIZE = (int)(PI_OVER_2 >> 15);
3944

4045
/// <summary>
@@ -206,7 +211,7 @@ static long AddOverflowHelper(long x, long y, ref bool overflow) {
206211
// then this means the result overflowed.
207212
var topCarry = hihi >> FRACTIONAL_PLACES;
208213
if (topCarry != 0 && topCarry != -1 /*&& xl != -17 && yl != -17*/) {
209-
return opSignsEqual ? MaxValue : MinValue;
214+
return opSignsEqual ? MaxValue : MinValue;
210215
}
211216

212217
// If signs differ, both operands' magnitudes are greater than 1,
@@ -257,7 +262,7 @@ public static Fix64 FastMul(Fix64 x, Fix64 y) {
257262
return new Fix64(sum);
258263
}
259264

260-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
265+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
261266
static int CountLeadingZeroes(ulong x) {
262267
int result = 0;
263268
while ((x & 0xF000000000000000) == 0) { result += 4; x <<= 4; }
@@ -318,7 +323,7 @@ static int CountLeadingZeroes(ulong x) {
318323

319324
public static Fix64 operator %(Fix64 x, Fix64 y) {
320325
return new Fix64(
321-
x.m_rawValue == MIN_VALUE & y.m_rawValue == -1 ?
326+
x.m_rawValue == MIN_VALUE & y.m_rawValue == -1 ?
322327
0 :
323328
x.m_rawValue % y.m_rawValue);
324329
}
@@ -359,6 +364,141 @@ public static Fix64 FastMod(Fix64 x, Fix64 y) {
359364
return x.m_rawValue <= y.m_rawValue;
360365
}
361366

367+
/// <summary>
368+
/// Returns 2 raised to the specified power.
369+
/// Provides at least 6 decimals of accuracy.
370+
/// </summary>
371+
internal static Fix64 Pow2(Fix64 x) {
372+
if (x.RawValue == 0) {
373+
return One;
374+
}
375+
376+
// Avoid negative arguments by exploiting that exp(-x) = 1/exp(x).
377+
bool neg = x.RawValue < 0;
378+
if (neg) {
379+
x = -x;
380+
}
381+
382+
if (x == One) {
383+
return neg ? One / (Fix64)2 : (Fix64)2;
384+
}
385+
if (x >= Log2Max) {
386+
return neg ? One / MaxValue : MaxValue;
387+
}
388+
if (x <= Log2Min) {
389+
return neg ? MaxValue : Zero;
390+
}
391+
392+
/* The algorithm is based on the power series for exp(x):
393+
* http://en.wikipedia.org/wiki/Exponential_function#Formal_definition
394+
*
395+
* From term n, we get term n+1 by multiplying with x/n.
396+
* When the sum term drops to zero, we can stop summing.
397+
*/
398+
399+
int integerPart = (int)Floor(x);
400+
// Take fractional part of exponent
401+
x = new Fix64(x.m_rawValue & 0x00000000FFFFFFFF);
402+
403+
var result = One;
404+
var term = One;
405+
int i = 1;
406+
while (term.m_rawValue != 0) {
407+
term = FastMul(FastMul(x, term), Ln2) / (Fix64)i;
408+
result += term;
409+
i++;
410+
}
411+
412+
result = FromRaw(result.m_rawValue << integerPart);
413+
if (neg) {
414+
result = One / result;
415+
}
416+
417+
return result;
418+
}
419+
420+
/// <summary>
421+
/// Returns the base-2 logarithm of a specified number.
422+
/// Provides at least 9 decimals of accuracy.
423+
/// </summary>
424+
/// <exception cref="ArgumentOutOfRangeException">
425+
/// The argument was non-positive
426+
/// </exception>
427+
internal static Fix64 Log2(Fix64 x) {
428+
if (x.RawValue <= 0) {
429+
throw new ArgumentOutOfRangeException("Non-positive value passed to Ln", "x");
430+
}
431+
432+
// This implementation is based on Clay. S. Turner's fast binary logarithm
433+
// algorithm (C. S. Turner, "A Fast Binary Logarithm Algorithm", IEEE Signal
434+
// Processing Mag., pp. 124,140, Sep. 2010.)
435+
436+
long b = 1U << (FRACTIONAL_PLACES - 1);
437+
long y = 0;
438+
439+
long rawX = x.m_rawValue;
440+
while (rawX < ONE) {
441+
rawX <<= 1;
442+
y -= ONE;
443+
}
444+
445+
while (rawX >= (ONE << 1)) {
446+
rawX >>= 1;
447+
y += ONE;
448+
}
449+
450+
var z = new Fix64(rawX);
451+
452+
for (int i = 0; i < FRACTIONAL_PLACES; i++) {
453+
z = FastMul(z, z);
454+
if (z.m_rawValue >= (ONE << 1)) {
455+
z = new Fix64(z.m_rawValue >> 1);
456+
y += b;
457+
}
458+
b >>= 1;
459+
}
460+
461+
return new Fix64(y);
462+
}
463+
464+
/// <summary>
465+
/// Returns the natural logarithm of a specified number.
466+
/// Provides at least 7 decimals of accuracy.
467+
/// </summary>
468+
/// <exception cref="ArgumentOutOfRangeException">
469+
/// The argument was non-positive
470+
/// </exception>
471+
public static Fix64 Ln(Fix64 x) {
472+
return FastMul(Log2(x), Ln2);
473+
}
474+
475+
/// <summary>
476+
/// Returns a specified number raised to the specified power.
477+
/// Provides about 5 digits of accuracy for the result.
478+
/// </summary>
479+
/// <exception cref="DivideByZeroException">
480+
/// The base was zero, with a negative exponent
481+
/// </exception>
482+
/// <exception cref="ArgumentOutOfRangeException">
483+
/// The base was negative, with a non-zero exponent
484+
/// </exception>
485+
public static Fix64 Pow(Fix64 b, Fix64 exp) {
486+
if (b == One) {
487+
return One;
488+
}
489+
if (exp.m_rawValue == 0) {
490+
return One;
491+
}
492+
if (b.m_rawValue == 0) {
493+
if (exp.m_rawValue < 0) {
494+
throw new DivideByZeroException();
495+
}
496+
return Zero;
497+
}
498+
499+
Fix64 log2 = Log2(b);
500+
return Pow2(exp * log2);
501+
}
362502

363503
/// <summary>
364504
/// Returns the square root of a specified number.
@@ -438,14 +578,14 @@ public static Fix64 Sin(Fix64 x) {
438578
// Find the two closest values in the LUT and perform linear interpolation
439579
// This is what kills the performance of this function on x86 - x64 is fine though
440580
var rawIndex = FastMul(clamped, LutInterval);
441-
var roundedIndex = Round(rawIndex);
581+
var roundedIndex = Round(rawIndex);
442582
var indexError = FastSub(rawIndex, roundedIndex);
443583

444-
var nearestValue = new Fix64(SinLut[flipHorizontal ?
445-
SinLut.Length - 1 - (int)roundedIndex :
584+
var nearestValue = new Fix64(SinLut[flipHorizontal ?
585+
SinLut.Length - 1 - (int)roundedIndex :
446586
(int)roundedIndex]);
447-
var secondNearestValue = new Fix64(SinLut[flipHorizontal ?
448-
SinLut.Length - 1 - (int)roundedIndex - Sign(indexError) :
587+
var secondNearestValue = new Fix64(SinLut[flipHorizontal ?
588+
SinLut.Length - 1 - (int)roundedIndex - Sign(indexError) :
449589
(int)roundedIndex + Sign(indexError)]);
450590

451591
var delta = FastMul(indexError, FastAbs(FastSub(nearestValue, secondNearestValue))).m_rawValue;
@@ -475,7 +615,7 @@ public static Fix64 FastSin(Fix64 x) {
475615
return new Fix64(flipVertical ? -nearestValue : nearestValue);
476616
}
477617

478-
618+
479619
static long ClampSinValue(long angle, out bool flipHorizontal, out bool flipVertical) {
480620
var largePI = 7244019458077122842;
481621
// Obtained from ((Fix64)1686629713.065252369824872831112M).m_rawValue
@@ -486,8 +626,7 @@ static long ClampSinValue(long angle, out bool flipHorizontal, out bool flipVert
486626
// Whereas simply doing x % PI_TIMES_2 is the 2e-3 range.
487627

488628
var clamped2Pi = angle;
489-
for (int i = 0; i < 29; ++i)
490-
{
629+
for (int i = 0; i < 29; ++i) {
491630
clamped2Pi %= (largePI >> i);
492631
}
493632
if (angle < 0) {

src/Properties/AssemblyInfo.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@
3434
// [assembly: AssemblyVersion("1.0.*")]
3535
[assembly: AssemblyVersion("1.0.0.0")]
3636
[assembly: AssemblyFileVersion("1.0.0.0")]
37+
38+
// Allow internal methods to be tested
39+
[assembly: InternalsVisibleTo("Tests")]

tests/Fix64Tests.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,106 @@ public void Sqrt()
361361
}
362362
}
363363

364+
[Fact]
365+
public void Log2()
366+
{
367+
double maxDelta = (double)(Fix64.Precision * 4);
368+
369+
for (int j = 0; j < m_testCases.Length; ++j)
370+
{
371+
var b = Fix64.FromRaw(m_testCases[j]);
372+
373+
if (b <= Fix64.Zero)
374+
{
375+
Assert.Throws<ArgumentOutOfRangeException>(() => Fix64.Log2(b));
376+
}
377+
else
378+
{
379+
var expected = Math.Log((double)b)/Math.Log(2);
380+
var actual = (double)Fix64.Log2(b);
381+
var delta = Math.Abs(expected - actual);
382+
383+
Assert.True(delta <= maxDelta, string.Format("Ln({0}) = expected {1} but got {2}", b, expected, actual));
384+
}
385+
}
386+
}
387+
388+
[Fact]
389+
public void Ln()
390+
{
391+
double maxDelta = 0.00000001;
392+
393+
for (int j = 0; j < m_testCases.Length; ++j)
394+
{
395+
var b = Fix64.FromRaw(m_testCases[j]);
396+
397+
if (b <= Fix64.Zero)
398+
{
399+
Assert.Throws<ArgumentOutOfRangeException>(() => Fix64.Ln(b));
400+
}
401+
else
402+
{
403+
var expected = Math.Log((double)b);
404+
var actual = (double)Fix64.Ln(b);
405+
var delta = Math.Abs(expected - actual);
406+
407+
Assert.True(delta <= maxDelta, string.Format("Ln({0}) = expected {1} but got {2}", b, expected, actual));
408+
}
409+
}
410+
}
411+
412+
[Fact]
413+
public void Pow2()
414+
{
415+
double maxDelta = 0.0000001;
416+
for (int i = 0; i < m_testCases.Length; ++i)
417+
{
418+
var e = Fix64.FromRaw(m_testCases[i]);
419+
420+
var expected = Math.Min(Math.Pow(2, (double)e), (double)Fix64.MaxValue);
421+
var actual = (double)Fix64.Pow2(e);
422+
var delta = Math.Abs(expected - actual);
423+
424+
Assert.True(delta <= maxDelta, string.Format("Pow2({0}) = expected {1} but got {2}", e, expected, actual));
425+
}
426+
}
427+
428+
[Fact]
429+
public void Pow()
430+
{
431+
for (int i = 0; i < m_testCases.Length; ++i)
432+
{
433+
var b = Fix64.FromRaw(m_testCases[i]);
434+
435+
for (int j = 0; j < m_testCases.Length; ++j)
436+
{
437+
var e = Fix64.FromRaw(m_testCases[j]);
438+
439+
if (b == Fix64.Zero && e < Fix64.Zero)
440+
{
441+
Assert.Throws<DivideByZeroException>(() => Fix64.Pow(b, e));
442+
}
443+
else if (b < Fix64.Zero && e != Fix64.Zero)
444+
{
445+
Assert.Throws<ArgumentOutOfRangeException>(() => Fix64.Pow(b, e));
446+
}
447+
else
448+
{
449+
var expected = e == Fix64.Zero ? 1 : b == Fix64.Zero ? 0 : Math.Min(Math.Pow((double)b, (double)e), (double)Fix64.MaxValue);
450+
451+
// Absolute precision deteriorates with large result values, take this into account
452+
// Similarly, large exponents reduce precision, even if result is small.
453+
double maxDelta = Math.Abs((double)e) > 100000000 ? 0.5 : expected > 100000000 ? 10 : expected > 1000 ? 0.5 : 0.00001;
454+
455+
var actual = (double)Fix64.Pow(b, e);
456+
var delta = Math.Abs(expected - actual);
457+
458+
Assert.True(delta <= maxDelta, string.Format("Pow({0}, {1}) = expected {2} but got {3}", b, e, expected, actual));
459+
}
460+
}
461+
}
462+
}
463+
364464
[Fact]
365465
public void Modulus()
366466
{

0 commit comments

Comments
 (0)