Common lisp 在common lisp中,如何格式化浮点并指定分组、分组字符和小数分隔符字符

Common lisp 在common lisp中,如何格式化浮点并指定分组、分组字符和小数分隔符字符,common-lisp,Common Lisp,假设我有一个浮点数1234.9 我想将其格式化为1.234,90 是否有格式指令组合~D,它可以处理分组和组字符,只处理整数~F根本不处理分组。据我所知,没有人能将小数点从改为, 我看到的唯一解决方案是对整数部分数字分组使用~D,并将其与、和小数部分连接起来。有更好的想法吗?正如jkiiski的评论所建议的,您可以使用该指令 这只是一个示例,您可以使用该函数进行详细说明: CL-USER> (defun q(stream arg &rest args) (de

假设我有一个浮点数
1234.9

我想将其格式化为
1.234,90

是否有格式指令组合<代码>~D,它可以处理分组和组字符,只处理整数
~F
根本不处理分组。据我所知,没有人能将小数点从
改为


我看到的唯一解决方案是对整数部分数字分组使用
~D
,并将其与
和小数部分连接起来。有更好的想法吗?

正如jkiiski的评论所建议的,您可以使用该指令

这只是一个示例,您可以使用该函数进行详细说明:

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
(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)))
编辑


第一个版本使用了
round
,这是错误的,
truncate
是正确的运算符。

如果您不介意拆分整数和小数部分,可以执行以下操作:

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

1.234,560

取整前的乘法表示要打印逗号后的位数。不确定这种方法是否能很好地自动控制精确打印,即
1.5
打印为“1,5”而不是“1500”。

其他答案目前使用
舍入
,这可能不是向上舍入(正数)或向下舍入(负数)时的预期行为。下面是另一个
~/custom/
指令的方法,主要是从Renzo的答案中派生出来的

(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))))))))
测验
您可以定义一个要用波浪斜杠调用的函数,其他大多数答案都已经这样做了,但为了得到类似于~F的输出,但要插入逗号字符,并替换小数点,我认为最好调用由~F生成的输出,然后修改它并将其写入字符串。这里有一种方法可以做到这一点,使用一个实用工具注入逗号,以指定的间隔向字符串添加一个逗号字符。下面是指令函数:

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
(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)))
以下是一些例子:

(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 :)
(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))))))
以下是中的示例,它们实际上帮助我捕获了一个带有负数的bug:

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
下面是插入逗号,并附有一些示例:

(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 :)
(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))))))

我认为最好的方法是编写您自己的函数,可以与指令一起使用。它甚至可能不是一些正数的预期行为。例如,
(第1024.56轮)
以多个值的形式返回1025和-0.4399414。@JoshuaTaylor是的,他说,我开始采用与您使用的方法相同的方法,并遇到与您生成的行为相同的行为:
(格式t”~F“1034.34)
生成
1034.34
,因此,如果我们打算进行替换,建议的解决方案可能会产生
1.034,34
,而不是
1.03433996582
。@JoshuaTaylor我无法像你说的那样格式化小数部分,但我现在没有太多时间进行测试。如果您有其他打印方法,请随意编辑或评论。我认为从
~F
中分割结果并根据需要进行更新可能是最好的方法;我补充道,由于地板的语义,这不会一直起作用。例如,(楼层125.56)=>125,0.55999756,因此第二个~D可能会产生意外的结果。@JoshuaTaylor我看到了问题,但我认为
会解决它。对于
125.56
它仍将生成“125560”。只有当你尝试输入过多的数字时,它才会断开。但是,它会因负数而中断,在这种情况下,可以使用
上限
(具有相同的限制)。所以,它不会那么简单…我认为这将是一个更好的选择,因为它总是朝着零。但最终还是会得到两个独立的数字,小数部分可能与小数点后的原始数字“不同”,这是行不通的。例如,
(格式t“~/q/~%”1034.56)=>1.035,56
,比输入值大1。更正,谢谢,我通常在
圆形
地板
天花板
截断
中选择了错误的函数,这里有一些可能的改进:因为有相当多的字符串处理,一些字符串可以预先分配。例如,在注入逗号中,我们可以提前计算输出字符串的长度,并且可以提前计算。类似地,我们可以简单地将字符串连接起来,然后将字符串写入流,而不是第二次使用格式来连接字符串。如果有人觉得有必要,我可以实现它们,但我认为这会使代码不那么清晰,不便于随意阅读。这适用于sbcl,但不适用于Clozure Common Lisp,知道为什么吗?Clozure中的Bug?@Joshua Taylor在您提供的任何示例中都会出现无效格式字符串错误