我正在编写一个Lisp程序来使用Spigot算法来近似pi,该算法一次找到一个数字而不需要先前计算的任何数字.这个:

假设一次只输入一个十六进制数字,但是测试它我们有连续调用(迭代i)产生Lisp众所周知的分数:
2F/F 6A/333 33D/4CB3 13C/3B79
等等.这是使用带有~x参数的格式函数实现的:
(format t "~x" [some number])
Run Code Online (Sandbox Code Playgroud)
或者,
(format t "~d" [some number])
Run Code Online (Sandbox Code Playgroud)
产生基数10位小数:
3.1333334 0.12942614 0.042220525 0.020755336
但是我想要十六进制的小数,例如,如果一轮产生0.5,那么我想要0.8; 如果基数10是0.75,那么我想要0.C. 这样我就可以分别计算出pi的每个十六进制数字,这是我选择这个Spigot算法的原因.我也可以使用表格的一小部分(n/16 ^ k),因为这可以很容易地转换成十六进制小数.
这可能吗?
~x并且~d是整数的格式化例程,而不是浮点数(强调添加):
22.3.2.2 Tilde D:十进制
arg应该是一个整数,以十进制基数打印.~D永远不会在数字后面加一个小数点.... 如果arg不是整数,则以~A格式和十进制基数打印.
22.3.2.5 Tilde X:十六进制
这就像~D但是以十六进制基数(基数16)而不是十进制打印.
(顺便说一句,我认为~x如果它不是一个整数,实际上有些基础应该打印它的参数的模糊性.它归结为"就像~D但是以十六进制打印"是否会覆盖整数参数,或者所有参数.)
但是这些格式指令究竟在做什么呢?他们对这些的价值具有约束力*print-base*,*print-radix*而且这些文件的文档说它们只会影响有理数的印刷:
变量
*PRINT-BASE*,*PRINT-RADIX*
*print-base*并*print-radix*控制理性的印刷.值*print-base*称为当前输出基数.值
*print-base*是打印机打印有效数据的基数.对于10以上的基数,字母表中的字母用于表示9以上的数字.如果值为
*print-radix*true,则打印机将打印基数说明符,以指示打印有理数的基数.基数说明符始终使用小写字母打印.如果*print-base*是2,8或16,则使用的基数说明符分别是#b,#o或#x.对于整数,十进制数由后面的小数点而不是前导基数说明符表示; 对于比率,使用#10r.
现在,有一些浮点指令format,它们列在22.3.3 FORMAT浮点打印机中.不幸的是,这些都没有做任何不同的基础或基数,所以你将无法用它们打印十六进制.
看起来你最终必须编写自己的输出例程,或在库中找到一个.当然,您可能应该将其实现为可以与〜斜杠一起使用的函数,这样您仍然可以编写如下内容:
(format t "~/hexadecimal/" 1/16)
Run Code Online (Sandbox Code Playgroud)
这实际上并不太难,所以这是一种方式(但我不保证这没有错误).将其扩展为使用用户提供的基础并使其更像其他浮点打印机(例如,+如果@提供了添加正数,并添加支持宽度等).
(in-package #:common-lisp-user)
(defun hexadecimal (stream number colonp atp &rest args)
(declare (ignore colonp atp args))
(when (< number 0)
(write-char #\- stream)
(setq number (- number)))
(multiple-value-bind (quotient remainder) (floor number 1.0)
(format stream "~x." quotient)
(do () ((zerop remainder))
(multiple-value-setq (quotient remainder)
(floor (* 16 remainder)))
(format stream "~x" quotient))))
Run Code Online (Sandbox Code Playgroud)
有了这个,你得到了预期的结果:
(LOOP :FOR NUM :FROM -1/2 :TO 1/2 :BY 1/256
:COLLECT (FORMAT NIL "~/hexadecimal/" NUM))
;=>
("-0.8" "-0.7F" "-0.7E" "-0.7D" "-0.7C" "-0.7B" "-0.7A" "-0.79" "-0.78"
"-0.77" "-0.76" "-0.75" "-0.74" "-0.73" "-0.72" "-0.71" "-0.7" "-0.6F"
...
"-0.1D" "-0.1C" "-0.1B" "-0.1A" "-0.19" "-0.18" "-0.17" "-0.16" "-0.15"
"-0.14" "-0.13" "-0.12" "-0.11" "-0.1" "-0.0F" "-0.0E" "-0.0D" "-0.0C"
"-0.0B" "-0.0A" "-0.09" "-0.08" "-0.07" "-0.06" "-0.05" "-0.04" "-0.03"
"-0.02" "-0.01" "0." "0.01" "0.02" "0.03" "0.04" "0.05" "0.06" "0.07"
"0.08" "0.09" "0.0A" "0.0B" "0.0C" "0.0D" "0.0E" "0.0F" "0.1" "0.11"
...
"0.6C" "0.6D" "0.6E" "0.6F" "0.7" "0.71" "0.72" "0.73" "0.74" "0.75"
"0.76" "0.77" "0.78" "0.79" "0.7A" "0.7B" "0.7C" "0.7D" "0.7E" "0.7F"
"0.8")
Run Code Online (Sandbox Code Playgroud)
事实上,让这更加通用并不难.因为我们可以将参数传递给格式指令,所以我们可以将打印基础作为参数,并且我们可以同时绑定*print-base*它,因此我们可以只在函数体中写入,并且我们可以使用*print-base*:
(in-package #:common-lisp-user)
(defun floating (stream number colonp atp
&optional (*print-base* 10) (num-digits 10)
&rest args)
(declare (ignore colonp args))
;; If the number is negative, print the #\- and invert the number.
;; Otherwise, the number is non-negative, and if an @ was provided
;; we print a leading #\+.
(cond
((minusp number)
(write-char #\- stream)
(setq number (- number)))
(atp
(write-char #\+ stream)))
;; Print number, which is now guaranteed to be positive. Begin by
;; taking its integer part and printing it, followed by a point.
;; Then, pull individual places and write them. This continues,
;; updating quotient and remainder by multiplying the remainder by
;; the base and taking the floor again until either the remainder
;; becomes zero, or we've reached the maximum number of digits.
(multiple-value-bind (quotient remainder) (floor number 1.0)
(write quotient :stream stream)
(write-char #\. stream)
(do ((num-digits num-digits (1- num-digits)))
((or (zerop remainder) (zerop num-digits)))
(multiple-value-setq (quotient remainder)
(floor (* *print-base* remainder)))
(write quotient :stream stream))))
Run Code Online (Sandbox Code Playgroud)
;; 1/2 base 10 is a repeating decimal in base 3.
CL-USER> (format t "~3/floating/" 1/2)
0.1111111111
;; so is -1/2, and asking for more numbers gives them to us
CL-USER> (format t "~3,15/floating/" -1/2)
-0.111111111111111
;; but 1/3 base 10 is non repeating in base 3
CL-USER> (format t "~3,15/floating/" 1/3)
0.1
;; it's non-repeating in base 6, as well
CL-USER> (format t "~6,15/floating/" 1/3)
0.2
;; base 16 still works
CL-USER> (format t "~16/floating/" 189/256)
0.BD
;; and the @ will give us a leading +
CL-USER> (format t "~16@/floating/" 189/256)
+0.BD
Run Code Online (Sandbox Code Playgroud)