I need to take differences of unsigned numbers. For that purpose I propose to add a
signed_sub function, that subtracts UInts, producing a (signed) Int result. For UInt64, it would be:
function signed_sub(x::UInt64, y::UInt64)
reinterpret(Int64, x - y)
That’s straightforward. But what I really want is the overflow-checked version, which was trickier to write:
function checked_signed_sub(x::UInt64, y::UInt64)
r, f = Base.sub_with_overflow(x, y)
# f is the sign of the 65-bit result.
# When we reinterpret, the top bit becomes the sign.
# So overflow is when those differ.
f ⊻ Core.is_top_bit_set(r) && Base.Checked.throw_overflowerr_binaryop(:signed_sub, x, y)
If there is agreement on the functionality (and the names) I’ll make a PR to add it as generally as I can (for all Unsigned types). (I’m happy if someone else wants to do it. I don’t know a good way to get the signed version of an unsigned type. I’d probably just make a Dict, but maybe there is a better way?)
I considered the dual, taking signed integers and producing an unsigned result, but I couldn’t imagine a use for that.
I think this should go in a package first, the very least until the API stabilizied, and then it should be considered for
Base if it is (1) widely used and (2) stable.
The biggest problem I see is supporting generic types (related to your question): if a package defined
T <: Unsigned, how to they hook into
signed_sub? Requiring that all such types define a method for such a niche use case may not be worth it.
Good to know about this. Thanks!
I see and appreciate most of your points. I was only considering the builtin subtypes of
Unsigned, where I’m willing to define the result type.
I don’t understand your "Requiring that all such types define a method " comment. I was trying to parallel the existing
Base.checked_sub. What am I missing?
Imagine that a third-party packages defines a new type
CustomUnsigned <: Unsigned. Should it define a method of
Base.checked_signed_sub for this type?
If yes, then it adds to the complexity of the interface. If not, then it cannot be used in generic code.
This is a trade-off and the cost should be considered relative to the benefit. IMO this is a pretty niche use case and should be supported in a package (for types in
Base, but YMMV).
Got it. I agree this is a niche case. I have been a developer for many years, and this is the first time I’ve wanted such a thing.
On the other hand, when I wanted it, it took me many hours reading through
Base code to figure out how to implement it efficiently. I imagine that would be the case for most users of the
checked_... functions. So I wanted to capture that knowledge. I’m fine with making it a package.
On the other other hand, I’m now thinking that for my particular use case, I can define my
signed_sub as returning a signed type of the next larger precision. That can’t overflow so I don’t need
checked_signed_sub, and makes
signed_sub very different from
checked_sub and the other checked functions. But I do not need the other checked functions for my application. So I will go ahead and make the package we are talking about eventually, but with lower priority.
Generally, if I need signed differences, I would go with a signed type.
It is very rare that the extra bit of range I get out of an
Unsigned type with the same number of bits is crucial.
Of course. As I mentioned, I have not wanted it until now. In my case, I have implemented a timestamp with nanosecond precision (using the Unix epoch as a base). And I want to take signed differences between these timestamps. I have no need for times before the Unix epoch, so using UInt64 gives me a wider range. I will admit that it probably makes no practical difference, but it makes me feel better.