I'm going to assume that your computer uses IEEE 754 double-precision to store numbers. Write a function for converting between a floating-point number and its bit representation. In Python, you can do:
import struct
def get_bits_from_double(x):
return struct.unpack('=q', struct.pack('=d', x))[0]
def get_double_from_bits(n):
return struct.unpack('=d', struct.pack('=q', n))[0]
Of course, if you want fast calculations, you'll probably be using C instead of an interpreted language like Python. In C, you can use a union to store two numbers at the same memory location.
#include <stdint.h>
typedef union
{
uint64_t bits;
double value;
} DOUBLE_un;
inline uint64_t get_bits_from_double(double x)
{
DOUBLE_un un;
un.value = x;
return un.bits;
}
inline double get_double_from_bits(uint64_4 bits)
{
DOUBLE_un un;
un.bits = bits;
return un.value;
}
Or something like that; I don't have a C compiler handy, so I haven't actually tested it.
Now, recall that a double is stored as a three-part bitfield, in the following order:
- sign (1 bit, 0 = positive / 1 = negative)
- exponent (11 bits)
- significand (52 bits)
Since the square root of a negative number isn't real, I'm going to assume that you're only going to pass positive numbers to the function. Thus, the sign bit will always be zero, and the exponent field will dominate the number. So the bit pattern is like an approximate logarithm. We want to approximate a square root, so let's see what happens when we cut the bit pattern in half.
double halve_bits(double x)
{
uint64_t bits = get_bits_from_double(x)
bits = bits / 2;
return get_double_from_bits(bits)
}
If you evaluate the expression halve_bits(x) / sqrt(x) for various numbers, you'll get a very tiny number on the order of $10^{-154}$. This is because we neglected to count for the bias in the exponent field. We could adjust the numbers by multiplying them by 1e154. But you wanted a fast function, so let's try to apply strength reduction by replacing a floating-point multiplication by an integer addition. Work on the bit pattern directly. And of course, write in C for speed.
double approx_sqrt(double x)
{
uint64_t bits = get_bits_from_double(x)
bits = bits / 2 + 2303426388484757850;
return get_double_from_bits(bits)
}
The magic number on the penultimate line is the bit representation of the number $1.0914553763271334 \times 10^{-154}$. YMMV depending on exactly how you're calculating the error. But AFAICT, this function has a maximum relative error of 3.5%. If you need a more accurate square root, you can use the output of approx_sqrt as the initial guess for an iterative algorithm like Newton's method. But since you explicitly want a rough approximation, this will be fine.
So there you go: A fast approximate square root algorithm done entirely with integer math instead of floating-point. But you'll want to do some timing tests to make sure it's actually faster than the standard sqrt function.
sqrtcall is a significant cost. How many points are you generating? How much time do you have to generate these points? – Cris Luengo Jul 11 '23 at 15:32sqrtfunction is slow, the bottleneck might not be caused by it at all. – Filip Milovanović Jul 11 '23 at 18:06sunflower_on_sphere()algorithm only uses trig and inverse trig. – uhoh Jul 11 '23 at 22:26sqrt()by extracting the floating point exponent (with e.g.ilogb(), which gives you a fast approximation to the base-2 log – Tavian Barnes Jul 12 '23 at 18:59sqrt()will unlikely hold you back, see this simple JS animation with 720ksqrt()calls per frame: https://stackoverflow.com/a/58485681/7916438 - and also see the other one, which is slow as hell because of the change in drawing, not the calculation. If you have performance issues, don't rule out that they're somewhere else. – tevemadar Jul 13 '23 at 12:51