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在您提供的任何示例中都会出现无效格式字符串错误