我正在將我在Dafny中做的一個練習翻譯成SPARK,在這個練習中,人們用一個尾部遞回函式來驗證一個遞回的函式。Dafny的源代碼(有刪減,因為它可能仍然被用于類):
函式Sum(n:nat):nat
遞減n
{
if n==0 then n else n Sum(n-1)
}
方法 ComputeSum(n:nat) 回傳 (s:nat)
確保s == Sum(n)
{
s := 0;
// ...審查...
}
到目前為止,我在SPARK中得到的東西:
function Sum (n : in Natural) return Natural
是
開始
如果n=0,那么
回傳n。
否則
回傳 n Sum(n - 1)。
end if;
結束Sum。
函式 ComputeSum(n : in Natural) 回傳 Natural
與
Post => ComputeSum'Result = Sum(n)
是
s : 自然 :=0。
開始
回傳s。
結束 ComputeSum。
我似乎無法弄清楚如何表達decreases n條件(現在我想起來可能有點奇怪......但幾年前我因為這個問題被打分,所以我有什么資格評判,問題仍然是如何完成它)。因此,我得到了可能溢位和/或無限遞回的警告。
我猜想有一個前置或后置條件需要添加。我嘗試了Pre => n <= 1,這顯然不會溢位,但我仍然得到了警告。在此基礎上添加Post => Sum'Result <= n**n,使警告消失,但該條件得到 "postcondition might fail "警告,這是不對的,但估計驗證器無法分辨。這也不是我應該檢查的運算式,但我似乎無法理解我在尋找的其他Post。可能是與遞回運算式非常接近的東西,但我的嘗試都沒有成功。一定是遺漏了某些語言結構......
那么,我如何才能在一個人的身上找到 "遞回運算式"?
那么,我怎樣才能表達遞回約束呢?
編輯1:
按照這個SO答案和這個SPARK檔案部分的鏈接,我嘗試這樣做:
function Sum (n : in Natural) return Natural
是
(如果n = 0,那么0,否則n Sum(n - 1))
與
Pre => (n in 0 ... 2)。
Contract_Cases => (n = 0 => Sum'Result = 0,
n >= 1 => Sum'Result = n Sum(n - 1))。)
Subprogram_Variant => (Decreases => n)。
但是從SPARK得到這些警告:
spark.adb:32:30: medium: overflow check might fail [reason for check: result of addition must fit in a 32-bits machine integer][#0] 。
spark.adb:36:56: 警告:在其后置條件中呼叫 "Sum "將導致無限遞回。
uj5u.com熱心網友回復:
如果你想證明某個尾部遞回求和函式的結果等于某個值N的給定遞回求和函式的結果,那么原則上,只需要定義遞回函式(作為運算式函式)而沒有任何后置條件就足夠了。然后你只需要在尾部遞回函式的后置條件中提到遞回(運算式)函式(注意,在 Dafny 中也沒有對遞回函式的后置條件(ensures))。
然而,由于SPARK的主要目標之一是證明不存在運行時錯誤,你必須證明溢位不會發生,并且由于這個原因,你確實需要一個遞回函式的后置條件。正如 @Jeffrey Carter 已經在評論中建議的那樣,這種后置條件的合理選擇是 算術級數的顯式求和公式:
Sum (N) = N * (1 N) / 2
這個選擇實際上是非常有吸引力的,因為有了這個公式,我們現在還可以根據一個眾所周知的計算一系列自然數之和的數學明確運算式來對遞回函式本身進行功能驗證。
不幸的是,使用這個公式原樣只能讓你走到一半。在SPARK中(也包括Ada),前置條件和后置條件是可以選擇執行的(參見RM 11.4.2和SPARK參考指南中的5.11.1部分),因此本身必須是沒有任何運行時錯誤。因此,按原樣使用該公式只能讓你證明任何正數都不會發生溢位,直到
在后置條件中,乘法也不允許溢位(注意, 為了解決這個問題,你需要利用Ada 202x標準庫中引入的大整數包(另見RM A.5.6;這個包已經包含在GNAT CE 2021和GNAT FSF 11.2中)。大整數是無界的,用這些整數進行的計算絕不會溢位。使用這些整數,我們可以證明任何正數都不會發生溢位,直到 下面的例子說明了這些整數在后置條件中的用法。
一些最后的注釋: 要編譯一個使用新的Ada 202x標準庫中的函式的程式,使用編譯器選項 雖然我使用子型別來約束 將反例視為可能的線索而不是事實。它們可能有意義,也可能沒有意義。反例是由一些求解器選擇性地生成的,可能沒有意義。參見SPARK用戶指南中的7.2.6節。 main.adb 輸出(gnatprove) 補充說明(針對評論) 因此,你可以將后置條件添加為遞回函式,但這對證明不存在溢位沒有幫助;你仍然必須對函式結果提供一些上限,以便說服驗證者,運算式 為了檢查加法程序中是否存在溢位,驗證者將考慮 如果精確的上界不可用,那么你通常會退回到一個更保守的邊界,例如,在這種情況下, 這里有一個替代的例子: example.ads example.adb output (gnatprove) uj5u.com熱心網友回復: 我找到了一些有時能起作用的東西,我認為這對于關閉標題問題來說已經足夠了: GNAT Studio中的命令列呼叫(Ctrl Alt F),--counterproof=on和--prover=z3是我對它的補充: 心得體會:max N s.
max N s.t. N * (1 N) <= Integer'Last <-> N = 46340
Natural'Last = Integer'Last = 2**31-1)。
max N s.
max N s.t. N * (1 N) / 2 <= Natural'Last <-> N = 65535 = 2**16 - 1
Subprogram_Variant方面只需要證明一個遞回子程式最終會終止。這樣的終止證明必須通過向函式添加注解來明確要求(也顯示在下面的例子中,并在評論中由@egilhh指出的SPARK檔案中討論)。然而,對于你最初的目的來說,Subprogram_Variant方面是不需要的:證明某個尾部遞回求和函式的結果等于某個值N的特定遞回求和函式的結果。
-gnat2020。
N的允許值范圍,但你也可以使用一個前提條件。這不應該有任何區別。然而,在SPARK(也包括Ada)中,一般認為盡可能使用(子)型別來表達約束是一種最佳做法。
用Ada.Numerics.Big_Numbers.Big_Integers;
帶有SPARK_Mode的程序Main是
包BI重命名Ada.Numerics.Big_Numbers.Big_Integers。
使用型別BI.Valid_Big_Integer。
-- 轉換函式。
function To_Big (Arg : Integer) return BI.Valid_Big_Integer 重命名BI.To_Big_Integer。
function To_Int (Arg : BI.Valid_Big_Integer) return Integer 重命名BI.To_Integer。
子型別Domain是自然范圍0 ... 2**16 - 1。
函式 Sum (N : Domain) 回傳 Natural 是
(如果N=0則為0,否則N Sum(N-1))
與
Post => Sum'Result = To_Int (To_Big (N) * (1 To_Big (N)) / 2),
Subprogram_Variant => (Decreases => N);
-- 要求證明Sum對所有可能的N值都會終止。
pragma Annotate (GNATprove, Terminating, Sum);
開始
null。
結束主。
$ gnatprove -Pdefault.gpr --output=oneline --report=all --level=1-prover=z3
第2階段:生成全球合同 ...
第2階段:流程分析和證明 ...
main.adb:13:13: 資訊:子程式 "Sum "將終止,終止注釋已被證明。
main.adb:14:30: 資訊:溢位檢查已證明
main.adb:14:32: 資訊:子程式變體已被證明
main.adb:14:39: 資訊:范圍檢查已證明
main.adb:16:18: 資訊:后置條件已證明
main.adb:16:31: 資訊:范圍檢查已證明。
main.adb:16:53: 資訊: 謂詞檢查已證明
main.adb:16:69: 資訊:證明了除法檢查。
main.adb:16:71: 資訊: 謂詞檢查已證明
摘要登錄[...]/gnatprove.out
N Sum (N - 1)不會導致溢位。
Sum根據其規范可能回傳的所有可能的值,并查看這些值中是否至少有一個可能導致加法溢位。在后置條件中沒有明確的約束,Sum可能根據其回傳型別,回傳Natural'Range范圍內的任何值。這個范圍包括Natural'Last,這個值肯定會導致溢位。因此,驗證器將報告說加法可能溢位。事實上,Sum在其允許的輸入值下從未回傳該值,這與此無關(這就是為什么它報告might)。因此,我們需要一個關于回傳值的更精確的上限值。
N * N(或者使用飽和數學,如SPARK用戶手冊中的斐波那契例子所示,5.2.7節,但是這種方法會改變你的函式,可能不可取)。package Example的SPARK_Mode是
子型別Domain是自然范圍0 ... 2**15。
函式Sum (N : Domain) 回傳Natural
with Post =>
Sum'Result = (if N = 0 then 0 else N Sum (N - 1)) and
Sum'Result <= N * N; -- 保守的上界,如果封閉形式的
-- 遞回函式的封閉形式的解將
--不存在。
結束示例。
帶有SPARK_Mode的package body Example是
函式 Sum (N : Domain) 回傳 Natural 是
開始
如果N=0,那么
回傳N。
否則
回傳 N Sum (N - 1)。
end if;
結束Sum。
結束示例。
$ gnatprove -Pdefault.gpr --output=oneline --report=all
第2階段:生成全球合同 ...
第2階段:流程分析和證明 ...
example.adb:8:19: 資訊:溢位檢查已被證明
example.adb:8:28: 資訊:范圍檢查已證明
example.adb:7:08: 資訊:后置條件被證明。
example.adds:7:45: 資訊:溢位檢查已證明
example.adds:7:54: 資訊:范圍檢查已證明
摘要登錄[...]/gnatprove.out
函式Sum(sum)()()
function Sum (n : in Natural) return Natural
是
(如果n = 0,那么0,否則n Sum(n - 1))
與
Pre => (n in 0 ... 10), -- 與--prover=z3一起作業,不是Default (CVC4)
-- Pre => (n in 0 ... 100), -- 不作業 -- "溢位檢查可能失敗,例如當n = 2時"
Subprogram_Variant => (Decreases => n),
Post => ((n = 0 and then Sum'Result = 0)
或(n > 0然后Sum'Result = n Sum(n - 1)))。
-- Contract_Cases => (n = 0 => Sum'Result = 0,
-- n > 0 => Sum'Result = n Sum(n - 1)); -- 警告:在其后置條件中呼叫 "Sum "會導致無限遞回。
-- Contract_Cases => (n = 0 => Sum'Result = 0,
-- n > 0 => n Sum(n - 1) = Sum'Result); -- 有效
-- Contract_Cases => (n = 0 => Sum'Result = 0。
-- n > 0 => Sum'Result = n * (n 1) / 2); -- 有效,并對高n給出了良好的溢位反例,但并不是真正的遞回
gnatprove -P%PP -j0 %X --output=oneline --ide-progress-bar --level=0 -u %fp --counterexamples=on --prover=z3
Subprogram_Variant => (Decreases => n)需要告訴驗證器每次遞回呼叫的n次遞減,就像Dafny版本一樣。
對于類似的合同,作業方式不一致,見注釋
Contract_Cases。
n = 2在0 ... 100的范圍內呈現為反證,但在0 ... 10的范圍內沒有。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/319303.html
標籤:
上一篇:遞回查找并將其作為一個陣列加入
下一篇:遞回陣列.push()的問題
