If wrapping is desired, &+ and friends exist. If you want to detect and handle overflow, addingReportingOverflow(_:) and friends exist. You shouldn’t have to manually check before doing the operation for basic arithmetic primitives.
Right, but the ergonomics of those are pretty terrible.
- They're way more verbose, so any non-trivial mathematical expression becomes completely unreadable once translated to them.
- They don't use the standard error handling mechanism (
try/throw), you have to manually check their return value. So they don't scale well w.r.t. how many operations you're doing. You don't get any of the luxuries of proper error handling, like separating the happy path from the failure paths.
But you're right that they do exist, at least - I was glossing over them before.
Even considering them, as far as I can tell there's no equivalents for things like exponentiation (e.g. pow(_:_:)?
throwing operators would be great on the server, right now doing any sort of arithmetic on user-provided inputs is a simple DoS vulnerability.
pow operates on doubles, not ints, right? So on overflow it can just return infinity.
Ah yes, I forgot there's still no integer version of pow. ![]()
I glanced over the list in SE-0246: Generic math(s) functions in writing that, and was misled by the definition there looking generic (static func pow(_ x: Self, _ y: Self) -> Self - there is a comment that Self must be 'real' but it's not written in the actual type system).
A lot of those math functions presumably return infinities or NaNs for invalid input, which makes them somewhat analogous to addingReportingOverflow. Although not completely - there's a difference between passing in a NaN and receiving a NaN, and passing in finite integers and receiving a NaN. I think it'd still be nice to have throwing versions of some of those maths functions as well, covering at least the cases where you've newly generating likely bogus values (infinities, NaNs, etc).
From a quick experiment (e.g. log(0.0), overflowing pow, etc) it appears -Ounchecked has no influence on those functions, which is a relief at least. I was afraid it might turn generation of infinities or NaNs into crashes.
No, these operations are fully specified and have no invalid inputs, so there can never be a domain error. There are no preconditions that could be elided in unchecked.
Fixed-width integer exponentiation is not a very interesting or useful operation; it simply overflows for almost all inputs. Modular exponentiation and arbitrary-precision integer exponentiation are the actually useful operations (and even the latter is mostly only interesting for toy number-theory problems).
Well, you could say the same about integer +, if you simply define it to wrap.
Certainly for my own use, but I also believe true generally, it's not intended to get infinities or NaNs out of mathematical functions. It indicates a mistake - either a programming error or bad input data.
There are classes of mathematics where infinites are wrangled, but that's pretty esoteric in the context of Swift at large.
"Unchecked exceptions" to the rescue? While we do not have them in the language / standard library (yet? ever?), here's a recent attempt to implement them manually.
Yeah, maybe not pow specifically but abs, negate or something similar.
It's not hard to implement those missing bits
extension FixedWidthInteger {
mutating func negateReportingOverflow() -> /*overflow*/ Bool {
let result = negativeReportingOverflow
if result.overflow { return true }
self = result.partialValue
return false
}
var negativeReportingOverflow: (partialValue: Self, overflow: Bool) {
let result = Self.zero.subtractingReportingOverflow(self)
if result.overflow { return (self, true) }
return result
}
}
func absReportingOverflow<T: FixedWidthInteger>(_ x: T) -> (partialValue: T, overflow: Bool) {
if x >= 0 { return (x, false) }
return x.negativeReportingOverflow
}
though it would be better to have those available out of the box.
in another life i do recall having to implement fixed-width integer exponentiation in order to compute some GL texture sizes. although in hindsight if swift had better support for constant data, it would have been far wiser to use a lookup table.
It would be kind of nice to have fixed-power-of-integer operations that expanded to the optimum sequence of multiplies, for instance being able to write x**5 or #pow(x, 5) or something instead of { let x2 = x*x; return x2*x2*x }().
For integers, where multiplication is associative, the compiler is free to do this optimization for you from x*x*x*x*x.¹ For floating-point types, pow is generally² more accurate than a sequence of multiplications, and should be preferred.
For powers larger than 5 or 6, this starts to get unwieldy, and having a function would be nice, but those are relatively rare and overflow very fast.
¹ up to overflow, but I think we're in agreement that the compiler is allowed to reorder this such that overflows happen under the same conditions, if not in precisely the same place, even if we don't actually take advantage of this yet.
² at least on Apple platforms and reasonably modern glibc. YMMV in the Wild West of other math libraries.
But like @taylorswift said, it would be very useful to have integer pow() for e.g. mip generation in a virtual texturing system. Bonus points for log2() and sqrt!
Do you actually want integer log2( ) or do you want bitWidth - leadingZeroBitCount - 1?
People are going to reach for something called log. Case in point, here’s some Vulkan sample code for calculating the number of mips in a mip chain:
mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;
If your expression yields the same results, it seems like a very useful definition of integer log2()!
Can't say what "the same result is", since that implementation will give different results on different platforms, and some of them will lead to nasty errors. Turns out reaching for log2 was a mistake.
It’s the canonical way people do this. Under what circumstances would that C++ expression fail to produce the expected result? (Assume texWidth and texHeight are reasonable values that don’t cause any computations to blow up.)
In some sketchier platform math libraries, log2(x) has been implemented as log(x) / log(2) calculated in double precision, which can round incorrectly when x is an exact power of two, such that the subsequent floor produces one less than the correct result. Fortunately this bug is relatively rare; the analogous bug with log10() is quite common, however.
Good to know! But that merely sounds like a reason why Swift shouldn’t use such an implementation. If we can implement FixedWidthInteger.log2() in a way that avoids this bug, is faster, and supports -Ounchecked for those “gotta go fast” use cases, that seems like a worthwhile feature, no?
-
Let's call "Unchecked exceptions" (1).
-
Another approach is to make a wrapping type:
struct Integer<T: FixedWidthInteger> {
private var value: T
private (set) var overflow: Bool = false
init(_ value: T) { self.value = value }
func getValue() throws T {
if overflow { throw ... }
return value
}
static func + (lhs: Self, rhs: Self) -> Self { ... }
...
}
And do the calculations with that. Once the final result is calculated – get it's value (e.g. via a throwing getValue() call). Beware of the size increase with this approach, e.g. an array of Integer<Int64> would require 16 bytes per element instead of 8 (8 bytes for the value itself, 1 byte for the overflow bit and the rest due to alignment requirements.
- Same idea as in (1) but instead of a full blown "dynamic throw" just use the @taskLocal storage for the overflow bit.