Friday January 20, 2006
It's been quite a while since my last blog entry: I had a bit of a technology meltdown that I'm not quite done with yet :-(
I was doing some recreational hacking over the holidays that involved evaluating cosines. I ended up doing (once again!) an implementation of cosine (don't ask why). Given the confused flamage that my previous posts on cosine generated, I figure that showing some code would be useful. I would never have thought that cosine would generate so much email traffic. Yes, I know about Taylor series. No I wasn't trying to insult centuries of standard mathematical practice. Performance is often the art of cheating carefully.
So, here's my implementation of cosine, with its argument in turns (1 turn == 360 degrees):
public static float cost(float f) {
int bits = Float.floatToRawIntBits(f);
int mantissa = (bits&0x7FFFFF)|(1<<23);
int shift = (bits<<1>>>24)-127+9; // exponent, unbiased, with shift
if(shift>=32 || shift<=-32) return 1;
int fractionBits = shift>=0 ? mantissa<<shift : mantissa>>-shift;
int tableIndex = (fractionBits>>>(30-resultPrecision))&tableSizeMask;
switch(fractionBits>>>30) { // Quadrant is top two bits
case 0: return cosTable[tableIndex];
case 1: return -cosTable[tableSizeMask-tableIndex];
case 2: return -cosTable[tableIndex];
default/*case 3*/: return cosTable[tableSizeMask-tableIndex];
}
}
Lets go through this slowly:
int bits = Float.floatToRawIntBits(f);
Since this is just a table
lookup, the resulting approximation can be pretty jagged if the table is small. But it's easy to tune the
table size depending on accuracy needs. The smaller the table is, the higher the cache hit rate will
be, and the more likely it is that the whole table will fit in cache.
Table lookups are a very common way to implement mathematical functions, particularly the periodic
ones like cosine. There are all kinds of elaborations. One of the most common for improving accuracy
is to do some sort of interpolation between table elements (linear or cubic, usually).
Permalink
Comments [11]
You can do much better than simple interpolation using these relations:
In particular, if x = T + r, where T is the nearest table entry and r is the remainder, then you can get sin T and cos T by table lookup, and compute sin r and cos r by just one or two terms of Taylor -- or, you can do another table lookup, using a second table with smaller range but finer granularity.Finally, if you've never seen it, you've got to check out CORDIC, a truly brilliant and elegant solution. This was used in *everything* before floating-point became cheap. It's rarely taught anymore, which is a real shame because it's just so damn clever.
Posted by Jeff Bonwick on January 21, 2006 at 03:50 AM PST #
Posted by Alex Lam on January 21, 2006 at 04:08 PM PST #
Posted by James Gosling on January 21, 2006 at 10:02 PM PST #
Posted by sdaf on January 22, 2006 at 05:06 AM PST #
Posted by Alex Lam on January 22, 2006 at 09:04 AM PST #
I'm searching for a good arbitrary-precision arithmetics package for numerical computations with support for trigonometric functions.
Can you suggest one?
Posted by Axel Kramer on January 23, 2006 at 03:59 AM PST #
Posted by nile black on January 23, 2006 at 06:32 AM PST #
Posted by Daniel Steinberg on January 23, 2006 at 07:38 AM PST #
What's so grotesque about that? If every function requires information about the error bounds, then enforcing the desired error limits becomes quite complicated. This is doubly true, while there is no standard way to track and describe error bounds. Perhaps JSR 275 will address that. http://www.jcp.org/en/jsr/detail?id=275
I would think that "The Jackpot Way" to solve this problem would be to provide an editor for complex functions and equations. Then a function evaluation engine can evaluate the entire function, tracking the error along the way. Radically different evaluation methods could be chosen, simply by changing the acceptable error.
Total error is a pain to track, but it is almost always the only important thing. The error involved in any single stage of a calculation is only important to the extent that it impacts total error.
If you think this approach requires too much intelligence from the function evaluation engine to ever compete in terms of performance, would you say the same thing about Java itself?
Posted by Curt Cox on January 23, 2006 at 08:04 AM PST #
Posted by wes on January 24, 2006 at 03:06 AM PST #
Posted by Alex Lam on January 24, 2006 at 05:20 AM PST #