shithub: mc

Download patch

ref: f6e3a00fc29a7770b39c1ad51e4cfbff305bda38
parent: 9843290d44b6266758c478838d57a9478af39aa3
author: S. Gilles <sgilles@math.umd.edu>
date: Wed Jun 27 20:09:38 EDT 2018

Fix some errors in sin-impl.

 - Remez algorithms don't work when the function to be approximated
   vanishes (they track relative error, which would go to infinity),
   so the sin_coeffs needed to be recalculated.

 - sin and cos were swapped when retrieving from C, so sin(0) was 1.

 - Negative post-reduction x values needed to be handled better
   (negating sign before other potential permutations).

 - Fix huge_reduction by calculating xb from x - xhi, not x.

 - Change pi/4 for pi/2 (I forgot I was in the middle of doing this
   halfway through writing it.)

--- a/lib/math/ancillary/generate-minimax-by-Remez.gp
+++ b/lib/math/ancillary/generate-minimax-by-Remez.gp
@@ -37,8 +37,8 @@
         return(abs(f(x) - p_eval(n, v, x)));
 }
 
-{ find_M(f, n, v, a, b) = my(X, gran, l, lnext, len, xprev, xcur, xnext, yprev, ycur, ynext, thisa, thisb, k, j);
-        gran = 100 * n;
+{ find_M(f, n, v, a, b, depth) = my(X, gran, l, lnext, len, xprev, xcur, xnext, yprev, ycur, ynext, thisa, thisb, k, j);
+        gran = 10000 * depth;
         l = listcreate();
 
         xprev = a - (b - a)/gran;
@@ -64,7 +64,7 @@
         );
 
         vecsort(l, 2);
-        if(length(l) < n + 2 || l[1][2] < 2^(-100), return("q"),);
+        if(length(l) < n + 2 || l[1][2] < 2^(-2000), return("q"),);
         len = length(l);
 
         lnext = listcreate();
@@ -111,17 +111,25 @@
         for(j = 1, 100,
                 v = remez_step(f, n, a, b, c);
                 print("v = ", v);
-                c = find_M(f, n, v, a, b);
+                c = find_M(f, n, v, a, b, j);
                 if(c == "q", return,);
                 print("c = ", c);
         );
 }
 
+{ sinoverx(x) =
+        return(if(x == 0, 1, sin(x)/x));
+}
+
 print("\n");
-print("Minimaxing sin(x), degree 7, on [-Pi/(4 * 256), Pi/(4 * 256)]:");
-find_minimax(sin, 7, -Pi/1024, Pi/1024)
+print("Minimaxing sin(x) / x, degree 6, on [-Pi/(4 * 256), Pi/(4 * 256)]:");
+find_minimax(sinoverx, 6, -Pi/1024, Pi/1024)
 print("\n");
+print("(You'll need to add a 0x0 at the beginning to make a degree 7...\n");
+print("\n");
+print("\n");
 print("Minimaxing cos(x), degree 7, on [-Pi/(4 * 256), Pi/(4 * 256)]:");
 find_minimax(cos, 7, -Pi/1024, Pi/1024)
 print("\n");
-
+print("\n");
+print("Remember that there's that extra, ugly E term at the end of the vector that you want to lop off.\n");
--- a/lib/math/ancillary/pi-constants.c
+++ b/lib/math/ancillary/pi-constants.c
@@ -47,10 +47,10 @@
         }
 
         printf("\n");
-        printf("1000 bits of pi/4:\n");
+        printf("1000 bits of pi/2:\n");
 
         for (int bits_obtained = 0; bits_obtained < 1000; bits_obtained += 53) {
-                mpfr_div_si(pi, pi, 4, MPFR_RNDN);
+                mpfr_div_si(pi, pi, 2, MPFR_RNDN);
                 d = mpfr_get_d(pi, MPFR_RNDN);
                 u = FLT64_TO_UINT64(d);
                 printf("%#018lx\n", u);
@@ -59,9 +59,9 @@
         }
 
         printf("\n");
