In general lisp how can I format floating point and specify grouping, char group and char decimal separator

Let's say I have a floating point 1234.9

I want to format it as 1.234,90

Is there a format combination for this? ~D , which can handle grouping and char group, processes only integers. ~F does not handle grouping at all. And not one, as far as I know, can change the decimal point from . until

The only solution I see is to use ~D to group the digits of the integer part and combine it with , and the decimal part. Any better ideas?

+5
source share
4 answers

You can define a function that will be called with a tilde-slash, which most of the other answers have already performed, but to get a result similar to ~ F, but with the entered commas of characters and replacing the decimal point, I think it’s best to call the result created by ~ F, and then modify it and write to a string. Here's how to do this using the insert-comma utility, which adds a comma character at regular intervals to the line. Here's the directive function:

 (defun print-float (stream arg colonp atp &optional (point-char #\.) (comma-char #\,) (comma-interval 3)) "A function for printing floating point numbers, with an interface suitable for use with the tilde-slash FORMAT directive. The full form is ~point-char,comma-char,comma-interval/print-float/ The point-char is used in place of the decimal point, and defaults to #\\. If : is specified, then the whole part of the number will be grouped in the same manner as ~D, using COMMA-CHAR and COMMA-INTERVAL. If @ is specified, then the sign is always printed." (let* ((sign (if (minusp arg) "-" (if (and atp (plusp arg)) "+" ""))) (output (format nil "~F" arg)) (point (position #\. output :test 'char=)) (whole (subseq output (if (minusp arg) 1 0) point)) (fractional (subseq output (1+ point)))) (when colonp (setf whole (inject-comma whole comma-char comma-interval))) (format stream "~A~A~C~A" sign whole point-char fractional))) 

Here are some examples:

 (progn ;; with @ (for sign) and : (for grouping) (format t "~',' .2@ :/print-float/ ~%" 12345.6789) ;=> +1.23.45,679 ;; with no @ (no sign) and : (for grouping) (format t "~'.'_3:/print-float/ ~%" 12345.678) ;=> 12_345.678 ;; no @ (but sign, since negative) and : (for grouping) (format t "~'.'_3:/print-float/ ~%" -12345.678) ;=> -12_345.678 ;; no @ (no sign) and no : (no grouping) (format t "~'.' _3@ /print-float/ ~%" 12345.678)) ;=> +12345.678 (no :) 

Here are some examples from coredump-answer that actually helped me catch the error with negative numbers:

 CL-USER> (loop for i in '(1034.34 -223.12 -10.0 10.0 14 324 1020231) do (format t "~','.:/print-float/~%" i)) 1.034,34 -223,12 -10,0 10,0 14,0 324,0 1.020.231,0 NIL 

Here 's a comma insert , with some examples:

 (defun inject-comma (string comma-char comma-interval) (let* ((len (length string)) (offset (mod len comma-interval))) (with-output-to-string (out) (write-string string out :start 0 :end offset) (do ((i offset (+ i comma-interval))) ((>= i len)) (unless (zerop i) (write-char comma-char out)) (write-string string out :start i :end (+ i comma-interval)))))) 
 (inject-comma "1234567" #\, 3) ;;=> "1,234,567" (inject-comma "1234567" #\. 2) ;;=> "1.23.45.67" 
+4
source

As jkiiski comments, you can use the ~/func/ directive.

This is just an example, you can talk more about the function:

 CL-USER> (defun q(stream arg &rest args) (declare (ignore args)) (format stream "~,,'.,:D,~a" (truncate arg) (let ((float-string (format nil "~f" arg))) (subseq float-string (1+ (position #\. float-string)))))) Q CL-USER> (format t "~/q/~%" 1024.36) 1.024,36 NIL CL-USER> (format t "~/q/~%" -1024.36) -1.024,36 NIL 

Edited

In the first version there was round , which is not true, truncate is the right operator to use.

+4
source

Other answers currently use round , which is probably not the intended behavior when rounding (positive numbers) or down (negative numbers). Here is another approach to the ~/custom/ directive, derived mainly from Renzo's answer.

 (defun custom (stream number &rest args) (declare (ignore args)) (multiple-value-bind (integer decimal) (truncate number) (format stream "~,,'.,: D~@ [,~a~]" integer (unless (zerop decimal) (let ((decimal-string (princ-to-string (abs decimal)))) (subseq decimal-string (1+ (position #\. decimal-string)))))))) 

TESTS

 (loop for i in '(1034.34 -223.12 -10.0 10.0 14 324 1020231) collect (custom nil i)) => ("1.034,33996582" "-223,11999512" "-10" "10" "14" "324" "1.020.231") 
+1
source

If you are not opposed to separating the integer and fractional parts, you can do the following:

 (multiple-value-bind (int rest) (floor 1234.56) (let ((rest (round (* rest 1000)))) (format t "~,,'.,:D,~D~%" int rest))) 1.234,560 

Multiplication before rounding indicates how many decimal places you want to print. I’m not sure if this approach is perfect for the automatic control of precision printing, i.e. 1.5 printed as "1.5", not "1500".

0
source

Source: https://habr.com/ru/post/1241483/


All Articles