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** 函式始終返回浮點值。
值得注意的是,floor 和 ceil 函式返回包含整數值的浮點數。例如,如果您使用 floor 將圓周率向下舍入到最接近的整數,結果為 3,但它儲存為浮點數而不是整數。
(integer? (floor PI))
;-> nil
(floor PI)
;-> 3
(float? (ceil PI))
;-> true
abs 和 sgn 函式也可以在測試和轉換數字時使用。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
要將數字轉換為字串,請使用 string 和 format 函式。
(reverse (string PI))
;-> "456395141.3"
string 和 println 都只使用前 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
min 和 max 按預期工作,儘管它們始終返回浮點數。與許多算術運算子一樣,您可以提供多個值。
(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 的所有三角函式 sin、cos、tan、asin、acos、atan、atan2 以及雙曲函式 sinh、cosh 和 tanh 都在弧度制下工作。如果您喜歡以度數為單位工作,您可以將替代版本定義為函式。
(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
以下通用函式在陣列和列表上都同樣有效:first、last、rest、mat、nth、setf、sort、append 和 slice。
還有一些用於陣列和列表的特殊函式,它們提供矩陣運算:invert、det、multiply、transpose。請參閱 矩陣。
陣列可以是多維的。例如,要建立一個 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-train 和 bayes-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