Skip to content

Commit 45e6f0a

Browse files
committed
Clearly marked experimental types as such; minor improvements
1 parent f87799b commit 45e6f0a

File tree

8 files changed

+124
-64
lines changed

8 files changed

+124
-64
lines changed

Fix16.cs renamed to Experimental/Fix16.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
namespace FixMath.NET {
55

6+
/// <summary>
7+
/// This is more or less a straight port of libfixmath (https://code.google.com/p/libfixmath/)
8+
/// It sort of works but I didn't spend much time on it.
9+
/// </summary>
610
public partial struct Fix16 : IEquatable<Fix16>, IComparable<Fix16> {
711

812
readonly int m_rawValue;

Fix16Test.cs renamed to Experimental/Fix16Test.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ public void AdditionSign() {
307307
}
308308
}
309309

310-
[Test]
310+
//[Test]
311311
public void Sqrt() {
312312
for (int i = (int.MaxValue / 2); i <= int.MaxValue; i += 21) {
313313
var f = Fix16.FromRaw(i);

Fix8.cs renamed to Experimental/Fix8.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
using System.Globalization;
33

44
namespace FixMath.NET {
5+
6+
/// <summary>
7+
/// Represents a Q3.4 fixed-point number.
8+
/// </summary>
9+
/// <remarks>
10+
/// I wrote this type essentially as a stepping stone towards writing Fix64,
11+
/// so while it works, it is not very optimized and lacks some operations.
12+
/// </remarks>
513
struct Fix8 : IEquatable<Fix8>, IComparable<Fix8> {
614
readonly sbyte m_rawValue;
715
/// <summary>
@@ -28,11 +36,11 @@ struct Fix8 : IEquatable<Fix8>, IComparable<Fix8> {
2836

2937
/// <summary>
3038
/// Returns the absolute value of a Fix8 number.
31-
/// If the number is equal to MinValue, throws an OverflowException.
39+
/// Note: Abs(Fix8.MinValue) == Fix8.MaxValue.
3240
/// </summary>
3341
public static Fix8 Abs(Fix8 value) {
3442
if (value.m_rawValue == sbyte.MinValue) {
35-
throw new OverflowException("Cannot take the absolute value of the minimum value representable.");
43+
return MaxValue;
3644
}
3745

3846
// branchless implementation, see http://www.strchr.com/optimized_abs_function
@@ -100,7 +108,7 @@ public static explicit operator decimal(Fix8 value) {
100108

101109
public static explicit operator Fix8(decimal value) {
102110
var nearestExact = Math.Round(value * 16m);
103-
return new Fix8((sbyte)(nearestExact ));
111+
return new Fix8((sbyte)(nearestExact));
104112
}
105113

106114
public static Fix8 operator +(Fix8 x, Fix8 y) {
@@ -166,7 +174,7 @@ static sbyte AddOverflowHelper(sbyte x, sbyte y, ref bool overflow) {
166174
//sbyte sum = (sbyte)((sbyte)loResult + midResult1 + midResult2 + hiResult);
167175

168176
bool opSignsEqual = ((xl ^ yl) & sbyte.MinValue) == 0;
169-
177+
170178
// if signs of operands are equal and sign of result is negative,
171179
// then multiplication overflowed positively
172180
// the reverse is also true
@@ -212,7 +220,7 @@ static sbyte AddOverflowHelper(sbyte x, sbyte y, ref bool overflow) {
212220
return new Fix8(sum);
213221
}
214222

215-
static int Clz(byte x) {
223+
static int CountLeadingZeroes(byte x) {
216224
int result = 0;
217225
if (x == 0) { return 8; }
218226
while ((x & 0xF0) == 0) { result += 4; x <<= 4; }
@@ -224,9 +232,9 @@ static int Clz(byte x) {
224232
var xl = x.m_rawValue;
225233
var yl = y.m_rawValue;
226234

227-
//if (yl == 0) {
228-
// throw new DivideByZeroException();
229-
//}
235+
if (yl == 0) {
236+
throw new DivideByZeroException();
237+
}
230238

231239
var remainder = (byte)(xl >= 0 ? xl : -xl);
232240
var divider = (byte)(yl >= 0 ? yl : -yl);
@@ -241,7 +249,7 @@ static int Clz(byte x) {
241249
}
242250

243251
while (remainder != 0 && bitPos >= 0) {
244-
int shift = Clz(remainder);
252+
int shift = CountLeadingZeroes(remainder);
245253
if (shift > bitPos) {
246254
shift = bitPos;
247255
}
@@ -277,9 +285,9 @@ static int Clz(byte x) {
277285
public static Fix8 Sqrt(Fix8 x) {
278286
var xl = x.m_rawValue;
279287
if (xl < 0) {
280-
// We cannot represent infinities like Single and Double, and Sqrt is
281-
// mathematically undefined for x < 0. So we just throw an exception.
282-
throw new ArgumentException("Negative value passed to Sqrt", "x");
288+
// We cannot represent NaN, and Sqrt is undefined for x < 0.
289+
// So we just throw an exception.
290+
throw new ArgumentOutOfRangeException("Negative value passed to Sqrt", "x");
283291
}
284292

285293
var num = (byte)xl;
@@ -337,6 +345,10 @@ public static Fix8 Sqrt(Fix8 x) {
337345
}
338346

339347

348+
/// <summary>
349+
/// If it worked, this method would return the Sine of x
350+
/// But it doesn't, so don't use it
351+
/// </summary>
340352
public static Fix8 Sin(Fix8 x) {
341353
// Using Taylor series http://dotancohen.com/eng/taylor-sine.php
342354

@@ -388,10 +400,8 @@ public override int GetHashCode() {
388400
}
389401

390402
public override bool Equals(object obj) {
391-
if (!(obj is Fix8)) {
392-
return false;
393-
}
394-
return ((Fix8)obj).m_rawValue == m_rawValue;
403+
var fix8 = obj as Fix8?;
404+
return fix8.HasValue && fix8.Value.m_rawValue == m_rawValue;
395405
}
396406

397407
public override string ToString() {

Fix8Tests.cs renamed to Experimental/Fix8Tests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class Fix8Tests {
1313

1414
[Test]
1515
public void DecimalToFix8AndBack() {
16-
var sources = new[] { -8m, -7.99m, -7.98m, -7.97m, -7.96m, -7.95m, -0.0m, 0.0m, 7.87m, 7.88m, 7.89m, 7.90m, 7.91m, 7.92m, 7.93m, 7.94m, 7.95m };
17-
var expecteds = new[] { -8m, -8m, -8m, -8m, -7.9375m, -7.9375m, 0.0m, 0.0m, 7.875m, 7.875m, 7.875m, 7.875m, 7.9375m, 7.9375m, 7.9375m, 7.9375m, 7.9375m };
16+
var sources = new[] { -8m, -7.99m, -7.98m, -7.97m, -7.96m, -7.95m, -0.0m, 0.0m, 7.87m, 7.88m, 7.89m, 7.90m, 7.91m, 7.92m, 7.93m, 7.94m, 7.95m };
17+
var expecteds = new[] { -8m, -8m, -8m, -8m, -7.9375m, -7.9375m, 0.0m, 0.0m, 7.875m, 7.875m, 7.875m, 7.875m, 7.9375m, 7.9375m, 7.9375m, 7.9375m, 7.9375m };
1818
int failed = 0;
1919
for (var i = 0; i < sources.Length; ++i) {
2020
var expected = expecteds[i];
@@ -236,8 +236,8 @@ public void Modulus() {
236236

237237
[Test]
238238
public void Sin() {
239-
for (int i = sbyte.MinValue; i <= sbyte.MaxValue; ++i) {
240-
var f = Fix8.FromRaw((sbyte)i);
239+
for (sbyte i = sbyte.MinValue; i <= sbyte.MaxValue; ++i) {
240+
var f = Fix8.FromRaw(i);
241241
var expected = (decimal)Math.Sin((double)(decimal)f);
242242
var actual = (decimal)Fix8.Sin(f);
243243
var delta = Math.Abs(expected - actual);
@@ -247,7 +247,7 @@ public void Sin() {
247247

248248
[Test]
249249
public void Abs() {
250-
Assert.Throws<OverflowException>(() => Fix8.Abs(Fix8.MinValue));
250+
Assert.AreEqual(Fix8.MaxValue, Fix8.Abs(Fix8.MinValue));
251251
for (int i = sbyte.MinValue + 1; i <= sbyte.MaxValue; ++i) {
252252
var f = Fix8.FromRaw((sbyte)i);
253253
var original = (decimal)f;
@@ -310,7 +310,7 @@ public void Sqrt() {
310310
for (int i = sbyte.MinValue; i <= sbyte.MaxValue; ++i) {
311311
var f = Fix8.FromRaw((sbyte)i);
312312
if (i < 0) {
313-
Assert.Throws<ArgumentException>(() => Fix8.Sqrt(f));
313+
Assert.Throws<ArgumentOutOfRangeException>(() => Fix8.Sqrt(f));
314314
}
315315
else {
316316
var expected = (decimal)Math.Sqrt((double)(decimal)f);

Fix64.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace FixMath.NET {
1313

14+
/// <summary>
15+
/// Represents a Q31.32 fixed-point number.
16+
/// </summary>
1417
public partial struct Fix64 : IEquatable<Fix64>, IComparable<Fix64> {
1518
readonly long m_rawValue;
1619

@@ -526,6 +529,12 @@ public static Fix64 FastCos(Fix64 x) {
526529
return FastSin(new Fix64(rawAngle));
527530
}
528531

532+
/// <summary>
533+
/// Returns the tangent of x.
534+
/// </summary>
535+
/// <remarks>
536+
/// This function is not well-tested. It may be wildly inaccurate.
537+
/// </remarks>
529538
public static Fix64 Tan(Fix64 x) {
530539
var clampedPi = x.m_rawValue % PI;
531540
var flip = false;

Fix64Tests.cs

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -66,26 +66,53 @@ public void LongToFix64AndBack() {
6666

6767
[Test]
6868
public void DoubleToFix64AndBack() {
69-
var sources = new[] { int.MinValue, -1.0, -0.0, 0.0, 1.0, int.MaxValue };
70-
var expecteds = new[] { int.MinValue, -1.0, 0.0, 0.0, 1.0, int.MaxValue };
71-
for (int i = 0; i < sources.Length; ++i) {
72-
var expected = expecteds[i];
73-
var f = (Fix64)sources[i];
74-
var actual = (double)f;
75-
Assert.AreEqual(expected, actual);
69+
var sources = new[] {
70+
(double)int.MinValue,
71+
-(double)Math.PI,
72+
-(double)Math.E,
73+
-1.0,
74+
-0.0,
75+
0.0,
76+
1.0,
77+
(double)Math.PI,
78+
(double)Math.E,
79+
(double)int.MaxValue
80+
};
81+
82+
foreach (var value in sources) {
83+
AreEqualWithinPrecision(value, (double)(Fix64)value);
7684
}
7785
}
7886

87+
static void AreEqualWithinPrecision(decimal value1, decimal value2) {
88+
Assert.IsTrue(Math.Abs(value2 - value1) < Fix64.Precision);
89+
}
90+
91+
static void AreEqualWithinPrecision(double value1, double value2) {
92+
Assert.IsTrue(Math.Abs(value2 - value1) < (double)Fix64.Precision);
93+
}
94+
7995
[Test]
8096
public void DecimalToFix64AndBack() {
81-
var sources = new[] { int.MinValue, -123456789.123456789m, -1.0m, -0.0m, 0.0m, 1.0m, 123456789.123456789m, int.MaxValue };
82-
var expecteds = new[] { int.MinValue, -123456789.123456789m, -1.0m, 0.0m, 0.0m, 1.0m, 123456789.123456789m, int.MaxValue };
83-
for (int i = 0; i < sources.Length; ++i) {
84-
var expected = expecteds[i];
85-
var f = (Fix64)sources[i];
86-
var actual = (decimal)f;
87-
actual = decimal.Round(actual, 9); // we're supposed to have 9 significant decimal places
88-
Assert.AreEqual(expected, actual);
97+
98+
Assert.AreEqual(Fix64.MaxValue, (Fix64)(decimal)Fix64.MaxValue);
99+
Assert.AreEqual(Fix64.MinValue, (Fix64)(decimal)Fix64.MinValue);
100+
101+
var sources = new[] {
102+
int.MinValue,
103+
-(decimal)Math.PI,
104+
-(decimal)Math.E,
105+
-1.0m,
106+
-0.0m,
107+
0.0m,
108+
1.0m,
109+
(decimal)Math.PI,
110+
(decimal)Math.E,
111+
int.MaxValue
112+
};
113+
114+
foreach (var value in sources) {
115+
AreEqualWithinPrecision(value, (decimal)(Fix64)value);
89116
}
90117
}
91118

@@ -325,7 +352,7 @@ public void Modulus() {
325352

326353
[Test]
327354
public void SinBenchmark() {
328-
var deltas = new List<decimal>();
355+
var deltas = new List<double>();
329356

330357
var swf = new Stopwatch();
331358
var swd = new Stopwatch();
@@ -334,18 +361,18 @@ public void SinBenchmark() {
334361
for (var angle = 0.0; angle <= 2 * Math.PI ; angle += 0.000004) {
335362
var f = (Fix64)angle;
336363
swf.Start();
337-
var actualF = Fix64.FastSin(f);
364+
var actualF = Fix64.Sin(f);
338365
swf.Stop();
339-
var actual = (decimal)actualF;
366+
var actual = (double)actualF;
340367
swd.Start();
341368
var expectedD = Math.Sin(angle);
342369
swd.Stop();
343-
var expected = (decimal)expectedD;
370+
var expected = (double)expectedD;
344371
var delta = Math.Abs(expected - actual);
345372
deltas.Add(delta);
346373
}
347-
Console.WriteLine("Max error: {0} ({1} times precision)", deltas.Max(), deltas.Max() / Fix64.Precision);
348-
Console.WriteLine("Average precision: {0} ({1} times precision)", deltas.Average(), deltas.Average() / Fix64.Precision);
374+
Console.WriteLine("Max error: {0} ({1} times precision)", deltas.Max(), deltas.Max() / (double)Fix64.Precision);
375+
Console.WriteLine("Average precision: {0} ({1} times precision)", deltas.Average(), deltas.Average() / (double)Fix64.Precision);
349376
Console.WriteLine("Fix64.Sin time = {0}ms, Math.Sin time = {1}ms", swf.ElapsedMilliseconds, swd.ElapsedMilliseconds);
350377
}
351378

@@ -373,12 +400,19 @@ public void Sin() {
373400
}
374401

375402
foreach (var val in m_testCases) {
376-
var f = (Fix64)val;
403+
var f = Fix64.FromRaw(val);
377404
var actualF = Fix64.Sin(f);
378405
var expected = (decimal)Math.Sin((double)f);
379406
var delta = Math.Abs(expected - (decimal)actualF);
380-
Assert.LessOrEqual(delta, 0.01, string.Format("Sin({0}): expected {1} but got {2}", f, expected, actualF));
407+
Assert.LessOrEqual(delta, 0.003, string.Format("Sin({0}): expected {1} but got {2}", f, expected, actualF));
381408
}
409+
410+
Console.WriteLine("Max delta = {0}", m_testCases.Max(val => {
411+
var f = Fix64.FromRaw(val);
412+
var actualF = Fix64.Sin(f);
413+
var expected = (decimal)Math.Sin((double)f);
414+
return Math.Abs(expected - (decimal)actualF);
415+
}));
382416
}
383417

384418
[Test]
@@ -392,7 +426,7 @@ public void FastSin() {
392426
}
393427

394428
foreach (var val in m_testCases) {
395-
var f = (Fix64)val;
429+
var f = Fix64.FromRaw(val);
396430
var actualF = Fix64.FastSin(f);
397431
var expected = (decimal)Math.Sin((double)f);
398432
var delta = Math.Abs(expected - (decimal)actualF);
@@ -424,11 +458,11 @@ public void Cos() {
424458
}
425459

426460
foreach (var val in m_testCases) {
427-
var f = (Fix64)val;
461+
var f = Fix64.FromRaw(val);
428462
var actualF = Fix64.Cos(f);
429463
var expected = (decimal)Math.Cos((double)f);
430464
var delta = Math.Abs(expected - (decimal)actualF);
431-
Assert.LessOrEqual(delta, 0.01, string.Format("Cos({0}): expected {1} but got {2}", f, expected, actualF));
465+
Assert.LessOrEqual(delta, 0.004, string.Format("Cos({0}): expected {1} but got {2}", f, expected, actualF));
432466
}
433467
}
434468

@@ -443,7 +477,7 @@ public void FastCos() {
443477
}
444478

445479
foreach (var val in m_testCases) {
446-
var f = (Fix64)val;
480+
var f = Fix64.FromRaw(val);
447481
var actualF = Fix64.FastCos(f);
448482
var expected = (decimal)Math.Cos((double)f);
449483
var delta = Math.Abs(expected - (decimal)actualF);
@@ -519,7 +553,7 @@ public void Atan2() {
519553
}
520554

521555

522-
[Test]
556+
//[Test]
523557
public void Atan2Benchmark() {
524558
var deltas = new List<decimal>();
525559

@@ -574,10 +608,11 @@ public void Equals() {
574608

575609
[Test]
576610
public void EqualityAndInequalityOperators() {
577-
foreach (var op1 in m_testCases) {
578-
foreach (var op2 in m_testCases) {
579-
var d1 = (decimal)op1;
580-
var d2 = (decimal)op2;
611+
var sources = m_testCases.Select(Fix64.FromRaw).ToList();
612+
foreach (var op1 in sources) {
613+
foreach (var op2 in sources) {
614+
var d1 = (double)op1;
615+
var d2 = (double)op2;
581616
Assert.True((op1 == op2) == (d1 == d2));
582617
Assert.True((op1 != op2) == (d1 != d2));
583618
Assert.False((op1 == op2) && (op1 != op2));
@@ -591,11 +626,10 @@ public void CompareTo() {
591626
var numsDecimal = nums.Select(t => (decimal)t).ToArray();
592627
Array.Sort(nums);
593628
Array.Sort(numsDecimal);
594-
var sortedNums = nums.Select(t => (decimal)t).ToArray();
595-
Assert.True(sortedNums.SequenceEqual(numsDecimal));
629+
Assert.True(nums.Select(t => (decimal)t).SequenceEqual(numsDecimal));
596630
}
597631

598-
[Test]
632+
//[Test]
599633
public void GenerateLuts() {
600634
Fix64.GenerateSinLut();
601635
Fix64.GenerateTanLut();

0 commit comments

Comments
 (0)