About me

About me

Feeds

RSS feed

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.


  1. ^ uLisp - Lisp for the Arduino, Micro Bit, and MSP430.
  2. ^ Printing Floating-Point Numbers on RyanJuckett.com.

blog comments powered by Disqus