跳转到内容

newLISP 入门/数字操作

来自维基教科书,开放的书籍,开放的世界

数字操作

[编辑 | 编辑源代码]

如果你处理数字,你会很高兴地知道 newLISP 包含了你预期的大多数基本函数,以及更多。本节旨在帮助你充分利用它们,并避免你可能遇到的某些小陷阱。如往常一样,请参阅官方文档以获取完整信息。

整数和浮点数

[编辑 | 编辑源代码]

newLISP 处理两种不同的数字类型:整数和浮点数。整数是精确的,而浮点数(**float**)精度较低。两者各有优缺点。如果你需要使用非常大的整数,大于 9 223 372 036 854 775 807,请参阅介绍大整数(64 位)和任意精度整数(大小不受限制)之间差异的部分 - 更大的数字

算术运算符 **+**、**-**、**\***、**/**、**%** 始终返回整数值。一个常见的错误是忘记这一点,并且使用 **/** 和 **\***,而没有意识到它们正在执行整数运算。

(/ 10 3)
;-> 3

这可能不是你所期望的!

浮点数只保留最重要的 15 或 16 位数字(即数字左侧的数字,具有最高位值)。

浮点数的理念是 **足够接近**,而不是 **精确值**。

假设你尝试定义一个符号 PI 来存储 pi 的值,保留 50 位小数。

