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
