Division and prefix minus with overflow

swift defines these three:

* Overflow addition ( `&+` )
* Overflow subtraction ( `&-` )
* Overflow multiplication ( `&*` )

but not the fourth for division. an oversight? usage example:

func divide(x: Int8, y: Int8) -> Int8 {
    return x / y   // Fatal error: Division results in an overflow
    return x &/ y // a hypothetical new operator, gives -128 in this case
}
func foo() {
    divide(x: -128, y: -1)
}

also i think it would be useful to have the overflow version of prefix minus operator:

func negative(x: Int8) -> Int8 {
    return -x  // Swift runtime failure: arithmetic overflow
    return &-x // a hypothetical new operator - ok, gives -128 in this case
}
func bar() -> Int8 {
    negative(x: -128)
}

Not an oversight. The operators &/ and &% were removed in Swift 1.2 because they did not provide two’s complement behavior like other overflow operators, and to simplify the language. (This is documented in the changelog.)

In the rare circumstance you need this functionality, use dividedReportingOverflow.

3 Likes

In the rare circumstance that you need this behavior, what is the problem with writing 0 &- x?

2 Likes

This one isn't rare at all, but is easily written as 0 &- x, as you say.

1 Like

good to know there's this workaround, thank you.

let's say you are implementing an app that grabs Int16 samples from the mic or camera does some fancy math and outputs result to the speaker or screen. and there are bunch of those " + - * / % " scattered across the code. and that algorithm wasn't written by you, and it's massive (but contained), and you don't quite understand it. would you prefer the app to crash once in a while due to unfortunate sample/pixel combination happening in the mic/cam data or would you prefer a minor glitch sent to the speaker/screen? if the latter and i didn't have time to do it properly - i'd just do a full text search replace + to &+, etc. i know, it's a bit of a lazy / hammer and not ideal approach. i'd prefer the app not crashing on those math overflow issues at all in release mode, perhaps if i opt-in to that behaviour. (crashing in debug mode is of course very helpful.)

(another approach i would probably do (and i remember actually doing smth like this in the past) - my own types instead of the types the algorithm is using, overriding math ops, and having asserts in those ops overrides for debug builds).

This is not an either-or choice. The preferred option is that you neither crash nor emit unexpected output. To do this, you would handle each computation that may overflow in an appropriate manner, either by asserting that it will not overflow using the ordinary operators, testing for overflow using *ReportingOverflow operations, or obtaining the full result using full-width operations.

But since you're asking, one of the tentpole design features of Swift is safety, and one way in which Swift delivers on that promise is by refusing to continue execution when an unexpected state is encountered. You absolutely shouldn't be emitting unexpected output to my speakers using an algorithm you don't understand with inputs you don't expect--this could quite possibly damage the hardware itself. Users would absolutely want your code to stop executing before it does that.

4 Likes

If your application enters an unexpected state, that is commonly referred to as a “bug”, and Swift does not universally protect against bugs. Of course, overflow can be a common source of bugs, but continuing execution after overflow is not necessarily failing to deliver on the promise of safety, unless you are arguing that Swift should stop execution on every bug.

FWIW, Rust only stops execution on overflow in debug builds, and not in release builds. Clearly debug builds should do their best to report programmer errors, but it is reasonable to want release builds to prioritise other things.

I doubt this true. There are certain rare edge-cases such as resonance which might damage the hardware (or persisting a static image on an OLED screen, etc), but overflow can be guarded against quite easily.

Anecdotally, I blew my speaker because I forgot to renormalize audio frame :woman_facepalming:, on macOS framework no less. At least it's a cheaper third-party speaker and is easy to replace.

The bottom line is, well, I guess it's better to err on the side of caution.

6 Likes

Huh, well I’d still question the software stack and even the hardware that allowed that to happen. There are several layers in the middle that should have done a better job guarding against that IMO.

Heh, it should, no doubt, and I'm not saying that this is common. Just that it's an interesting event I'd like to share.