-        printf("Pre-computed 4/pi:\n");
+        printf("Pre-computed 2/pi:\n");
         mpfr_const_pi(pi, MPFR_RNDN);
-        mpfr_set_si(t, 4, MPFR_RNDN);
+        mpfr_set_si(t, 2, MPFR_RNDN);
         mpfr_div(pi, t, pi, MPFR_RNDN);
         d = mpfr_get_d(pi, MPFR_RNDN);
         u = FLT64_TO_UINT64(d);
--- a/lib/math/sin-impl.myr
+++ b/lib/math/sin-impl.myr
@@ -50,16 +50,16 @@
 /* Split precision down the middle */
 const twentysix_bits_mask = (0xffffffffffffffff << 27)
 
-/* Pi/4 in lots of detail, for range reducing sin(2^18) or so */
-const pi_over_4 : uint64[4] = [
-	0x3fe921fb54442d18,
-	0x3c61a62633145c07,
-	0xb8cf1976b7ed8fbc,
-	0x3544cf98e804177d,
+/* Pi/2 in lots of detail, for range reducing sin(2^18) or so */
+const pi_over_2 : uint64[4] = [
+	0x3ff921fb54442d18,
+	0x3c81a62633145c07,
+	0xb8ff1976b7ed8fbc,
+	0x3584cf98e804177d,
 ]
 
 /* Pre-computed inverses */
-const four_over_pi : uint64 = 0x3ff45f306dc9c883 /* 1/(pi/4) */
+const two_over_pi : uint64 = 0x3fe45f306dc9c883 /* 1/(pi/2) */
 const oneohtwofour_over_pi : uint64 = 0x40745f306dc9c883 /* 1/(pi/(4 * 256)) */
 
 /*
@@ -67,14 +67,14 @@
    and cos on [-Pi/1024, Pi/1024] (generated by a Remez algorithm).
  */
 const sin_coeff : uint64[8] = [
-	0xb791800000000000,
+	0x0000000000000000,
 	0x3ff0000000000000,
-	0x38b6000000000000,
+	0xb8c7400000000000,
 	0xbfc5555555555555,
-	0xb9cc000000000000,
-	0x3f81111111111024,
-	0x3ab0000000000000,
-	0xbf2a019f99ab90ae,
+	0x39d0000000000000,
+	0x3f81111111111061,
+	0xbacc000000000000,
+	0xbf2a019fa7ee0417,
 ]
 
 const cos_coeff : uint64[8] = [
@@ -83,7 +83,7 @@
 	0xbfe0000000000000,
 	0x39a0000000000000,
 	0x3fa55555555553ee,
-	000000000000000000,
+	0x0000000000000000,
 	0xbf56c16b9bfd9fd6,
 	0xbbec000000000000,
 ]
@@ -447,7 +447,6 @@
 	var swap_sin_cos : bool = false
 	var then_negate_sin : bool = false
 	var then_negate_cos : bool = false
-	N = ((N % 4) + 4) % 4
 	match N
 	| 1:
 		/* sin(x + pi/2) = cos(x), cos(x + pi/2) = -sin(x) */
@@ -464,11 +463,12 @@
 	| _:
 	;;
 
+	var first_negate_sin : bool = false
 	if x1 < 0.0
 		x1 = -x1
 		x2 = -x2
 		x3 = -x3
-		N = -N
+		first_negate_sin = true
 	;;
 
 	/* Figure out where in the C table x lies */
@@ -480,7 +480,7 @@
 	;;
 
 	var xi, sin_xi, cos_xi, sin_delta, cos_delta
-	(xi, sin_xi, cos_xi) = C[j]
+	(xi, cos_xi, sin_xi) = C[j]
 
 	/*
 	   Compute x - xi with compensated summation. Because xi
@@ -510,6 +510,7 @@
 	cos_delta += delta2 * fma64(delta1, 2.0*std.flt64frombits(cos_coeff[2]), std.flt64frombits(cos_coeff[1]))
 
 	var q : flt64[4]
+
 	if (want_sin && !swap_sin_cos) || (want_cos && swap_sin_cos)
 		(q[0], q[1]) = two_by_two(std.flt64frombits(sin_xi), cos_delta)
 		(q[2], q[3]) = two_by_two(std.flt64frombits(cos_xi), sin_delta)
@@ -518,6 +519,7 @@
 		/* TODO: use double-compensation instead */
 		(sin_ret, _, _) = triple_compensate_sum(q[:])
 	;;
+
 	if (want_cos && !swap_sin_cos) || (want_sin && swap_sin_cos)
 		(q[0], q[1]) = two_by_two(std.flt64frombits(cos_xi), cos_delta)
 		(q[2], q[3]) = two_by_two(std.flt64frombits(sin_xi), sin_delta)
@@ -529,8 +531,12 @@
 		(cos_ret, _, _) = triple_compensate_sum(q[:])
 	;;
 
+	if first_negate_sin
+		sin_ret = -sin_ret
+	;;
+
 	if swap_sin_cos
-		(sin_ret, cos_ret) = (cos_ret, sin_ret)
+		std.swap(&sin_ret, &cos_ret)
 	;;
 
 	if then_negate_sin
@@ -550,10 +556,10 @@
 	var Nf : flt64
 
 	/*
-	   We actually only care about N's parity. If x is very
-	   large, the ultimate N that we end up using might not be
+	   We actually only care about N mod 4. If x is very large,
+	   the ultimate N that we end up using might not be
 	   representable (either as an int64 or flt64), so we instead
-	   just keep track of the parity exactly.
+	   just keep track of the mod 4 part exactly.
 	 */
 
 	/*
@@ -572,31 +578,33 @@
 	   however.
 	 */
 	var x1, x2, x3
-	var q : flt64[12]
 	(x1, x2, x3) = huge_reduce_2pi(x)
 
-	var pi_o_4_0 : flt64 = std.flt64frombits(pi_over_4[0])
-	var pi_o_4_1 : flt64 = std.flt64frombits(pi_over_4[1])
-	var pi_o_4_2 : flt64 = std.flt64frombits(pi_over_4[2])
-	var pi_o_4_3 : flt64 = std.flt64frombits(pi_over_4[3])
+	var pi_o_2_0 : flt64 = std.flt64frombits(pi_over_2[0])
+	var pi_o_2_1 : flt64 = std.flt64frombits(pi_over_2[1])
+	var pi_o_2_2 : flt64 = std.flt64frombits(pi_over_2[2])
+	var pi_o_2_3 : flt64 = std.flt64frombits(pi_over_2[3])
 
+	var total_N = 0
+	var q : flt64[11]
 	while true
-		N = rn(x1 * std.flt64frombits(four_over_pi))
+		N = rn(x1 * std.flt64frombits(two_over_pi))
 		Nf = (-N : flt64)
 		(q[0], q[1], q[2]) = (x1, x2, x3)
-		(q[3], q[ 4]) = two_by_two(Nf, pi_o_4_0)
-		(q[5], q[ 6]) = two_by_two(Nf, pi_o_4_1)
-		(q[7], q[ 8]) = two_by_two(Nf, pi_o_4_2)
-		(q[9], q[10]) = two_by_two(Nf, pi_o_4_3)
+		(q[3], q[ 4]) = two_by_two(Nf, pi_o_2_0)
+		(q[5], q[ 6]) = two_by_two(Nf, pi_o_2_1)
+		(q[7], q[ 8]) = two_by_two(Nf, pi_o_2_2)
+		(q[9], q[10]) = two_by_two(Nf, pi_o_2_3)
 		(x1, x2, x3) = triple_compensate_sum(q[:])
+		total_N += (N % 4)
 
-		if le_33(x1, x2, x3, pi_o_4_0, pi_o_4_1, pi_o_4_2) && \
-		   le_33(-pi_o_4_0, -pi_o_4_1, -pi_o_4_2, x1, x2, x3)
+		if le_33(x1, x2, x3, pi_o_2_0, pi_o_2_1, pi_o_2_2) && \
+		   le_33(-pi_o_2_0, -pi_o_2_1, -pi_o_2_2, x1, x2, x3)
 			break
 		;;
 	;;
 
-	-> (N, x1, x2, x3)
+	-> (((total_N % 4) + 4) % 4, x1, x2, x3)
 }
 
 
