我了解使用二进制(或二进制)浮点并以十进制表示结果时的复杂性:
1 (do ((numerator 1 (* 10 numerator)))
2 ((>= numerator 1000000000000))
3 (let ((fred (list 'coerce (/ numerator 3) (quote 'double-float))))
4 (prin1 fred)
5 (princ " ")
6 (prin1 (eval fred))
7 (terpri)))
(COERCE 1/3 'DOUBLE-FLOAT) 0.3333333333333333d0
(COERCE 10/3 'DOUBLE-FLOAT) 3.3333333333333335d0
(COERCE 100/3 'DOUBLE-FLOAT) 33.333333333333336d0
(COERCE 1000/3 'DOUBLE-FLOAT) 333.3333333333333d0
(COERCE 10000/3 'DOUBLE-FLOAT) 3333.3333333333335d0
(COERCE 100000/3 'DOUBLE-FLOAT) 33333.333333333336d0
(COERCE 1000000/3 'DOUBLE-FLOAT) 333333.3333333333d0
(COERCE 10000000/3 'DOUBLE-FLOAT) 3333333.3333333335d0
(COERCE 100000000/3 'DOUBLE-FLOAT) 3.3333333333333332d7
(COERCE 1000000000/3 'DOUBLE-FLOAT) 3.333333333333333d8
(COERCE 10000000000/3 'DOUBLE-FLOAT) 3.3333333333333335d9
(COERCE 100000000000/3 'DOUBLE-FLOAT) 3.3333333333333332d10
但我希望避免最后那个不同数字的不恰当之处。 在这种情况下,计算速度对我来说并不重要。
是否存在真正的十进制浮点 LISP 包?
编辑 1:理想情况下,这个包允许任意精度,就像 bignum 对整数所做的那样。
编辑2,回应Dennis Jaheruddin的问题:
[I]如果您对最后一位数字不感兴趣而只想 如果数字相同,您可能只想观察前 15 个或 那么数字?
我想到了这一点。 这是行不通的。 例如,在 2/3 的情况下,我想要类似 666667 的东西。我看到的是这样的:
1 (do ((numerator 2 (* 10 numerator)))
2 ((>= numerator 1000000000000))
3 (let ((fred (list 'coerce (/ numerator 3) (quote 'double-float))))
4 (prin1 fred)
5 (princ " ")
6 (prin1 (eval fred))
7 (terpri)))
(COERCE 2/3 'DOUBLE-FLOAT) 0.6666666666666666d0
(COERCE 20/3 'DOUBLE-FLOAT) 6.666666666666667d0
(COERCE 200/3 'DOUBLE-FLOAT) 66.66666666666667d0
(COERCE 2000/3 'DOUBLE-FLOAT) 666.6666666666666d0
(COERCE 20000/3 'DOUBLE-FLOAT) 6666.666666666667d0
(COERCE 200000/3 'DOUBLE-FLOAT) 66666.66666666667d0
(COERCE 2000000/3 'DOUBLE-FLOAT) 666666.6666666666d0
(COERCE 20000000/3 'DOUBLE-FLOAT) 6666666.666666667d0
(COERCE 200000000/3 'DOUBLE-FLOAT) 6.6666666666666664d7
(COERCE 2000000000/3 'DOUBLE-FLOAT) 6.666666666666666d8
(COERCE 20000000000/3 'DOUBLE-FLOAT) 6.666666666666667d9
(COERCE 200000000000/3 'DOUBLE-FLOAT) 6.6666666666666664d10
如你所见,我什至无法使用最后一位数字来确定是否四舍五入; 64 四舍五入为 60,而不是 70。但我可以丢弃最后一位数字并使用前一位数字来四舍五入数字的其余部分。 不过,我对此感到不舒服,因为(a)此时我开始放弃很多精度,并且(b)我不确定是否存在这会导致四舍五入错误的情况。 十进制浮点包,最好具有任意精度,将是理想的。
编辑 3:正如 Rainer Joswig 在下面的回答中指出的那样,一个不可移植的潜在解决方案是设置浮点精度。 对于那些在家里跟随的人,他指出here这是这样做的:
(SETF (EXT:LONG-FLOAT-DIGITS) n)
编辑 4:在回答后的评论中,Rainer Joswig 建议研究代数系统 Maxima 和 Axiom。 这样做可以获取这些优秀的维基百科资源:
编辑5:我已经确定我不需要十进制浮点包,但我仍然很好奇是否有一个。 应该没有吧。
为什么我不需要一个? 答案是 (a) Rainer Joswig 指向 wu-decimal 包的指针和 (b) wvxvw 提到的长除法的组合。
虽然 wu-decimal 没有传统的特征和使用基数 10 的尾数,但它确实引入了一个有趣的想法:将数字存储为比率。 因此 1/3 存储为 1/3,而不是重复的(对于有限长度)二进制分数。 尽管以比率形式存储的数字相乘很快就会产生相当长的比率,但始终保持原始精度。 我将使用这个想法。 我不需要的是 wu-decimal 相当漂亮的解析,并在适当的时候将比率写入十进制数字,所以我不会安装该包。 如果您有兴趣轻松解析和编写此类值,请查看该包。 (我没用过。)
剩下的就是将比率打印为十进制数。 为此,我将使用长除法,就像 wvxvw 一样。 我的代码有些不同,但是对于长除法的想法,我非常感谢他。
https://www.reddit.com/r/lisp/comments/7t48mv/common_lisp_floatingpoint_number_accuracy/
他们在这里也讨论了这个问题。但到目前为止,结果并不是我想要的。
我认为 Common Lisp 社区应该构建一个像 Python 的标准包 Decimals 一样的包。为什么我们没有?
一个开始可能如下。 我尝试使用有理数表示。但它导致了相同的浮点错误。
(= 1734.05 (+ 1708.59 25.46)) ;; => NIL but it should be T
(+ 1708.59 25.46) ;; => 1734.0499
我很想在标准
+
、-
、*
、/
上构建方法,就像在 Python 中使用 dunder 方法或在 Ruby 或 Julia 中一样。但在 Common Lisp 中,这些函数似乎无法被覆盖? (或者我只是缺乏知识?)
(defpackage :exact-decimal
(:use :cl)
(:export :decimal :to-string :to-float))
(in-package :exact-decimal)
(defclass decimal ()
((value :initarg :value
:accessor value
:initform 0
:type integer)
(scale :initarg :scale
:accessor scale
:initform 0
:type integer)))
(defun decimalp (obj)
(typep obj 'decimal))
(defun decimal-from-string (decimal-str)
"Creates an exact decimal from a string."
(let* ((parts (cl-ppcre:split "\\." decimal-str))
(integer-part (parse-integer (first parts)))
(fractional-part (if (second parts) (second parts) ""))
(fraction-length (length fractional-part))
(full-value (parse-integer (concatenate 'string (first parts) fractional-part))))
(declare (ignore integer-part))
(make-instance 'decimal :value full-value :scale fraction-length)))
(defun decimal-from-float (float-num)
"Creates an exact decimal from a floating-point number."
(decimal-from-string (format nil "~,f" float-num)))
(defgeneric to-float (number)
(:documentation "Convert NUMBER to a floating-point value."))
(defmethod to-float ((number decimal))
(/ (float (value number)) (expt 10 (scale number))))
(defgeneric to-string (number)
(:documentation "Convert NUMBER to a string representation."))
(defmethod to-string ((number decimal))
(let* ((integer-str (write-to-string (value number)))
(length (length integer-str))
(scale (scale number)))
(if (zerop scale)
integer-str
(let ((point-pos (- length scale)))
(if (<= point-pos 0)
(concatenate 'string "0." (make-string (- point-pos) :initial-element #\0) integer-str)
(concatenate 'string (subseq integer-str 0 point-pos) "." (subseq integer-str point-pos)))))))
(defmethod %+ ((a decimal) (b decimal))
(let* ((scale-diff (- (scale a) (scale b)))
(adjusted-a (if (plusp scale-diff)
(make-instance 'decimal :value (* (value a) (expt 10 (abs scale-diff))) :scale (scale b))
a))
(adjusted-b (if (minusp scale-diff)
(make-instance 'decimal :value (* (value b) (expt 10 (abs scale-diff))) :scale (scale a))
b)))
(make-instance 'decimal
:value (+ (value adjusted-a) (value adjusted-b))
:scale (max (scale a) (scale b)))))
(defmethod %- ((a decimal) (b decimal))
(let* ((scale-diff (- (scale a) (scale b)))
(adjusted-a (if (plusp scale-diff)
(make-instance 'decimal :value (* (value a) (expt 10 (abs scale-diff))) :scale (scale b))
a))
(adjusted-b (if (minusp scale-diff)
(make-instance 'decimal :value (* (value b) (expt 10 (abs scale-diff))) :scale (scale a))
b)))
(make-instance 'decimal
:value (- (value adjusted-a) (value adjusted-b))
:scale (max (scale a) (scale b)))))
(defmethod %* ((a decimal) (b decimal))
(make-instance 'decimal
:value (* (value a) (value b))
:scale (+ (scale a) (scale b))))
(defmethod %/ ((a decimal) (b decimal))
(let* ((result-value (/ (* (value a) (expt 10 (scale b))) (value b)))
(result-scale (scale a)))
(make-instance 'decimal
:value result-value
:scale result-scale)))
(defmethod print-object ((obj decimal) stream)
(print-unreadable-object (obj stream :type t :identity t)
(format stream "~a" (to-string obj))))
;; Example usage:
(let ((d1 (decimal-from-string "1.20"))
(d2 (decimal-from-string "1.30")))
(print (%* d1 d2))
(print (to-float (%* d1 d2)))
(print (to-string (%* d1 d2))))
;; Output: #<DECIMAL 1.5600>
;; 1.56
;; "1.5600"
我想利用 Common Lisp 中的无限大整数表示(也像 Python)。 但有人可能有更好的想法来实现更复杂的表示?以及如何处理此类小数的求幂等?