Proposed signed_sub function

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)
end

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)
    reinterpret(Int64, r)
end

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.

2 Likes

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.

1 Like

Have you considered GitHub - JeffreySarnoff/SaferIntegers.jl: These integer types use checked arithmetic, otherwise they are as system types. for your needs?

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.

1 Like

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.

julia> Timestamp(typemax(UInt64))
2554-07-21T23:34:33.709551615

julia> Timestamp(typemax(Int64))
2262-04-11T23:47:16.854775807
1 Like