Printing floating-point numbers
3rd June 2018
The hardest part of adding floating-point support to my uLisp interpreter for small processors [1] was the routine to print the floating-point numbers.
An initial workaround
After finding an article on the web that warned that writing your own floating-point output routine was a foolhardy enterprise [2] I decided to avoid the problem by getting snprintf() to write the float to a string buffer with:
snprintf(buffer, bufmax, "%g", f);
and then print the string in uLisp.
This worked nicely on the Arduino Due which I was using to test the code, although it added an overhead of over 10Kbytes to my code. However, I discovered that on most other platforms the floating-point support in snprintf() was disabled by default, so this wasn't going to be a solution I could use.
Writing a floating-point printer
I therefore set out to write a floating-point output routine in C. The aim was that it should give the same output as princ in LispWorks Common Lisp, subject to differences in precision; my implementation has 32-bit short floats.
I prototyped the logic in Lisp before coding it in C. It's written in C-like Lisp to make it easy to translate into C once it's working.
After a few false starts I managed to get a reasonably compact version that seems to produce the correct output in all the test cases I've tried. I'd be interested if anyone can find a case that fails:
(defun pmantissa (f) (let* ((sig (floor (log f 10))) (mul (expt 10 (- 5 sig))) (i (round (* f mul))) d point) (when (= i 1000000) (setq i 100000) (incf sig)) (when (< sig 0) (princ "0.") (setq point t) (dotimes (j (1- (- sig))) (princ "0"))) (setq mul 100000) (dotimes (j 7) (setq d (truncate i mul)) (princ (code-char (+ (char-code #\0) d))) (setq i (- i (* d mul))) (when (zerop i) (unless point (dotimes (k (- sig j)) (princ "0")) (princ ".0")) (return)) (when (and (= j sig) (>= sig 0)) (princ ".") (setf point t)) (setq mul (/ mul 10))))) (defun print-float (f) (let ((e 0)) (when (= f 0.0) (princ "0") (return-from print-float)) (when (< f 0) (princ "-") (setq f (- f))) ;; Calculate the exponent (when (or (< f 1e-3) (>= f 1e5)) (setq e (floor (log f 10))) (setq f (/ f (expt 10 e)))) (pmantissa f) ;; Print the exponent (when (/= e 0) (princ "e") (princ e))) (values))
Some examples:
CL-USER > (print-float 12345.6789) 12345.7 CL-USER > (print-float 123456.789) 1.23457e5 CL-USER > (print-float 0.00123456789) 0.00123457 CL-USER > (print-float 0.000123456789) 1.23457e-4 CL-USER > (print-float 0.99999999) 1.0
Update
4th February 2019: Updated the program to fix a bug that caused some exact multiples of 10 to be printed incorrectly.
- ^ uLisp - Lisp for the Arduino, Micro Bit, and MSP430.
- ^ Printing Floating-Point Numbers on RyanJuckett.com.
blog comments powered by Disqus