Общие пользовательские функции оператора: курьезный случай плохой * инструкции
Я столкнулся с этой проблемой, играя с обобщенными и пользовательскими операторами в Swift. В приведенном ниже фрагменте кода я представляю два новых префиксных оператора ∑ и ∏, а затем реализую их префиксные функции в виде векторной суммы и произведения соответственно. Чтобы не приходилось реализовывать эти и аналогичные функции для всех целочисленных типов и типов с плавающей запятой по отдельности, я определил вместо этого два протокола: Summable (для которого требуется + реализация) и Multiplicable (для которого требуется * реализация). Кроме того, я реализовал две функции для аргументов SequenceType, которые работают, например, с типами Array и Rage. Наконец, вы можете увидеть из вызовов println в конце фрагмента, что все это работает довольно хорошо, за исключением ∏(1...100). Здесь программа аварийно завершает работу с EXC_BAD_INSTRUCTION и больше ничего не происходит. Обратите внимание, что ∑(1...100) работает, даже если он реализован таким же образом. На самом деле, если я изменю начальное значение в строке return reduce(s, 1, {$0 * $1})
0, чем программа завершает без ошибок, хотя и с неправильными выходами из вызовов to.
Итак, все сводится к использованию 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 ответ
Это простое целочисленное переполнение. Вы пытаетесь вычислить факториал
1 * 2 * ... * 100 = 100!
= 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
≈ 9.33 × 10^157
по словам Вольфрама Альфа. С начальным значением 0
вместо 1
все продукты равны нулю и переполнение не происходит.
∏(1...20) = 2432902008176640000
работает как ожидалось и является самым большим факториалом, который может храниться в 64-битном целом числе.
В Swift целочисленные вычисления не "оборачиваются", а вызывают исключение, если результат не вписывается в целевой тип данных.
Swift имеет специальные "операторы переполнения"&+
, &*
,... с другим поведением переполнения для целочисленных вычислений, см. "Операторы переполнения" в документации Swift.