From savicky at cs.cas.cz  Wed Dec 23 23:42:23 2009
From: savicky at cs.cas.cz (Petr Savicky)
Date: Wed, 23 Dec 2009 23:42:23 +0100
Subject: [Rd] choose(n,k) when n is almost integer
In-Reply-To: <20091222215843.GA9120@cs.cas.cz>
References: <19239.19864.396771.484772@lynne.math.ethz.ch>
	<20091215095243.GA14388@cs.cas.cz>
	<19239.38154.219034.206322@lynne.math.ethz.ch>
	<19242.15337.267242.128687@lynne.math.ethz.ch>
	<20091217231449.GA23480@cs.cas.cz>
	<19245.16249.290239.986122@cmath-6.math.ethz.ch>
	<20091222215843.GA9120@cs.cas.cz>
Message-ID: <20091223224223.GA3057@cs.cas.cz>

In a previous email, i suggested two patches A and B to choose(n, k), which
solve some of its problems, but keep some of the inaccuracies of the original
implementation. I would like to suggest another patch, which i will call C
(patch-C.txt in an attachment), which eliminates the warnings obtained sometimes
from the original implementation and which is more accurate in all ranges of
the output.

For testing patch C a simpler script is sufficient, since we need not to take
care of the warnings. Namely
  http://www.cs.cas.cz/~savicky/R-devel/test_choose_1.R
which produces the output (the error is the relative error)

  > source("test_choose_1.R")
  k <= 0 max err = 0 
  k <= 10 max err = 1.332268e-15 
  k <= 20 max err = 2.442491e-15 
  k <= 30 max err = 3.774758e-15 
  k <= 40 max err = 2.553513e-14 
  k <= 50 max err = 2.88658e-14 
  k <= 60 max err = 3.197442e-14 
  k <= 70 max err = 4.396483e-14 
  k <= 80 max err = 4.685141e-14 
  k <= 90 max err = 4.907186e-14 
  k <= 100 max err = 5.084821e-14 
  k <= 110 max err = 5.373479e-14 
  k <= 120 max err = 5.551115e-14 
  k <= 130 max err = 7.41629e-14 
  k <= 140 max err = 9.592327e-14 
  k <= 150 max err = 9.636736e-14 
  k <= 160 max err = 9.725554e-14 
  k <= 170 max err = 9.947598e-14 
  k <= 180 max err = 1.04583e-13 
  k <= 190 max err = 1.088019e-13 
  k <= 200 max err = 1.090239e-13 
  minimum log2() of a wrong result for integer n : 53.32468 
  maximum error for real n : 1.090239e-13 

Increasing accuracy of choose(n, k) for n almost an integer needed to use
additional transformations of it to those already used in the code. I will
work out a description of these transformations and send a link to it.
Similarly as patches A and B, patch C also does not modify lchoose().

It should be pointed out that choose(n, k) for non-integer n is mainly
needed if n is a rational number like 1/2, 1/3, 2/3, .... However, making
choose(n, k) accurate for all inputs seems to be not too complex as the
patch C and its test results show.

I appreciate comments on patch C.

Petr Savicky.

-------------- next part --------------
--- R-devel-orig-intel/src/nmath/choose.c	2009-12-17 17:52:39.000000000 +0100
+++ R-devel-work-copy-3-intel/src/nmath/choose.c	2009-12-23 21:59:40.000000000 +0100
@@ -93,13 +93,14 @@
     return lfastchoose(n, k);
 }
 
+#define IS_INT(x)	  ((x) == floor(x))
 #define k_small_max 30
 /* 30 is somewhat arbitrary: it is on the *safe* side:
  * both speed and precision are clearly improved for k < 30.
 */
 double choose(double n, double k)
 {
-    double r, k0 = k;
+    double r, k0 = k, l, aux;
     k = floor(k + 0.5);
 #ifdef IEEE_754
     /* NaNs propagated correctly */
@@ -109,36 +110,37 @@
 	MATHLIB_WARNING2(_("'k' (%.2f) must be integer, rounded to %.0f"), k0, k);
     if (k < k_small_max) {
 	int j;
-	if(n-k < k && n >= 0 && R_IS_INT(n)) k = n-k; /* <- Symmetry */
+	if(n-k < k && n >= 0 && IS_INT(n)) k = n-k; /* <- Symmetry */
 	if (k <	 0) return 0.;
 	if (k == 0) return 1.;
 	/* else: k >= 1 */
 	r = n;
 	for(j = 2; j <= k; j++)
-	    r *= (n-j+1)/j;
-	return R_IS_INT(n) ? floor(r + 0.5) : r;
+	    r *= (n-(j-1))/j;
+	return IS_INT(n) ? floor(r + 0.5) : r;
 	/* might have got rounding errors */
     }
     /* else: k >= k_small_max */
     if (n < 0) {
-	r = choose(-n+ k-1, k);
-	if (ODD(k)) r = -r;
+	r = n / k * choose(k - 1.0 - n, k - 1.0);
+	if (ODD(k - 1.0)) r = -r;
 	return r;
     }
-    else if (R_IS_INT(n)) {
+    else if (IS_INT(n)) {
 	if(n < k) return 0.;
 	if(n - k < k_small_max) return choose(n, n-k); /* <- Symmetry */
 	return floor(exp(lfastchoose(n, k)) + 0.5);
     }
     /* else non-integer n >= 0 : */
-    if (n < k-1) {
-	int s_choose;
-	r = lfastchoose2(n, k, /* -> */ &s_choose);
-	return s_choose * exp(r);
+    l = floor(n + 0.5);
+    if (l <= k-1) {
+	aux = lfastchoose(n, l) + lfastchoose(k - 1.0 - n, k - l - 1.0) - lfastchoose(k, l);
+	return exp(aux) * (n - l) / (k - l) * (ODD(k - l) ? 1.0 : - 1.0);
     }
     return exp(lfastchoose(n, k));
 }
 
 #undef ODD
+#undef IS_INT
 #undef R_IS_INT
 #undef k_small_max


[Rd] choose(n,k) when n is almost integer
Petr Savicky