通用自定义运算符函数:一个奇怪的坏指令案例



我在Swift中使用泛型和自定义运算符时遇到了这个问题。在下面的代码片段中,我将介绍两个新的前缀运算符∑和π,然后分别将它们的前缀函数实现为向量和和乘积。为了不必分别为所有整数和浮点类型实现这些和类似的函数,我定义了两个协议:sumable(需要+实现)和Multiplicable(需要*实现)。此外,我实现了SequenceType参数的两个函数,例如,它适用于Array和Rage类型。最后,您可以从代码段末尾的println调用中看到,除了π(1…100)之外,这一切都运行得很好。这里的程序与EXC_BAD_INSTRUCTION一起崩溃,没有太多其他内容。请注意,∑(1…100%)可以工作,即使它是以相同的方式实现的。事实上,如果我将行return reduce(s, 1, {$0 * $1})中的初始值更改为0,那么程序将无错误地完成,尽管从调用到π的输出是错误的。

所以,这一切都归结为使用0或1作为初始值!?当有问题的行中的代码在几行中被解包时,很明显崩溃发生在$0 * $1。还要注意,我应该能够直接传递+和*运算符函数,而不是闭包{$0 * $1}{$0 + $1}。唉,这冒犯了编译器:"不允许部分应用泛型方法"。

有什么想法吗?将1(或任何非零Int)替换为0怎么会导致崩溃?为什么这种情况只发生在乘法的范围内,而初始值为0或1的加法范围很好?

prefix operator ∑ {}
prefix operator ∏ {}
protocol Summable { func +(lhs: Self, rhs: Self) -> Self }
protocol Multiplicable { func *(lhs: Self, rhs: Self) -> Self }
extension Int: Summable, Multiplicable {}
extension Double: Summable, Multiplicable {}
prefix func ∑<T, S: SequenceType where T == S.Generator.Element, 
    T: protocol<IntegerLiteralConvertible, Summable>>(var s: S) -> T {
    return reduce(s, 0, {$0 + $1})
}
prefix func ∏<T, S: SequenceType where T == S.Generator.Element, 
    T: protocol<IntegerLiteralConvertible, Multiplicable>>(var s: S) -> T {
    return reduce(s, 1, {$0 * $1})
}
let ints = [1, 2, 3, 4]
let doubles: [Double] = [1, 2, 3, 4]
println("∑ints = ( ∑ints )") // --> ∑ints = 10
println("∑doubles = ( ∑doubles )") // --> ∑doubles = 10.0
println("∑(1...100) = ( ∑(1...100) )") // --> ∑(1...100) = 5050
println("∏ints = ( ∏ints )") // --> ∏ints = 24
println("∏doubles = ( ∏doubles )") // --> ∏doubles = 24.0
println("∏(1...100) = ( ∏(1...100) )") // --> CRASH: EXC_BAD_INSTRUCTION

编辑:虽然我很尴尬,但我在这段代码中犯的错误对你的编程眼光来说是一个可爱的测试。在阅读下面马丁的答案之前,看看你是否能弄清楚。当你这样做的时候,你会自我感觉良好。(然而,我可能需要寻找另一份职业。)

这是一个简单的整数溢出。你试图计算阶乘

1 * 2 * ... * 100 = 100!
= 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
≈ 9.33 × 10^157

根据Wolfram Alpha的说法。初始值为0而不是1时,所有乘积都为零,并且溢出不会发生

∏(1...20) = 2432902008176640000

工作正常,是可以存储在64位整数。

在Swift中,整数计算不会"环绕",但如果结果不适合目标数据类型。

Swift有特殊的"溢出运算符"&+&*。。。对于整数计算具有不同的溢出行为,请参阅"溢出运算符"Swift文档中。

最新更新