Now we come to a set of Lisp functions that are obscure because the operations they perform are fairly esoteric. These functions are decode-float, scale-float, float-radix, float-sign, float-digits, float-precision, and integer-decode-float. Many of these functions have direct analogues in glibc. Note that the glibc functions return values as if the floating-point radix is 2, while the Lisp functions use the true radix, whatever that might be, whose value can be retrieved with float-radix.
- decode-float is almost equivalent to frexp(), frexpf(), and frexpl(), but its first returned value is always positive, and the sign is present in the third returned value, while the glibc function produces a negative value for the normalized fraction if the input is negative.
- scale-float is equivalent to ldexp(), ldexpf(), ldexpl(), or scalb(), scalbf(), and scalbl() in BSD. You would typically use it to reassemble a number you had decoded with decode-float or integer-decode-float after the parameters had been adjusted for your purposes.
- float-radix returns the radix for a particular float value. The POSIX equivalent is the FLT_RADIX macro.
- float-sign behaves like copysign(), but with the arguments reversed.
- float-digits returns the number of base-radix digits in the representation of the floating-point number. It autodetects the type of its argument, and returns appropriate value. The POSIX equivalents are FLT_MANT_DIG for floats, and DBL_MANT_DIG for doubles, and LDLB_MANT_DIG for long doubles.
- float-precision returns the number of significant digits in the argument. For normalized floating-point values, this is the same as float-digits, otherwise it may be lower. There is no direct analogue in glibc, but by using fpclassify() and frexpf(), the programmer can detect subnormal floating-point values and take appropriate action.
- integer-decode-float is much like decode-float, but scales up the normalized fraction to an integer, adjusting the exponent appropriately. There is no analogue in glibc.
If you have not run across these glibc functions and macros, you’re not likely to be interested in their Lisp equivalents. These functions are used for specialized manipulations of floating-point numbers. Some of them are useful to detect the floating-point parameters on a particular platform, possibly altering algorithms or data types to improve behaviour on unusual hardware. Others are more useful during the execution of algorithms.
I have been doing numerical simulations and floating-point work for a long time, but of all these functions, I’ve only had to use frexpf() and ldexpf() in production code. In setting up a mathematical model on a mesh, with user-supplied node coordinates as single-precision floats, some of the calculations to be done would fail badly if two nodes were too close together. The code could handle the nodes being coincident, or reasonably far apart, but when the floating-point representation of a coordinate value of two nodes differed by only a few floating-point quanta, the subsequent calculations would fail. To fix this problem, I wanted to round off the node coordinates on input to some very fine square grid, maybe one spaced 8 floating-point quanta apart, and be able to do this whether the input mesh coordinates were spanning thousands of metres, of thousandths of metres. The solution I used, in C++, looks like this:
float binaryround(float x) { int exponent; float prefactor = frexpf(x, &exponent); prefactor = (int) (prefactor * 1048576 + 0.5); exponent -= 20; return ldexpf(prefactor, exponent); }
The equivalent in Lisp would be this:
(defun binaryround (x) (multiple-value-bind (normalized exp sgn) (integer-decode-float x) (setf normalized (/ (+ 4 normalized) 8)) (* (scale-float (float normalized) (+ 3 exp)) sgn)))
Finally, I’d like to point something out. If you’re starting to do serious floating-point work on computers, you probably need to research some of the subtle tricks and dangers involved. A good source is this: What Every Computer Scientist Should Know About Floating-Point Arithmetic.