Search code examples
formatcommon-lispcoerce

How to print numbers as floats with a specified format in lisp?


An arbitrary number can be readily converted to float with some number of decimals. For example, using (format nil "~,2f" 6) gives "6.00". But is there a way to directly coerce an arbitrary number using an analogous float type spec; i.e., outputting a number, not a string? Alternately, I've tried (read-from-string (format nil "~,2f" 6)), but this does not retain the formatting.

I would like to print a nested tree containing numbers (along with other lisp objects), as floats with two decimal places. For example, a printout might look like X -> (A 3.00 (7.10 B) (C 8.12) 0.75) regardless of the specific number types represented in X. Is set-pprint-dispatch used for this kind of output or is format sufficient?


Solution

  • Is set-pprint-dispatch used for this kind of output or is format sufficient?

    Let's try with SET-PPRINT-DISPATCH:

    CL-USER> (let ((*print-pprint-dispatch* (copy-pprint-dispatch)))
               (set-pprint-dispatch 'float (lambda (s f) (format s "~,2f" f)))
               (write '(A 3.00 (7.10 B) (C 8.12) 0.75) :pretty t))
    
    (A 3.00 (7.10 B) (C 8.12) 0.75)  ;; standard output
    (A 3.0 (7.1 B) (C 8.12) 0.75)    ;; result printed by the REPL
    

    Since the pretty-print dispatch table is copied before being modified, the function associated with floats is only called from inside the let-binding. When the return value of WRITE is printed to the REPL, the default dispatch table is used. You could define a global variable to avoid recomputing the modified table each time you need it.

    Note that if you don't give a value for :pretty, then the special *PRINT-PRETTY* variable defines whether the pretty-printer is used or not. For details see 22.2.1.4 Pretty Print Dispatch Tables.

    I would like to print a nested tree containing numbers

    If what you want is to output any number as a float, just dispatch on the REAL type (complex numbers with non-zero imaginary parts can't be printed as you want, so I don't recommend dispatching on number). Any necessary coercion will happen implicitly:

    (let ((*print-pprint-dispatch* (copy-pprint-dispatch)))
      (set-pprint-dispatch 'real (lambda (s f) (format s "~,2f" f)))
      (write '(A 3 (7.1d0 B) (C 203/25) #C(3/4 0)) :pretty t))
    

    ... writes:

    (A 3.00 (7.10 B) (C 8.12) 0.75)