1 Like

Having been involved in DSP algorithms for speaker protection in the past, I assure you that it is quite easy to damage physical hardware in many cases, and that overflow bugs are a common way to do it.

In general, lower-levels of the audio stack should be doing this for you, but someone has to write those too.

4 Likes

What stops you from doing the same for / -> dividingReportingOverflow? It’s just a regex away.

Swift cannot protect against all bugs by stopping execution, but it provides safety against whole classes of bugs in that way, and among these specifically are out-of-bounds access and arithmetic overflow. It’s not merely a convenience to help developers debug their code, it’s there to limit the damage caused by poorly debugged code when it is released to users.

Swift does not stop you from rejecting this philosophy by writing your own subscripts and arithmetic operators, but it does not make that the path of least resistance. Overflow operators have two’s complement semantics essential for certain computations; they’re not there to enable find-and-replace substitutions to make code you don’t understand “not crash.” In fact, that it makes such an anti-pattern so tempting suggests the operators would probably best have been spelled differently (as they are in Rust).

If given a reference implementation of a numerical algorithm in C (or soon, C++) that you don’t understand, Swift allows you to call it as-is. This is the best way to ensure your code’s behavior is at least no buggier than the original. Translating a numerical algorithm from C to Swift requires accounting for not just overflow handling, but also integer promotion rules in C, differences in operator precedence (particularly bitwise and bit shift operators) between the two languages, and likely other issues that don’t come to mind at the moment.

If given a reference implementation of a numerical algorithm that’s already written in Swift, then “crashing” when you call it is a sign that you’re providing inputs that violate preconditions (or, you got a buggy reference implementation). Editing an implementation you don’t understand essentially to remove preconditions is a surefire way to introduce new bugs, turning an unchecked precondition upfront to a bigger problem down the line.

4 Likes

ah, i was just unaware of it. it's good:

struct MyInt<T: FixedWidthInteger> {
    var value: T
    init(_ value: T) {
        self.value = value
    }
    static func + (a: Self, b: Self) -> Self {
        #if DEBUG
        return Self(a.value + b.value)
        #else
        return Self(a.value &+ b.value)
        #endif
    }
    static func - (a: Self, b: Self) -> Self {
        #if DEBUG
        return Self(a.value - b.value)
        #else
        return Self(a.value &- b.value)
        #endif
    }
    static func * (a: Self, b: Self) -> Self {
        #if DEBUG
        return Self(a.value * b.value)
        #else
        return Self(a.value &* b.value)
        #endif
    }
    static func / (a: Self, b: Self) -> Self {
        #if DEBUG
        return Self(a.value / b.value)
        #else
        return Self(a.value.dividedReportingOverflow(by: b.value).partialValue)
        #endif
    }
    static prefix func -(a: Self)-> Self {
        Self(0) - a
    }
    static func += (a: inout Self, b: Self) {
        a = a + b
    }
    static func -= (a: inout Self, b: Self) {
        a = a - b
    }
    static func *= (a: inout Self, b: Self) {
        a = a * b
    }
    static func /= (a: inout Self, b: Self) {
        a = a / b
    }
    static var zero: Self {
        Self(0)
    }
}

    func test() {
        let a = MyInt<Int8>(-128)
        let b = MyInt<Int8>(127)
        
        print(-a)
        print(b + .init(1))
        print(a / .init(0))
        print("done")
    }

due to a bug in A/D converter machinery? sounds plausible.

understandable. i'd also expect some protection analogue circuits in the actual hardware, but perhaps those could be absent in cheap hardware.

Ah yes, the famously questionable software stack and bargain basement hardware made by *checks notes*

https://twitter.com/marcan42/status/1456185905090220038

These things happen in the ordinary course of things simply because we’re human and not necessarily because of a deviation from some norm.

For this reason, I’m confident that the wisdom of Swift’s design choices here will only become more apparent with time—it doesn’t just protect those people who run bad software on cheap hardware, it protects us…from us.

5 Likes