Skip to content

Commit b6a8a01

Browse files
Bartlasik
authored andcommitted
Add precise Atan and Acos functions
1 parent 6dff437 commit b6a8a01

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

src/Fix64.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,77 @@ public static Fix64 Tan(Fix64 x) {
704704
return new Fix64(finalValue);
705705
}
706706

707+
/// <summary>
708+
/// Returns the arccos of of the specified number, calculated using Atan and Sqrt
709+
/// This function has at least 7 decimals of accuracy.
710+
/// </summary>
711+
public static Fix64 Acos(Fix64 x)
712+
{
713+
if (x.RawValue == 0)
714+
return Fix64.PiOver2;
715+
716+
Fix64 result = Fix64.Atan(Fix64.Sqrt(One - x * x) / x);
717+
if (x.RawValue < 0)
718+
return result + Fix64.Pi;
719+
else
720+
return result;
721+
}
722+
723+
/// <summary>
724+
/// Returns the arctan of of the specified number, calculated using Euler series
725+
/// This function has at least 7 decimals of accuracy.
726+
/// </summary>
727+
public static Fix64 Atan(Fix64 z)
728+
{
729+
if (z.RawValue == 0)
730+
return Zero;
731+
732+
// Force positive values for argument
733+
// Atan(-z) = -Atan(z).
734+
bool neg = (z.RawValue < 0);
735+
if (neg) z = -z;
736+
737+
Fix64 result;
738+
739+
if (z == One)
740+
result = Pi/(Fix64)4;
741+
else
742+
{
743+
bool invert = z > One;
744+
if (invert) z = One / z;
745+
746+
result = One;
747+
Fix64 term = One;
748+
749+
Fix64 zSq = z * z;
750+
Fix64 zSq2 = zSq * (Fix64)2;
751+
Fix64 zSqPlusOne = zSq + One;
752+
Fix64 zSq12 = zSqPlusOne * (Fix64)2;
753+
Fix64 dividend = zSq2;
754+
Fix64 divisor = zSqPlusOne * (Fix64)3;
755+
756+
for (int i = 2; i < 30; i++)
757+
{
758+
term *= dividend / divisor;
759+
result += term;
760+
761+
dividend += zSq2;
762+
divisor += zSq12;
763+
764+
if (term.RawValue == 0)
765+
break;
766+
}
767+
768+
result = result * z / zSqPlusOne;
769+
770+
if (invert)
771+
result = PiOver2 - result;
772+
}
773+
774+
if (neg) result = -result;
775+
return result;
776+
}
777+
707778
public static Fix64 Atan2(Fix64 y, Fix64 x) {
708779
var yl = y.m_rawValue;
709780
var xl = x.m_rawValue;

tests/Fix64Tests.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,42 @@ public void FastSin()
579579
}
580580
}
581581

582+
[Fact]
583+
public void Acos()
584+
{
585+
var maxDelta = 0.00000001m;
586+
var deltas = new List<decimal>();
587+
588+
// Precision
589+
for (var x = -1.0; x < 1.0; x += 0.001)
590+
{
591+
var xf = (Fix64)x;
592+
var actual = (decimal)Fix64.Acos(xf);
593+
var expected = (decimal)Math.Acos((double)xf);
594+
var delta = Math.Abs(actual - expected);
595+
deltas.Add(delta);
596+
Assert.True(delta <= maxDelta, string.Format("Precision: Acos({0}): expected {1} but got {2}", xf, expected, actual));
597+
}
598+
599+
for (int i = 0; i < m_testCases.Length; ++i)
600+
{
601+
var b = Fix64.FromRaw(m_testCases[i]);
602+
603+
if (b < -Fix64.One)
604+
continue;
605+
if (b > Fix64.One)
606+
continue;
607+
608+
var expected = (decimal)Math.Acos((double)b);
609+
var actual = (decimal)Fix64.Acos(b);
610+
var delta = Math.Abs(expected - actual);
611+
deltas.Add(delta);
612+
Assert.True(delta <= maxDelta, string.Format("Acos({0}) = expected {1} but got {2}", b, expected, actual));
613+
}
614+
Console.WriteLine("Max error: {0} ({1} times precision)", deltas.Max(), deltas.Max() / Fix64.Precision);
615+
Console.WriteLine("Average precision: {0} ({1} times precision)", deltas.Average(), deltas.Average() / Fix64.Precision);
616+
}
617+
582618
[Fact]
583619
public void Cos()
584620
{
@@ -666,6 +702,64 @@ public void Tan()
666702
//}
667703
}
668704

705+
[Fact]
706+
public void Atan()
707+
{
708+
var maxDelta = 0.00000001m;
709+
var deltas = new List<decimal>();
710+
711+
// Precision
712+
for (var x = -1.0; x < 1.0; x += 0.0001)
713+
{
714+
var xf = (Fix64)x;
715+
var actual = (decimal)Fix64.Atan(xf);
716+
var expected = (decimal)Math.Atan((double)xf);
717+
var delta = Math.Abs(actual - expected);
718+
deltas.Add(delta);
719+
Assert.True(delta <= maxDelta, string.Format("Precision: Atan({0}): expected {1} but got {2}", xf, expected, actual));
720+
}
721+
722+
// Scalability and edge cases
723+
foreach (var x in m_testCases)
724+
{
725+
var xf = (Fix64)x;
726+
var actual = (decimal)Fix64.Atan(xf);
727+
var expected = (decimal)Math.Atan((double)xf);
728+
var delta = Math.Abs(actual - expected);
729+
deltas.Add(delta);
730+
Assert.True(delta <= maxDelta, string.Format("Scalability: Atan({0}): expected {1} but got {2}", xf, expected, actual));
731+
}
732+
Console.WriteLine("Max error: {0} ({1} times precision)", deltas.Max(), deltas.Max() / Fix64.Precision);
733+
Console.WriteLine("Average precision: {0} ({1} times precision)", deltas.Average(), deltas.Average() / Fix64.Precision);
734+
}
735+
736+
//[Fact]
737+
public void AtanBenchmark()
738+
{
739+
var deltas = new List<decimal>();
740+
741+
var swf = new Stopwatch();
742+
var swd = new Stopwatch();
743+
744+
for (var x = -1.0; x < 1.0; x += 0.001)
745+
{
746+
for (int k = 0; k < 1000; ++k)
747+
{
748+
var xf = (Fix64)x;
749+
swf.Start();
750+
var actualF = Fix64.Atan(xf);
751+
swf.Stop();
752+
swd.Start();
753+
var expected = Math.Atan((double)xf);
754+
swd.Stop();
755+
deltas.Add(Math.Abs((decimal)actualF - (decimal)expected));
756+
}
757+
}
758+
Console.WriteLine("Max error: {0} ({1} times precision)", deltas.Max(), deltas.Max() / Fix64.Precision);
759+
Console.WriteLine("Average precision: {0} ({1} times precision)", deltas.Average(), deltas.Average() / Fix64.Precision);
760+
Console.WriteLine("Fix64.Atan time = {0}ms, Math.Atan time = {1}ms", swf.ElapsedMilliseconds, swd.ElapsedMilliseconds);
761+
}
762+
669763
[Fact]
670764
public void Atan2()
671765
{

0 commit comments

Comments
 (0)