How to format money with cl-format (common lisp format函数的clojure实现)

How to format money with cl-format (clojure implementation of common lisp format function)

我正在尝试使用 cl-format 格式化货币。我要(f 12345.555) ;=> "12,345.56"。我使用格式字符串 "~$" 获取小数,使用 "~:D" 获取逗号分隔符。如何组合它们?

对于 Common Lisp,我建议使用支持区域设置并定义 ~Ncl-l10n。或者,您可以自己推出:

(defun money (stream number colonp atsignp &optional (decimal-places 2))
  (multiple-value-bind (integral decimal) (truncate number)
    (format stream
            (concatenate 'string
                         "~"
                         (and colonp ":")
                         (and atsignp "@")
                         "D"
                         "~0,vf")
            integral
            decimal-places
            (abs decimal))))

(setf *read-default-float-format* 'double-float)

(format nil "~2:@/money/" 123456789.123456789)
=> "+123,456,789.12"

现在,对于Clojure,cl-format好像还不支持~/,所以不能直接复制上面的代码。使用 Java 库可能更快(参见 this question or this other one)。

部分问题是 ~:d 指令仅在传递整数(无论是浮点数还是整数)时添加逗号,即如果小数点后有除零以外的任何内容,~:d 只是按原样打印出数字。对于 CL 的 format 和 Clojure 的 cl-format 都是如此。

解决方法是将数字拆分为整数和小数,然后分别格式化。一种方法是使用 truncate 函数,据我所知,Clojure 及其标准库均未提供该函数。这是一种方法,使用 clojure.math.numeric-tower 中的 floorceil。 (感谢 coredump 指出我早期版本中的错误。)

(defn truncate [x] 
  (if (neg? x)
    (ceil x)
    (floor x)))

(defn make-money [x]
  (let [int-part (truncate x)
        dec-part (- x int-part)]
    (cl-format nil "~:d~$" int-part dec-part)))

(make-money 123456789.123456789) ;=> "123,456,7890.12"

请注意,这仅适用于正数。 (编辑:正如 Xavi 在评论中指出的那样,这不是解决方案,因为在最后一条评论之后有一个 4 位数的组。)

这回答了 OP 的问题(编辑:不是真的——见上文),但我会注意到在 Common Lisp 中,~$ 的行为略有不同;默认情况下,它在小数点前打印出一个初始零(至少在我尝试过的实现中——不确定这是否标准化)。这可以通过自定义 ~f 指令来避免——它在 Clojure 中也以这种方式工作(有关详细信息,请参阅 Peter Seibel's introduction):

(defun make-money (x)
  (let* ((int-part (truncate x))
         (dec-part (- x int-part)))
    (format nil "~:d~0,2f" int-part dec-part)))

如果数字太大,使用此定义可能会得到意想不到的结果。我确信有一些方法可以通过调整定义来避免这个问题,而且无论如何,正如 Joshua Taylor 的评论所指出的,在 Common Lisp 中还有其他可能更好的方法来做到这一点。