Improve accuracy of clutter_sinx()

Improve clutter_sinx() by replacing the low precision CFX_SIN_STEP
with a multiply/divide pair. This reduces the maximum error from
1.8e-04 to 2.4e-05.

http://bugzilla.openedhand.com/show_bug.cgi?id=1314

Based on a patch by Owen W. Taylor <otaylor@fishsoup.net>
This commit is contained in:
Emmanuele Bassi 2009-01-22 15:59:23 +00:00
parent 27dd2f8319
commit 7b173b9e24

View File

@ -10,10 +10,12 @@
* *
* Currently contains 257 entries. * Currently contains 257 entries.
* *
* The current error (compared to system sin) is about * The current maximum absolute error is about 1.9e-0.5
* 0.5% for values near the start of the table where the * and is greatest around pi/2 where the second derivative
* curve is steep, but improving rapidly. If this precision * of sin(x) is greatest. If greater accuracy is needed,
* is not enough, we can increase the size of the table * modestly increasing the table size, or maybe using
* quadratic interpolation would drop the interpolation
* error below the precision limits of CoglFixed.
*/ */
static const CoglFixed sin_tbl[] = static const CoglFixed sin_tbl[] =
{ {
@ -270,7 +272,7 @@ static const CoglFixed sqrt_tbl[] =
/* the difference of the angle for two adjacent values in the /* the difference of the angle for two adjacent values in the
* sin_tbl table, expressed as CoglFixed number * sin_tbl table, expressed as CoglFixed number
*/ */
#define COGL_SIN_STEP 0x00000192 static const gint sin_tbl_size = G_N_ELEMENTS (sin_tbl) - 1;
static const double _magic = 68719476736.0 * 1.5; static const double _magic = 68719476736.0 * 1.5;
@ -363,7 +365,9 @@ CoglFixed
cogl_fixed_sin (CoglFixed angle) cogl_fixed_sin (CoglFixed angle)
{ {
int sign = 1, indx1, indx2; int sign = 1, indx1, indx2;
CoglFixed low, high, d1, d2; CoglFixed low, high;
CoglFixed p1, p2;
CoglFixed d1, d2;
/* convert negative angle to positive + sign */ /* convert negative angle to positive + sign */
if ((int) angle < 0) if ((int) angle < 0)
@ -401,14 +405,17 @@ cogl_fixed_sin (CoglFixed angle)
} }
/* Calculate indices of the two nearest values in our table /* Calculate indices of the two nearest values in our table
* and return weighted average * and return weighted average.
*
* We multiple first than divide to preserve precision. Since
* angle is in the first quadrant, angle * SIN_TBL_SIZE (=256)
* can't overflow.
* *
* Handle the end of the table gracefully * Handle the end of the table gracefully
*/ */
indx1 = COGL_FIXED_DIV (angle, COGL_SIN_STEP); indx1 = (angle * sin_tbl_size) / COGL_FIXED_PI_2;
indx1 = COGL_FIXED_TO_INT (indx1);
if (indx1 == (G_N_ELEMENTS (sin_tbl) - 1)) if (indx1 == sin_tbl_size)
{ {
indx2 = indx1; indx2 = indx1;
indx1 = indx2 - 1; indx1 = indx2 - 1;
@ -421,10 +428,13 @@ cogl_fixed_sin (CoglFixed angle)
low = sin_tbl[indx1]; low = sin_tbl[indx1];
high = sin_tbl[indx2]; high = sin_tbl[indx2];
d1 = angle - indx1 * COGL_SIN_STEP; /* Again multiply the divide; no danger of overflow */
d2 = indx2 * COGL_SIN_STEP - angle; p1 = (indx1 * COGL_FIXED_PI_2) / sin_tbl_size;
p2 = (indx2 * COGL_FIXED_PI_2) / sin_tbl_size;
d1 = angle - p1;
d2 = p2 - angle;
angle = ((low * d2 + high * d1) / (COGL_SIN_STEP)); angle = ((low * d2 + high * d1) / (p2 - p1));
if (sign < 0) if (sign < 0)
angle = -angle; angle = -angle;