@@ -632,6 +640,8 @@
 	var b1 : flt64, b2 : flt64, b3 : flt64
 	var c1 : flt64, c2 : flt64, c3 : flt64
 	var u1 : uint64, u2 : uint64, u3 : uint64
+	var xcur = x
+
 	if j == 0
 		(u1, u2, u3) = R[0]
 		a1 = std.flt64frombits(u1)
@@ -638,6 +648,8 @@
 		a2 = std.flt64frombits(u2)
 		a3 = std.flt64frombits(u3)
 		var xhi = std.flt64frombits(b & ((~0) << (52 + (25 - e : uint64))))
+		xcur -= xhi
+		b = std.flt64bits(xcur)
 		xa = scale2(xhi, -25)
 
 		(b1, b2, b3) = (x - xhi, 0.0, 0.0)
@@ -649,6 +661,8 @@
 		a3 = std.flt64frombits(u3)
 		var e1 : int64 = 50*(j : int64) + 25
 		var xhi = std.flt64frombits(b & ((~0) << (52 + (e1 - e : uint64))))
+		xcur -= xhi
+		b = std.flt64bits(xcur)
 		xa = scale2(xhi, -e1)
 
 		/* j > 0 in this branch */
@@ -658,6 +672,8 @@
 		b3 = std.flt64frombits(u3)
 		var e2 : int64 = 50*(j - 1 : int64) + 25
 		var xmi = std.flt64frombits(b & ((~0) << (52 + (e2 - e : uint64))))
