我创建了一个测试,以比较空字段中的值数与数字。我对结果感到惊讶。
首先,我创建了一个带有字段f的签名,可以选择将一个A原子映射到另一个A原子:
sig A {f: lone A}
然后我创建了这个表达式:如果f为空,则f映射的A原子数的计数小于8:
check {
no A.f =>
# A.f < 8
}
我运行了检查命令,合金分析仪找到了一个反例。这让我感到非常惊讶。
我打开了评估工具,并输入了以下内容:
我输入了:A
评估者回答:{}
我输入了:no A.f
评估者回答:true
我输入了:# A.f
评估者回答:0
我输入了:# A.f < 8
评估者回答:false
嗯?
为什么0 < 8
为假?
答案 0 :(得分:2)
合金只有有限的一组整数,因为每个整数在寻找解决方案方面都相对昂贵。默认情况下,此设置为:
Int
┌──┬──┬──┬──┬──┬──┬──┬──┬─┬─┬─┬─┬─┬─┬─┬─┐⁻¹
│-8│-7│-6│-5│-4│-3│-2│-1│0│1│2│3│4│5│6│7│
└──┴──┴──┴──┴──┴──┴──┴──┴─┴─┴─┴─┴─┴─┴─┴─┘
当您执行7 + 1时,您实际上得到-8!
尝试一下...只需在评估器中输入8:
8
-8
这实际上并不限于Alloy,C,C ++,Java和大多数其他语言都执行相同的操作,您通常不会注意到它的原因是因为环绕点要高得多。对于Java int,它超过20亿。原因是将整数存储为一组位。一旦达到最大值,加法将需要一个额外的位。由于该位不存在,因此将其静默忽略。 (认识到到目前为止,我从未见过任何能处理此溢出的代码。)
因此,当您的最大数量为7时,合金中的默认数量为8,而实际上您使用的是8 -8 !
# A.f < -8
我们有这种恐惧的原因是当将Int合金提供给SAT求解器时,它也打包在一个位集中。
合金为此一直在苦苦挣扎,并且可以选择防止溢出。最初,我认为这是从天上来的礼物,但由于我意识到它的工作原理,因此禁用了它。问题在于它删除了可能有效的解决方案。找到解决方案并不算太坏,因为您会注意到那里什么都没有,但是对于断言来说则是相当糟糕的,因为断言可能会说模型实际上有没有解决方案。这让我不寒而栗,因为我想依靠一个断言。通过查看实际用例,我决定宁愿显式处理模型中的溢出,因为它们实际上也是最终产品中的问题。许多已知的错误是由意外溢出引起的。因此,将它们隐藏起来的模型不是很有用。
那么您如何处理呢?这样做的语法有点奇怪。您必须指定整数编码的 bitwidth 。因此,您可以将模型更改为:
sig A {f:lone A}
check {
no A.f =>
# A.f < 8
} for 5 int
for 5 int
将SAT编码的位宽设置为5位。 5位= 5 ^ 2 = 32。这样便得到了整数-16..15。
这显然是一条巨大的绊索。幸运的是,正在进行许多出色的工作,以使Alloy在SMT求解器上运行。 SMT求解器将具有比Alloy更自然的数字处理能力,这不会使您失望。
也就是说,如果您使用的常量整数不适合可用的Int集合,则可以尝试至少产生一个错误。也许您可以提交错误?