Monthly Archives: June 2014

The less-familiar parts of Lisp for beginners — set-macro-character

The set-macro-character function allows the programmer to modify the readtable.  We’ve talked a bit about modifying the readtable before, in the article on get-dispatch-macro-character.  For a simple example of the use of this function, see the earlier article on eval-when.

There’s not much else to mention here, apart from explaining the difference between a terminating and non-terminating macro character.  One of the optional arguments to set-macro-character allows the programmer to declare that the macro character is non-terminating.  A non-terminating macro character that appears in the middle of a token does not end the token, it only has its special readtable behaviour when it appears at the beginning of a token.  A terminating macro character, conversely, cannot appear as part of a token, because it performs its action when encountered, even inside a symbol name.

The less-familiar parts of Lisp for beginners — search

Next in our walk through less commonly covered Lisp features is search.  This isn’t especially mysterious, but it’s good to point it out, so newcomers know it’s there and don’t start writing their own versions of it.  This function locates a subsequence within a sequence.  Most obviously, the programmer can use it to find a substring in a longer string, but note that, with the :key and :test arguments, more general actions are possible.  There are also options to begin the search from someplace other than the beginning of the sequence, or to find the last occurrence, rather than the first occurrence, of the substring.  Here is an example of things that :key and :test can help with:
search.lisp

(defun demo-case-insensitive-search (haystack needle)
  (search needle haystack :key #'char-downcase :test #'char=))

(defun demo-find-n-letter-word (haystack n)
  (let ((pattern (format nil " ~A " 
                         (make-sequence 'string n 
                                        :initial-element #\A))))
    (search pattern haystack :key #'alpha-char-p)))

(defun demo-find-upper-case-of-word (haystack needle)
  (labels
      ((compare-fcn (char-1 char-2)
         (and (upper-case-p char-2)
              (char= (char-upcase char-1) char-2))))
    (search needle haystack :test #'compare-fcn)))

(defun demonstrate ()
  (let ((stringlist (list "alpha"
                          "beta"
                          "gamma"
                          "delta"
                          "epsilon"
                          "ALPHA"
                          "BETA"
                          "GAMMA"
                          "DELTA"
                          "EPSILON"))
        haystack)
    ;; build haystack, separated with blanks, and with leading
    ;; and trailing blanks
    (setf haystack " ")
    (dolist (word stringlist)
      (setf haystack (concatenate 'string haystack word " ")))
    
    (let ((needle "AlPhA"))
      (format t "case-insensitive search~%")
      (format t "searching for ~S in haystack~%" needle)
      (format t "position= ~A~%" 
              (demo-case-insensitive-search haystack 
                                            needle))
      (format t "~%")
      (format t "upper-case search~%")
      (format t "searching for ~S in haystack~%" needle)
      (format t "position= ~A~%" 
              (demo-find-upper-case-of-word haystack 
                                            needle)))

    (format t "~%")
    (format t "Looking for a 7-letter word~%")
    (format t "position= ~A~%" (demo-find-n-letter-word haystack 7))))
      
        

with output:
*slime-repl sbcl*
CL-USER> (demonstrate)
case-insensitive search
searching for "AlPhA" in haystack
position= 1

upper-case search
searching for "AlPhA" in haystack
position= 32

Looking for a 7-letter word
position= 23
NIL

The less-familiar parts of Lisp for beginners — scale-float and related functions

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:
binrnd.c

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:
binrnd.lisp
(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.

The less-familiar parts of Lisp for beginners — row-major-aref

We now come to a fairly useful Lisp feature, but again, one that might not have been covered in a basic introduction to Lisp.  The row-major-aref accessor allows the programmer to reference an element in a multi-dimensional array using only a single index.  For operations that require traversing the entire array, this lets the programmer visit all cells without needing to write nested loops, and even allows the code to operate on arrays of different dimensions.  Here is an example of this in use:
*slime-repl sbcl*

CL-USER> (defun sum-array-elements (array)
           (let ((sum 0.0d0))
             (dotimes (i (array-total-size array))
               (incf sum (row-major-aref array i)))
             sum))
SUM-ARRAY-ELEMENTS
CL-USER> (let ((2d-array (make-array (list 10 10)
                                     :initial-element 2))
               (3d-array (make-array (list 10 10 10)
                                     :initial-element 3)))

           (format t "Sum of elements in 2d-array: ~G~%"
                   (sum-array-elements 2d-array))
           (format t "Sum of elements in 3d-array: ~G~%"
                   (sum-array-elements 3d-array)))
Sum of elements in 2d-array: 200.    
Sum of elements in 3d-array: 3000.    
NIL

The less-familiar parts of Lisp for beginners — rotatef

Another somewhat obscure feature of Lisp is rotatef.  The rotatef function takes as its arguments a set of places (variables or forms that can be assigned to with setf, think l-value from C++).  It then rotates them, so that the value of the second is moved to the first place, the value of the third moved to the second, and so on, with the value of the first place being moved to the last place.

Calling
*slime-repl sbcl*

CL-USER> (rotatef place1 place2 place3)

is almost like calling
*slime-repl sbcl*
CL-USER> (psetf place1 place2
                place2 place3
                place3 place1)

with the caveat that the psetf example will evaluate each place twice, so if there are side-effects, the behaviour may not be as expected.

Finally, here is an example of using rotatef on places:
*slime-repl sbcl*

CL-USER> (let ((list1 (list 1 2 3 4))
               (list2 (list :A :B :C :D))
               (list3 (list "a" "b" "c" "d")))
           (rotatef (cdr list1) (cdr list2) (cdr list3))
           (values list1 list2 list3))
(1 :B :C :D)
(:A "b" "c" "d")
("a" 2 3 4)