+		xcur -= xmi
+		b = std.flt64bits(xcur)
 		xb = scale2(xmi, -e2)
 
 		if j == 1
@@ -674,7 +690,6 @@
 		;;
 	;;
 
-
 	/*
 	   Now we need to combine all this. Even worse, when we
 	   multiply the two together, we need to keep full precision
@@ -741,7 +756,7 @@
 	/* Multiply out */
 	var a1 : flt64 = xh * yh
 	var a2 : flt64 = xh * yl
-	var a3 : flt64 = yl * yh
+	var a3 : flt64 = xl * yh
 	var a4 : flt64 = xl * yl
 
 	/* By-hand compensated summation */
@@ -748,7 +763,7 @@
 	/* TODO: convert to normal compensated summation, move to sum-impl.myr */
 	var yy, u, t, v, z, s, c
 	if a2 < a3
-		(a3, a2) = (a2, a3)
+		std.swap(&a3, &a2)
 	;;
 
 	s = a1
--- /dev/null
+++ b/lib/math/test/sin-impl.myr
@@ -1,0 +1,106 @@
+use std
+use math
+use testr
+
+const main = {
+	math.fptrap(false)
+	testr.run([
+		[.name="sin-cos-01", .fn = sincos01], /* flt32 */
+		[.name="sin-cos-02", .fn = sincos02], /* flt64 */
+		[.name="sin-cos-03", .fn = sincos03], /* off-by-1-ulp quarantine */
+		[.name="sin-cos-04", .fn = sincos04], /* exhaustively test C */
+		[.name="sin-cos-05", .fn = sincos05], /* NaN handling */
+	][:])
+}
+
+const same32 = {a, b
+	if a == b
+		-> true
+	;;
+
+	if std.isnan(std.flt32frombits(a)) && std.isnan(std.flt32frombits(b))
+		-> true
+	;;
+
+	-> false
+}
+
+const same64 = {a, b
+	if a == b
+		-> true
+	;;
+
+	if std.isnan(std.flt64frombits(a)) && std.isnan(std.flt64frombits(b))
+		-> true
+	;;
+
+	-> false
+}
+
+const sincos01 = {c
+	var inputs : (uint32, uint32, uint32)[:] = [
+		(0x00000000, 0x00000000, 0x3f800000),
+		(0x3f000000, 0x3ef57744, 0x3f60a940),
+		(0x6e000000, 0xbec002e4, 0xbf6d50ea),
+	][:]
+
+	for (x, ys, yc) : inputs
+		var xf : flt32 = std.flt32frombits(x)
+		var rsf1, rcf1, rsf2, rcf2
+		(rsf1, rcf1) = math.sincos(xf)
+		rsf2 = math.sin(xf)
+		rcf2 = math.cos(xf)
+
+		var rsu1 = std.flt32bits(rsf1)
+		var rcu1 = std.flt32bits(rcf1)
+		var rsu2 = std.flt32bits(rsf2)
+		var rcu2 = std.flt32bits(rcf2)
+
+
+		testr.check(c, rsf1 == rsf2 && rcf1 == rcf2,
+			"sincos(0x{b=16,w=8,p=0}) is (0x{b=16,w=8,p=0}, 0x{b=16,w=8,p=0}), individual results (0x{b=16,w=8,p=0}, 0x{b=16,w=8,p=0})",
+			x, rsu1, rcu1, rsu2, rcu2)
+
+		testr.check(c, same32(rsu1, ys) && same32(rcu1, yc),
+			"sincos(0x{b=16,w=8,p=0}) should be (0x{b=16,w=8,p=0}, 0x{b=16,w=8,p=0}), was (0x{b=16,w=8,p=0}, 0x{b=16,w=8,p=0})",
+			x, ys, yc, rsu1, rcu1)
+	;;
+}
+
+const sincos02 = {c
+	var inputs : (uint64, uint64, uint64)[:] = [
+		(0x0000000000000000, 0x0000000000000000, 0x3ff0000000000000),
+		(0x4100000000000000, 0xbfeff8bd7b10d6b0, 0x3fa58ced65ec8b50),
+	][:]
+
+	for (x, ys, yc) : inputs
+		var xf : flt64 = std.flt64frombits(x)
+		var rsf1, rcf1, rsf2, rcf2
+		(rsf1, rcf1) = math.sincos(xf)
+		rsf2 = math.sin(xf)
+		rcf2 = math.cos(xf)
+
+		var rsu1 = std.flt64bits(rsf1)
+		var rcu1 = std.flt64bits(rcf1)
+		var rsu2 = std.flt64bits(rsf2)
+		var rcu2 = std.flt64bits(rcf2)
+
+
+		testr.check(c, rsf1 == rsf2 && rcf1 == rcf2,
+			"sincos(0x{b=16,w=16,p=0}) is (0x{b=16,w=16,p=0}, 0x{b=16,w=16,p=0}), individual results (0x{b=16,w=16,p=0}, 0x{b=16,w=16,p=0})",
+			x, rsu1, rcu1, rsu2, rcu2)
+
+		testr.check(c, same64(rsu1, ys) && same64(rcu1, yc),
+			"sincos(0x{b=16,w=16,p=0}) should be (0x{b=16,w=16,p=0}, 0x{b=16,w=16,p=0}), was (0x{b=16,w=16,p=0}, 0x{b=16,w=16,p=0})",
+			x, ys, yc, rsu1, rcu1)
+	;;
+}
+
+const sincos03 = {c
+}
+
+const sincos04 = {c
+}
+
+const sincos05 = {c
+}