(constant 'PI 3.14159265358979323846264338327950288419716939937510)
;-> 3.141592654

(println PI)
3.141592654

看起来 newLISP 截断了大约 40 位数字!实际上,大约存储了 15 或 16 位数字,并且丢弃了 35 个不那么重要的数字。

newLISP 如何存储此数字?让我们使用 **format** 函数看一下。

(format {%1.50f} PI)
;-> "3.14159265358979311599796346854418516159057617187500"

现在让我们做一个小的脚本,将这两个数字作为字符串进行比较,这样我们就不必 *视觉上 grep* 差异。

(setq original-pi-str "3.14159265358979323846264338327950288419716939937510")
(setq pi (float original-pi-str))
(setq saved-pi-str (format {%1.50f} pi))

(println pi " -> saved pi (float)")
(println saved-pi-str " -> saved pi formatted")
(println original-pi-str " -> original pi")

(dotimes (i (length original-pi-str) (!= (original-pi-str i) (saved-pi-str i)))
	(print (original-pi-str i)))

(println " -> original and saved versions are equal up to this")
3.141592654 -> saved pi (float)
3.14159265358979311599796346854418516159057617187500 -> saved pi formatted
3.14159265358979323846264338327950288419716939937510 -> original pi
3.141592653589793 -> original and saved versions are equal up to this

注意,该值在 **9793** 之前是准确的,但随后偏离了你最初提供的更精确的字符串。9793 之后的数字是所有计算机存储浮点值的方式的典型代表 - 这不是 newLISP 对你的数据进行创造性的处理!

你能使用的最大浮点数似乎是 - 至少在我的机器上 - 大约是 10308。但是,只有前 15 位左右的数字被存储,所以大部分都是零,你不能真正给它加 1。

浮点数座右铭的另一个例子:**足够接近**!

顺便说一下,以上评论适用于大多数计算机语言,不仅仅是 newLISP。浮点数是方便、速度和准确性之间的折衷方案。

整数和浮点数运算

[编辑 | 编辑源代码]

当你使用浮点数时,使用浮点算术运算符 **add**、**sub**、**mul**、**div** 和 **mod**,而不是 **+**、**-**、**\***、**/** 和 **%**,它们的整数专用等效项。

(mul PI 2)
;-> 6.283185307

并且,要查看 newLISP 存储的值(因为解释器的默认输出分辨率是 9 或 10 位数字)。

(format {%1.16f} (mul PI 2))
;-> "6.2831853071795862"

如果你忘记使用 **mul**,而使用 **\*** 代替,小数点后的数字将被丢弃。

(format {%1.16f} (* PI 2))
;-> "6.0000000000000000"

这里,pi 被转换为 3,然后乘以 2。

你可以重新定义熟悉的算术运算符,使它们默认使用浮点例程,而不是整数专用运算。

; before
(+ 1.1 1.1)
;-> 2

(constant (global '+) add)

; after
(+ 1.1 1.1)
;-> 2.2

你可以在你的 init.lsp 文件中放置这些定义,以便在你的机器上执行所有 newLISP 工作时使用它们。你将遇到的主要问题是与他人共享代码或使用导入的库时。他们的代码可能会产生令人惊讶的结果,或者你的代码也可能会产生令人惊讶的结果!

转换:显式和隐式

[编辑 | 编辑源代码]

要将字符串转换为数字,或将一种类型的数字转换为另一种类型,请使用 **int** 和 **float** 函数。

它们的主要用途是将字符串转换为数字 - 整数或浮点数。例如,你可能正在使用正则表达式从较长的字符串中提取数字字符串。

(map int (find-all {\d+} {the answer is 42, not 41}))
;-> (42 41)                             ; a list of integers

(map float (find-all {\d+(\.\d+)?} {the value of pi is 3.14, not 1.618}))
;-> (3.14 1.618)                        ; a list of floats

传递给 **int** 的第二个参数指定了一个默认值,如果转换失败,则应使用该值。

(int "x")
;-> nil
(int "x" 0)
;-> 0

**int** 是一个聪明的函数,它还可以将表示数字的字符串(以 10 以外的进制表示)转换为数字。例如,要将字符串形式的十六进制数字转换为十进制数字,请确保它以 *0x* 为前缀,并且不要使用 *f* 之后的字母。

(int (string "0x" "1F"))
;-> 31
(int (string "0x" "decaff"))
;-> 14600959

你可以通过在八进制数字字符串前添加 *0* 来转换包含八进制数字的字符串。

(int "035")
;-> 29

可以通过在二进制数字字符串前添加 *0b* 来转换二进制数字。

(int "0b100100100101001001000000000000000000000010100100")
;-> 160881958715556

即使你从未使用过八进制或十六进制,了解这些转换也很有价值,因为你可能有一天会使用它们,无论是故意还是意外地编写以下代码:

(int "08")

它计算为 0 而不是 8 - 这是一个失败的八进制到十进制的转换,而不是你可能期望的十进制 8!因此,在使用 **int** 对字符串输入进行转换时,始终最好指定默认值和进制。

(int "08" 0 10)                         ; default to 0 and assume base 10
;-> 8

如果你使用的是大整数(大于 64 位整数的整数),请使用 **bigint** 而不是 **int**。请参阅 更大的数字

隐式转换和舍入

[编辑 | 编辑源代码]

有些函数会自动将浮点数转换为整数。从 newLISP 10.2.0 版本开始,所有由字母组成的运算符都会生成浮点数,而用特殊字符编写的运算符会生成整数。

因此,使用 **++** 会将你的数字转换为整数并舍入,而使用 **inc** 会将你的数字转换为浮点数。

(setq an-integer 2)
;-> 2
(float? an-integer)
;-> nil
(inc an-integer)
;-> 3
(float? an-integer)
;-> true
(setq a-float (sqrt 2))
;-> 1.414213562
(integer? a-float)
;-> nil
(++ a-float)
;-> 2
(integer? a-float)
;-> true

要使 **inc** 和 **dec** 在列表上工作,你需要访问特定元素或使用 map 处理所有元素。

(setq numbers '(2 6 9 12))
;-> (2 6 9 12)
(inc (numbers 0))
;-> 3
numbers
;-> (3 6 9 12)
(map inc numbers)
;-> (4 7 10 13)
; but WATCH OUT!
(map (curry inc 3) numbers) ; this one doesn't produce what you expected
;-> (6 12 21 33)
; use this instead:
(map (curry + 3) numbers)
;-> (6 9 12 15)

许多 newLISP 函数会自动将整数参数转换为浮点值。这通常不是问题。但是,如果你将非常大的整数传递给转换为浮点的函数,可能会损失一些精度。

(format {%15.15f} (add 1 922337203685477580))
;-> "922337203685477632.000000000000000"

因为 **add** 函数将非常大的整数转换为浮点数,所以损失了一小部分精度(在本例中约为 52)。足够接近?如果不是,请仔细考虑如何存储和操作数字。

数字测试

[编辑 | 编辑源代码]

有时你可能需要测试一个数字是否是整数或浮点数。

(set 'PI 3.141592653589793)
;-> 3.141592654

(integer? PI)
;-> nil

(float? PI)
;-> true

(number? PI)
;-> true

(zero? PI)
;-> nil

使用 **integer?** 和 **float?**,你是在测试数字是存储为整数还是浮点数,而不是测试数字在数学上是否为整数或浮点值。例如,此测试返回 **nil**,这可能会让你感到惊讶。

(integer? (div 30 3))
;-> nil

这不是因为答案不是 10(它是),而是因为答案是浮点 10,而不是整数 10,因为 **div** 函数始终返回浮点值。

绝对值,从地板到天花板

[编辑 | 编辑源代码]

值得注意的是,floorceil 函数返回包含整数值的浮点数。例如,如果您使用 floor 将圆周率向下舍入到最接近的整数,结果为 3,但它存储为浮点数而不是整数。

(integer? (floor PI))
;-> nil

(floor PI)
;-> 3

(float? (ceil PI))
;-> true

abssgn 函数也可以在测试和转换数字时使用。abs 始终返回其参数的正版本,而 sgn 返回 1、0 或 -1,具体取决于参数是正、零还是负。

round 函数将数字舍入到最接近的整数,浮点数保持为浮点数。您还可以提供一个可选的附加值,将数字舍入到特定的小数位数。负数在小数点后舍入,正数在小数点前舍入。

(set 'n 1234.6789)
(for (i -6 6)
 (println (format {%4d %12.5f} i (round n i))))
  -6   1234.67890
  -5   1234.67890
  -4   1234.67890
  -3   1234.67900
  -2   1234.68000
  -1   1234.70000
   0   1235.00000
   1   1230.00000
   2   1200.00000
   3   1000.00000
   4      0.00000
   5      0.00000
   6      0.00000


sgn 具有另一种语法,允许您根据第一个参数是负、零还是正来评估最多三个不同的表达式。

(for (i -5 5) 
	(println i " is " (sgn i "below 0" "0" "above 0")))
-5 is below 0
-4 is below 0
-3 is below 0
-2 is below 0
-1 is below 0
0 is 0
1 is above 0
2 is above 0
3 is above 0
4 is above 0
5 is above 0

数字格式化

[编辑 | 编辑源代码]

要将数字转换为字符串,请使用 stringformat 函数。

(reverse (string PI))
;-> "456395141.3"

stringprintln 都只使用前 10 位左右的数字,即使内部存储了更多(最多 15 或 16 位)。

使用 format 以更多控制输出数字。

(format {%1.15f} PI)
;-> "3.141592653589793"

format 规范字符串使用广泛采用的 printf 样式格式化。请记住,您也可以使用 format 函数的结果。

(string "the value of pi is " (format {%1.15f} PI))
;-> "the value of pi is 3.141592653589793"

format 函数允许您将数字输出为十六进制字符串。

(format "%x" 65535)
;-> "ffff"

数字实用程序

[编辑 | 编辑源代码]

创建数字

[编辑 | 编辑源代码]

有一些有用的函数可以轻松创建数字。

序列和级数

[编辑 | 编辑源代码]

sequence 生成算术序列中的数字列表。提供起始和结束数字(包含在内)以及步长值。

(sequence 1 10 1.5)
;-> (1 2.5 4 5.5 7 8.5 10)

如果指定步长值,则所有数字都存储为浮点数,即使结果是整数,否则它们是整数。

; with step value sequence gives floats
(sequence 1 10 2)
;-> (1 3 5 7 9)
(map float? (sequence 1 10 2))
;-> (true true true true true)
; without step value sequence gives integers
(sequence 1 5)
;-> (1 2 3 4 5)
> (map float? (sequence 1 5))
;-> (nil nil nil nil nil)

series 将其第一个参数乘以其第二个参数多次。重复次数由第三个参数指定。这会生成几何序列。

(series 1 2 20)
;-> (1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288)

每个数字都存储为浮点数。

series 的第二个参数也可以是函数。该函数应用于第一个数字,然后应用于结果,然后应用于该结果,依此类推。

(series 10 sqrt 20)
;-> (10 3.16227766 1.77827941 1.333521432 1.154781985 1.074607828 1.036632928
1.018151722 1.009035045 1.004507364 1.002251148 1.001124941 1.000562313
1.000281117 1.000140549 1.000070272 1.000035135 1.000017567 1.00000878
1.000004392)

normal 函数返回具有指定平均值和标准差的浮点数列表。例如,可以生成具有 10 的平均值和 5 的标准差的 6 个数字的列表,如下所示。

(normal 10 5 6)
;-> (6.5234375 14.91210938 6.748046875 3.540039062 4.94140625 7.1484375)

随机数

[编辑 | 编辑源代码]

rand 创建小于您提供的数字的随机选择的整数列表。

(rand 7 20)                             
; 20 numbers between 0 and 6 (inclusive) or 7 (exclusive)
;-> (0 0 2 6 6 6 2 1 1 1 6 2 0 6 0 5 2 4 4 3)

显然,(rand 1) 生成一个零列表,没有用处。(rand 0) 也不做任何有用的事情,但它被分配了初始化随机数生成器的任务。

如果您省略第二个数字,它只会在范围内生成一个随机数。

random 生成一个乘以比例因子的浮点数列表,从第一个参数开始。

(random 0 2 10)                         
; 10 numbers starting at 0 and scaled by 2 
;-> (1.565273852e-05 0.2630755763 1.511210644 0.9173002638
; 1.065534475 0.4379183727 0.09408923243 1.357729434
; 1.358592812 1.869385792))

随机性

[编辑 | 编辑源代码]

使用 seed 来控制 rand(整数)、random(浮点数)、randomize(随机排列的列表)和 amb(随机选择的列表元素)的随机性。

如果您不使用 seed,每次运行脚本时都会出现相同的随机数集。这为您提供可预测的随机性,对于调试很有用。当您想模拟现实世界的随机性时,每次运行脚本时使用不同的值对随机数生成器进行播种。

不使用 seed

; today
(for (i 10 20)
 (print (rand i) { }))
7 1 5 10 6 2 8 0 17 18 0
; tomorrow
(for (i 10 20)
 (print (rand i) { }))
7 1 5 10 6 2 8 0 17 18 0              ; same as yesterday

使用 seed

; today
(seed (date-value)) 
(for (i 10 20)
 (print (rand i) { }))
2 10 3 10 1 11 8 13 6 4 0
; tomorrow
(seed (date-value)) 
(for (i 10 20)
 (print (rand i) { }))
0 7 10 5 5 8 10 16 3 1 9

通用数字工具

[编辑 | 编辑源代码]

minmax 按预期工作,尽管它们始终返回浮点数。与许多算术运算符一样,您可以提供多个值。

(max 1 2 13.2 4 2 1 4 3 2 1 0.2)
;-> 13.2
(min -1 2 17 4 2 1 43 -20 1.1 0.2)
;-> -20
(float? (max 1 2 3))
;-> true

比较函数允许您只提供一个参数。如果您将它们与数字一起使用,newLISP 会有帮助地假设您正在与 0 进行比较。请记住,您正在使用后缀表示法。

(set 'n 3)
(> n)
;-> true, assumes test for greater than 0
(< n)
;-> nil, assumes test for less than 0

(set 'n 0)
(>= n)
;-> true

factor 函数找到整数的因子并将其以列表形式返回。这是测试数字以查看它是否是素数的一种有用方法。

(factor 5)
;-> (5)

(factor 42)
;-> (2 3 7)

(define (prime? n)
 (and 
   (set 'lst (factor n))
   (= (length lst) 1)))
   
(for (i 0 30) 
 (if (prime? i) (println i)))
2
3
5
7
11
13
17
19
23
29


或者您可以使用它来测试数字是否为偶数。

(true? (find 2 (factor n)))
;-> true if n is even

gcd 查找精确地除以两个或多个数字的最大整数。

(gcd 8 12 16)
;-> 4

浮点数实用程序

[编辑 | 编辑源代码]

如果省略,pow 函数的第二个参数默认为 2。

(pow 2)                                 ; default is squared
;-> 4

(pow 2 2 2 2)                           ; (((2 squared) squared) squared)
;-> 256

(pow 2 8)                               ; 2 to the 8
;-> 256

(pow 2 3)
;-> 8

(pow 2 0.5)                             ; square root
;-> 1.414213562

您还可以使用 sqrt 来查找平方根。要查找立方根和其他根,请使用 pow

(pow 8 (div 1 3))                       ; 8 to the 1/3
;-> 2

exp 函数计算 ex,其中 e 是数学常数 2.718281828,而 x 是参数。

(exp 1)
;-> 2.71828128

log 函数有两种形式。如果您省略基数,则使用自然对数。

(log 3)                                 ; natural (base e) logarithms
;-> 1.098612289

或者您可以指定其他基数,例如 2 或 10。

(log 3 2)
;-> 1.584962501

(log 3 10)                              ; logarithm base 10
;-> 0.4771212547

newLISP 中默认提供的其他数学函数是 fft(快速傅里叶变换)和 ifft(逆快速傅里叶变换)。

三角学

[编辑 | 编辑源代码]

newLISP 的所有三角函数 sincostanasinacosatanatan2 以及双曲函数 sinhcoshtanh 都在弧度制下工作。如果您喜欢以度数为单位工作,您可以将替代版本定义为函数。

(constant 'PI 3.141592653589793)

(define (rad->deg r)
 (mul r (div 180 PI)))

(define (deg->rad d)
 (mul d (div PI 180)))

(define (sind _e)
 (sin (deg->rad (eval _e))))

(define (cosd _e)
 (cos (deg->rad (eval _e))))  

(define (tand _e)
 (tan (deg->rad (eval _e))))  

(define (asind _e)
 (rad->deg (asin (eval _e))))
 
(define (atan2d _e _f)
 (rad->deg (atan2 (deg->rad (eval _e)) (deg->rad (eval _f)))))

等等。

在编写等式时,一种方法是从末尾开始构建它们。例如,要将以下等式转换

分阶段构建它,像这样。

1                                  (tand beta)
2                                  (tand beta) (sind epsilon)
3                             (mul (tand beta) (sind epsilon))
4 (sind lamda)                (mul (tand beta) (sind epsilon))
5 (sind lamda) (cosd epsilon) (mul (tand beta) (sind epsilon))
6 (sub (mul (sind lamda) (cosd epsilon))  
                              (mul (tand beta) (sind epsilon)))
7 (atan2d (sub (mul (sind lamda) (cosd epsilon)) (mul (tand beta)(sind epsilon)))
          (cosd lamda))
8 (set 'alpha

等等...

在文本编辑器中将各种表达式对齐通常很有用。

(set 'right-ascension
 (atan2d
   (sub
      (mul
        (sind lamda)
        (cosd epsilon))
      (mul
        (tand beta)
        (sind epsilon)))
   (cosd lamda)))

如果您必须将大量数学表达式从中缀转换为后缀表示法,您可能想调查 infix.lsp 模块(可从 newLISP 网站获得)。

(load "/usr/share/newlisp/modules/infix.lsp")
(INFIX:xlate 
 "(sin(lamda) * cos(epsilon)) - (cos(beta) * sin(epsilon))")
;->
(sub (mul (sin lamda) (cos epsilon)) (mul (tan beta) (sin epsilon)))

newLISP 提供多维数组。数组与列表非常相似,您也可以对数组使用大多数操作列表的函数。

大型数组可能比类似大小的列表更快。以下代码使用 time 函数来比较数组和列表的工作速度。

(for (size 200 1000)
 ; create an array
 (set 'arry (array size (randomize (sequence 0 size))))
 ; create a list
 (set 'lst (randomize (sequence 0 size)))
 
 (set 'array-time 
 (time (dotimes (x (/ size 2)) 
  (nth x arry)) 100))
  ; repeat at least 100 times to get non-zero time!
 (set 'list-time
 (time (dotimes (x (/ size 2))
  (nth x lst)) 50))

 (println "with " size " elements: array access: " 
   array-time 
   "; list access: " 
   list-time 
   " " 
   (div list-time array-time )))
with 200 elements: array access: 1; list access: 1 1
with 201 elements: array access: 1; list access: 1 1
with 202 elements: array access: 1; list access: 1 1
with 203 elements: array access: 1; list access: 1 1
...
with 997 elements: array access: 7; list access: 16 2.285714286
with 998 elements: array access: 7; list access: 17 2.428571429
with 999 elements: array access: 7; list access: 17 2.428571429
with 1000 elements: array access: 7; list access: 17 2.428571429


确切时间因机器而异,但通常情况下,对于 200 个元素,数组和列表的速度相当。随着列表和数组大小的增加,nth 访问器函数的执行时间也会增加。当列表和数组分别包含 1000 个元素时,数组访问速度比列表快 2 到 3 倍。

要创建一个数组,请使用 array 函数。您可以创建一个新的空数组,创建一个新的数组并用默认值填充它,或者创建一个新的数组,它是现有列表的精确副本。

(set 'table (array 10))                 ; new empty array
(set 'lst (randomize (sequence 0 20)))  ; new full list
(set 'arry (array (length lst) lst))    ; new array copy of a list

要创建一个新的列表,它是现有数组的副本,请使用 array-list 函数。

(set 'lst2 (array-list arry))           ; makes new list

要区分列表和数组,您可以使用 list?array? 测试。

(array? arry)     
;-> true
(list? lst)
;-> true

可用于数组的函数

[编辑 | 编辑源代码]

以下通用函数在数组和列表上都同样有效:firstlastrestmatnthsetfsortappendslice

还有一些用于数组和列表的特殊函数,它们提供矩阵运算:invertdetmultiplytranspose。请参阅 矩阵

数组可以是多维的。例如,要创建一个 2x2 的表格,填充 0,可以使用以下代码

(set 'arry (array 2 2 '(0)))
;-> ((0 0) (0 0))

array 的第三个参数提供一些初始值,newLISP 将使用这些值来填充数组。newLISP 尽可能有效地使用这个值。因此,例如,你可以提供一个足够多的初始化表达式

(set 'arry (array 2 2 (sequence 0 10)))
arry
;-> ((0 1) (2 3))                       ; don't need all of them

或者只提供一个或两个提示

(set 'arry (array 2 2 (list 1 2)))
arry
;-> ((1 2) (1 2))

(set 'arry (array 2 2 '(42)))
arry
;-> ((42 42) (42 42))

这个数组初始化功能很酷,所以我有时即使在创建列表时也会使用它

(set 'maze (array-list (array 10 10 (randomize (sequence 0 10)))))
;-> ((9 4 0 2 10 6 7 1 8 5)
 (3 9 4 0 2 10 6 7 1 8)
 (5 3 9 4 0 2 10 6 7 1)
 (8 5 3 9 4 0 2 10 6 7)
 (1 8 5 3 9 4 0 2 10 6)
 (7 1 8 5 3 9 4 0 2 10)
 (6 7 1 8 5 3 9 4 0 2)
 (10 6 7 1 8 5 3 9 4 0)
 (2 10 6 7 1 8 5 3 9 4)
 (0 2 10 6 7 1 8 5 3 9))

获取和设置值

[编辑 | 编辑源代码]

要从数组中获取值,请使用 nth 函数,该函数需要一个索引列表,用于表示数组的维度,后面跟着数组的名称

(set 'size 10)
(set 'table (array size size (sequence 0 (pow size))))

(dotimes (row size)
   (dotimes (column size)
     (print (format {%3d} (nth (list row column) table))))
   ; end of row 
   (println))
 0 1 2 3 4 5 6 7 8 9
 10 11 12 13 14 15 16 17 18 19
 20 21 22 23 24 25 26 27 28 29
 30 31 32 33 34 35 36 37 38 39
 40 41 42 43 44 45 46 47 48 49
 50 51 52 53 54 55 56 57 58 59
 60 61 62 63 64 65 66 67 68 69
 70 71 72 73 74 75 76 77 78 79
 80 81 82 83 84 85 86 87 88 89
 90 91 92 93 94 95 96 97 98 99


(nth 也适用于列表和字符串。)

与列表一样,你可以使用隐式寻址来获取值

(set 'size 10)
(set 'table (array size size (sequence 0 (pow size))))

(table 3)
;-> (30 31 32 33 34 35 36 37 38 39)     ; row 3 (0-based!)

(table 3 3)                             ; row 3 column 3 implicitly
;-> 33

要设置值,请使用 setf。以下代码将所有非素数替换为 0。

(set 'size 10)
(set 'table (array size size (sequence 0 (pow size))))
(dotimes (row size)
  (dotimes (column size)
    (if (not (= 1 (length (factor (nth (list row column) table)))))
        (setf (table row column) 0))))

table
;-> ((0 0 2 3 0 5 0 7 0 0) 
 (0 11 0 13 0 0 0 17 0 19) 
 (0 0 0 23 0 0 00 0 29)
 (0 31 0 0 0 0 0 37 0 0)
 (0 41 0 43 0 0 0 47 0 0)
 (0 0 0 53 0 0 0 0 0 59)
 (0 61 0 0 0 0 0 67 0 0)
 (0 71 0 73 0 0 0 0 0 79)
 (0 0 0 83 0 0 0 0 0 89)
 (0 0 0 0 0 0 0 97 0 0))

除了隐式寻址 (table row column),我还可以写 (setf (nth (list row column) table) 0)。隐式寻址速度略快,但在某些情况下使用 nth 会使代码更易读。

有一些函数将数组或列表(具有正确的结构)视为矩阵。

  • invert 返回矩阵的逆矩阵
  • det 计算行列式
  • multiply 乘以两个矩阵
  • mat 将函数应用于两个矩阵或一个矩阵和一个数字
  • transpose 返回矩阵的转置

transpose 在嵌套列表中使用时也很有用(请参阅 关联列表)。

统计、财务和建模函数

[编辑 | 编辑源代码]

newLISP 有一套完整的函数,用于财务和统计分析以及模拟建模。

给定一个数字列表,stats 函数返回值的个数、平均值、与平均值的平均偏差、标准差(总体估计)、方差(总体估计)、分布的偏度和分布的峰度

(set 'data (sequence 1 10))
;->(1 2 3 4 5 6 7 8 9 10)
(stats data)
(10 5.5 2.5 3.02765035409749 9.16666666666667 0 -1.56163636363636)

以下是其他内置函数的列表

  • beta 计算 beta 函数
  • betai 计算不完全 beta 函数
  • binomial 计算二项式函数
  • corr 计算皮尔逊积矩相关系数
  • crit-chi2 计算给定概率的卡方
  • crit-f 计算给定置信概率的临界最小 F
  • crit-t 计算给定置信概率的临界最小学生 t
  • crit-z 计算给定累积概率的临界正态分布 Z 值
  • erf 计算一个数字的误差函数
  • gammai 计算不完全伽马函数
  • gammaln 计算对数伽马函数
  • kmeans-query 计算数据向量到质心的欧几里得距离
  • kmeans-train 对矩阵数据执行 K 均值聚类分析
  • normal 生成一个正态分布的浮点数列表
  • prob-chi2 计算卡方的累积概率
  • prob-f 找到观察统计量的概率
  • prob-t 找到正态分布值的概率
  • prob-z 计算 Z 值的累积概率
  • stats 查找中心趋势和分布矩的统计值
  • t-test 使用学生 t 检验来比较平均值

贝叶斯分析

[编辑 | 编辑源代码]

由托马斯·贝叶斯牧师在 18 世纪初发展起来的统计方法已被证明用途广泛且流行,足以进入今天的编程语言。在 newLISP 中,两个函数 bayes-trainbayes-query 协同工作,提供了一种简便的方法来计算数据集的贝叶斯概率。

以下是如何使用这两个函数来预测一小段文本是由两个作者中的哪一个写成的可能性。

首先,从两位作者那里选择文本,并为每位作者生成数据集。我选择了奥斯卡·王尔德和柯南·道尔。

(set 'doyle-data 
 (parse (lower-case 
   (read-file "/Users/me/Documents/sign-of-four.txt")) {\W} 0))
(set 'wilde-data 
 (parse (lower-case 
   (read-file "/Users/me/Documents/dorian-grey.txt")) {\W} 0))

bayes-train 函数现在可以扫描这两个数据集,并将单词频率存储在一个新的上下文中,我称之为 Lexicon

(bayes-train doyle-data wilde-data 'Lexicon)

此上下文现在包含列表中出现的单词列表,以及每个单词的频率。例如

Lexicon:_always
;-> (21 110)

也就是说,单词 always 在柯南·道尔的文本中出现了 21 次,在王尔德的文本中出现了 110 次。接下来,Lexicon 上下文可以保存到一个文件中

(save "/Users/me/Documents/lex.lsp" 'Lexicon)

并在需要时使用以下代码重新加载

(load "/Users/me/Documents/lex.lsp")

训练完成后,你可以使用 bayes-query 函数在一个上下文中查找单词列表,并返回两个数字,即这些单词属于第一个或第二个单词集的概率。以下是三个查询。请记住,第一个集合是道尔,第二个集合是王尔德

(set 'quote1 
 (bayes-query 
   (parse (lower-case 
    "the latest vegetable alkaloid" ) {\W} 0) 
   'Lexicon))
;-> (0.973352412 0.02664758802)

(set 'quote2 
 (bayes-query 
   (parse 
    (lower-case 
    "observations of threadbare morality to listen to" ) {\W} 0)
    'Lexicon))
;-> (0.5 0.5)

(set 'quote3 
 (bayes-query 
   (parse 
    (lower-case
    "after breakfast he flung himself down on a divan 
     and lit a cigarette" ){\W} 0) 
   'Lexicon))
;-> (0.01961482169 0.9803851783)

这些数字表明 quote1 很可能(97% 的确定性)来自柯南·道尔,quote2 既不是道尔式的也不是王尔德式的,而 quote3 很可能来自奥斯卡·王尔德。

也许这是幸运的,但这确实是一个好结果。第一个引文来自道尔的血字的研究,第三个引文来自王尔德的亚瑟·萨维尔勋爵的罪行,这两部文本都没有包含在训练过程中,但显然是作者词汇的典型代表。第二个引文来自简·奥斯汀,而牧师所发展的方法无法将其归属于任何一位作者。

财务函数

[编辑 | 编辑源代码]

newLISP 提供以下财务函数

  • fv 返回投资的未来价值
  • irr 返回内部收益率
  • nper 返回投资的期间数
  • npv 返回投资的净现值
  • pmt 返回贷款的支付额
  • pv 返回投资的现值

逻辑编程

[编辑 | 编辑源代码]

Prolog 编程语言使一种称为统一的逻辑编程变得流行起来。newLISP 提供了一个 unify 函数,该函数可以通过匹配表达式来执行统一。

(unify '(X Y) '((+ 1 2) (- (* 4 5))))
((X (+ 1 2)) (Y (- (* 4 5))))

使用 unify 时,未绑定的变量以大写字母开头,以区别于符号。

位运算符

[编辑 | 编辑源代码]

位运算符将数字视为由 1 和 0 组成。我们将使用一个实用函数,该函数使用 bits 函数以二进制格式打印数字

(define (binary n)
   (if (< n 0)
       ; use string format for negative numbers
      (println (format "%6d %064s" n (bits n)))
      ; else, use decimal format to be able to prefix with zeros
      (println (format "%6d %064d" n (int (bits n))))))

此函数打印出原始数字及其二进制表示

(binary 6)
;->       6 0000000000000000000000000000000000000000000000000000000000000110
;-> "     6 0000000000000000000000000000000000000000000000000000000000000110"

移位函数 (<<>>) 将位向左或向右移动

(binary (<< 6)) ; shift left
;->     12 0000000000000000000000000000000000000000000000000000000000001100
;->"    12 0000000000000000000000000000000000000000000000000000000000001100"
(binary (>> 6)) ; shift right
;->      3 0000000000000000000000000000000000000000000000000000000000000011
;->"     3 0000000000000000000000000000000000000000000000000000000000000011"

以下运算符比较两个或多个数字的位。以 4 和 5 为例

(map binary '(5 4))
;->     5 0000000000000000000000000000000000000000000000000000000000000101
;->     4 0000000000000000000000000000000000000000000000000000000000000100
;-> ("     5 0000000000000000000000000000000000000000000000000000000000000101"
;-> "     4 0000000000000000000000000000000000000000000000000000000000000100")
(binary (^ 4 5)) ; exclusive or: 1 if only 1 of the two bits is 1
;->      1 0000000000000000000000000000000000000000000000000000000000000001
;->"     1 0000000000000000000000000000000000000000000000000000000000000001"
(binary (| 4 5)) ; or: 1 if either or both bits are 1
;->      5 0000000000000000000000000000000000000000000000000000000000000101
;->"     5 0000000000000000000000000000000000000000000000000000000000000101"
(binary (& 4 5)) ; and: 1 only if both are 1
;->      4 0000000000000000000000000000000000000000000000000000000000000100
;->"     4 0000000000000000000000000000000000000000000000000000000000000100"

否定或非函数 (~) 反转数字中的所有位,交换 1 和 0

(binary (~ 5)) ; not: 1 <-> 0
;->     -6 1111111111111111111111111111111111111111111111111111111111111010
;->"    -6 1111111111111111111111111111111111111111111111111111111111111010"

打印出这些字符串的二进制函数使用 & 函数测试数字的最后一位是否为 1,并使用 >> 函数将数字向右移动一位,为下一次迭代做好准备。

OR 运算符 (|) 的一个用途是在使用 regex 函数时将正则表达式选项组合在一起。

crc32 为字符串计算 32 位 CRC(循环冗余校验)。

更大的数字

[编辑 | 编辑源代码]

对于大多数应用程序,newLISP 中的整数计算涉及从 -9223372036854775808 到 9223372036854775807 的整数。这些是可以使用 64 位存储的最大整数。如果你将 1 加到最大的 64 位整数,你会“翻滚”(或环绕)到范围的负端

(set 'large-int 9223372036854775807)
(+ large-int 1)
;-> -9223372036854775808

但 newLISP 可以处理比这更大的整数,即所谓的“bignums”或“大整数”。

(set 'number-of-atoms-in-the-universe 100000000000000000000000000000000000000000000000000000000000000000000000000000000)
;-> 100000000000000000000000000000000000000000000000000000000000000000000000000000000L
(++ number-of-atoms-in-the-universe)
;-> 100000000000000000000000000000000000000000000000000000000000000000000000000000001L
(length number-of-atoms-in-the-universe)
;-> 81
(float  number-of-atoms-in-the-universe)
;->1e+80

请注意,newLISP 使用尾随的“L”来表示大整数。通常,你可以进行大整数计算,无需任何考虑

(* 100000000000000000000000000000000 100000000000000000000000000000)
;-> 10000000000000000000000000000000000000000000000000000000000000L

这里两个操作数都是大整数,因此答案也自动成为大整数。

但是,当你的计算将大整数与其他类型的数字组合在一起时,你需要更加小心。规则是计算的第一个参数决定是否使用大整数。比较这个循环

(for (i 1 10) (println (+ 9223372036854775800 i)))
9223372036854775801
9223372036854775802
9223372036854775803
9223372036854775804
9223372036854775805
9223372036854775806
9223372036854775807
-9223372036854775808
-9223372036854775807
-9223372036854775806
-9223372036854775806

和这个

(for (i 1 10) (println (+ 9223372036854775800L i))) ; notice the "L"
9223372036854775801L
9223372036854775802L
9223372036854775803L
9223372036854775804L
9223372036854775805L
9223372036854775806L
9223372036854775807L
9223372036854775808L
9223372036854775809L
9223372036854775810L
;-> 9223372036854775810L

在第一个示例中,函数的第一个参数是一个很大的(64 位整数)。因此,将 1 加到最大的 64 位整数会导致翻滚——计算保持在大型整数领域。

在第二个示例中,附加到加法第一个参数的 L 强制 newLISP 切换到大整数运算,即使 两个操作数都是 64 位整数。第一个参数的大小决定了结果的大小。

如果您提供一个字面意义上的大整数,则不必附加“L”,因为很明显该数字是一个大整数。

(for (i 1 10) (println (+ 92233720368547758123421231455634 i)))
92233720368547758123421231455635L
92233720368547758123421231455636L
92233720368547758123421231455637L
92233720368547758123421231455638L
92233720368547758123421231455639L
92233720368547758123421231455640L
92233720368547758123421231455641L
92233720368547758123421231455642L
92233720368547758123421231455643L
92233720368547758123421231455644L
92233720368547758123421231455644L

还有其他方法可以控制 newLISP 在大整数和小整数之间进行转换的方式。例如,您可以使用 bigint 函数将某些内容转换为大整数。

(set 'bignum (bigint 9223372036854775807))
(* bignum bignum)
;-> 85070591730234615847396907784232501249L

(set 'atoms (bigint 1E+80))
;-> 100000000000000000000000000000000000000000000000000000000000000000000000000000000L
(++ atoms) 
;-> 100000000000000000000000000000000000000000000000000000000000000000000000000000001L
华夏公益教科书