Wanted: fast, low-precision trig functions for C

Leo de Wit leo at philmds.UUCP
Sat Mar 11 19:53:18 AEST 1989


In article <1274 at dukeac.UUCP> tcamp at dukeac.UUCP (Ted A. Campbell) writes:
|
|
|Thanks to all who responded to my query about high-speed, low
|precision trig functions for use in graphics applications.  
|I received numerous replies, along the following lines: 
    []
|(c) I have not utilized interpolation here.  If those who understand 
|mathematics can give me a model for an interpolation function, 
|we could incorporate it as a second level between 1 and the slower 
|routines.  

Simple. Given x values x0, x1 (x1 >= x0) and their corresponding y
values f(x0) = y0, f(x1) = y1, linear interpolation between these two
points just says: replace the curve f(x) by the straight line between
(x0,y0) and (x1,y1) for x values in the interval [x0,x1].

       l(x) - y0    y1 - y0
       --------- = ----------
       x - x0       x1 - x0

or

       l(x) = y0 + (x - x0) * (y1 - y0) / (x1 - x0)

       where l(x) is a linear approximation for f(x) on the interval [x0,x1].

Alternatively one can use linear approximation using the derivative (?)
of the functions at hand, since they are easy available: for sin(x) it
is cos(x) and for cos(x) it is -sin(x). This is what I used in the
example below. This is also called a Taylor series:

    f(x) = f(x0) + (x - x0) * f'(x0) + ....

|(d) These routines have been tested with AT&T Unix C compiler (7300) 
|and with the Microsoft QuickC (tm) compiler.  
|
|Thanks again to all who responded.  

I hope you don't mind me critisizing (part of) this code, any criticism
on my code would also be appreciated.

    []
|vpt_init()
|	{	
|	int i;
|	for ( i = 0; i < 91; i++ )
|		{
|		sin_table[ i ] = sin( (double) DEG_RAD * i );
|		}
|	}

Since we're talking approximations here, there is no need to calculate 
all sinuses for your table using sin(). The example I'll give shows how
it can be done.

|double 
|vpt_sin( i )
|	double i;
|	{
|	int sign, target, work;
|	switch( vpt_level )
|		{
|		case 1:
|			work = i;
|			while ( work < 0 )
|				{
|				work += 360;
|				}
|			work = work % 360;

This can take a loooooong time if work (i if you want) is a large
negative number. work == 3600000 for instance would take 10000
iterations. I would suggest in this case work = 360 - (-work % 360).
Note also that for initially negative numbers work = work % 360 is not
needed (because work will already be >= 0 and < 360). Another point
is that C float to int conversion does truncation instead of rounding
(and if I understand the dpANS correctly, is now no longer
implementation defined for negative numbers). So you should preferably
use work = i + 0.5 for positive i and work = i - 0.5 for negative i.

    []
|			else if ( ( work >= 90 ) && ( work < 180 ))
|				{
|				sign = 1;
|				target = 90 - ( work % 90 );
|				}

Since work is >= 90 and < 180, work % 90 will amount to work - 90, which
is perhaps a preferable operation (subtraction is a more basic operation
for many processors), especially since this leads to

				target = 180 - work;

There are many other examples where this could be used in the code.

|			return sign * sin_table[ target ];

I'd suggest you use a boolean for sign, and avoid doing a multiplication:

         return (sign) ? -sin_table[target] : sin_table[target];

And below is how I would do it (perhaps). sintab[] and costab[] I would
probably generate by a C program, thus avoiding having to do the
initialization. In this case however, init_tab() initiates the sin and 
cos tables, using the gonio rules:

	 sin(a+b) = sin(a) * cos(b) + cos(a) * sin(b);
	 cos(a+b) = cos(a) * cos(b) - sin(a) * sin(b);

Although these may lead to cumulative errors, these errors are quite small
in respect to any approximations.
The dsin() and dcos() macros are the simple, table-driven macros, the
macros dsin2() and dcos2() are a first order approximation (the error in
them is typically a factor 100 smaller).
Perhaps dsin2() and dcos2() should better be functions, thus avoiding to
recalculate x % 360.
Be warned! dsin(), dcos, dsin2(), and dcos2() all use their argument
several times, so avoid using side effects or (complex) calculations,
or implement them as functions if you must.

--------- c u t   h e r e ----------
#include <math.h>

/* One degree in radians */
#define ONE_DEG (M_PI / 180)

/* sin, cos for pos. x and neg. x (x in degrees), to be used by the macros
 * that follow.
 */
#define dsinp(x) sintab[(int)((x) + 0.5) % 180]
#define dsinn(x) -sintab[(int)(-(x) + 0.5) % 180]
#define dcosp(x) costab[(int)((x) + 0.5) % 180]
#define dcosn(x) costab[(int)(-(x) + 0.5) % 180]

/* Zero order approximation: simple table lookup (x in degrees) */
#define dsin(x) (((x) >= 0) ? dsinp(x) : dsinn(x))
#define dcos(x) (((x) >= 0) ? dcosp(x) : dcosn(x))

/* First order approximation (x in degrees) */
#define dsin2(x) (((x) >= 0) \
 ? (dsinp(x) + ONE_DEG * ((x) - (int)((x) + 0.5)) * dcosp(x)) \
 : (dsinn(x) + ONE_DEG * ((x) - (int)((x) - 0.5)) * dcosn(x)))
#define dcos2(x) (((x) >= 0) \
 ? (dcosp(x) + ONE_DEG * ((x) - (int)((x) + 0.5)) * dsinp(x)) \
 : (dcosn(x) - ONE_DEG * ((x) - (int)((x) - 0.5)) * dsinn(x)))

static double sintab[180], costab[180];

static void init_tab(), demo();

main()
{
    int i;

    init_tab();
    demo();
}

/* Shows abberation for dsin(), dcos(), dsin2() and dcos2() in the interval
 * [-56,-55]
 */
static void demo()
{
    double d;

    for (d = -55.; d >= -56.; d = d - 0.1) {
        printf("%g: %g %g\n", d,sin(d * ONE_DEG) - dsin(d),
                                cos(d * ONE_DEG) - dcos(d));
        printf("%g: %g %g\n", d,sin(d * ONE_DEG) - dsin2(d),
                                cos(d * ONE_DEG) - dcos2(d));
    }
}

/* This builds sintab,costab with errors of magnitude e-16, which can
 * be ignored without problem (the approximations introduce much
 * larger errors).
 */
static void init_tab()
{
    int i;
    double sin1 = sin(ONE_DEG), cos1 = cos(ONE_DEG);

    sintab[0] = 0.;
    costab[0] = 1.;

    for (i = 1; i <= 45; i++) {
        sintab[i]       = sintab[180 - i] = costab[90 - i]
                        = cos1 * sintab[i - 1] + sin1 * costab[i - 1];
        costab[90 + i]  = -sintab[i];
        costab[i]       = sintab[90 + i] = sintab[90 - i]
                        = cos1 * costab[i - 1] - sin1 * sintab[i - 1];
        costab[180 - i] = -costab[i];
    }
}
------- e n d s   h e r e ----------

	 Leo.

B.T.W. News had objections against some of the newsgroups in the list,
so I had to strip it a bit to get it out. Well ...
(Newsgroups was: misc.wanted,comp.lang.c,triangle.general,micro.general,triangle.graphics,micro.ibm)



More information about the Comp.lang.c mailing list