Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x % Unsigned, x % Signed #34864

Merged
merged 36 commits into from
Mar 27, 2020
Merged

x % Unsigned, x % Signed #34864

merged 36 commits into from
Mar 27, 2020

Conversation

JeffreySarnoff
Copy link
Contributor

This follows from a discussion on Slack (exerpted below); the implementation is from @Keno.


Matt Bauman: I guess I’m in a gripey mood today. Is there a reason why we don’t allow -1 % Unsigned?

julia> -1 % UInt
0xffffffffffffffff
julia> -1 % Unsigned
ERROR: InexactError: check_top_bit(Int64, -1) ...

Matt Bauman: Is the “correct” way to do this unsigned(-1)?

Keno Fischer: yes, but I think % Unsigned is reasonable.
...

Kristoffer Carlsson: Why [is reinterpret] yuk? Isn't that the most descriptive function name to use? Who knows what semantics unsigned has?

Matt Bauman: For some reason, reinterpret feels like it imposes a higher cognitive burden — like I need to think about the exact bit representation. All I care about in this operation is ensuring that negative values become absurdly large. But it’s a moot point.

Keno Fischer: In this case rem is just using the convert fallback. I think a definition like

rem(x::Signed, ::Type{Unsigned}) = x % unsigned(typeof(x))

would be reasonable ... if somebody wants to submit a PR ... https://github.com/JuliaLang/julia/blob/master/base/int.jl#L508-L510

Stefan Karpinski: @KristofferC: the reason I don’t like reinterpret is that it forces the type of the input and the result to have the same size but this is a perfectly well-defined operation even for different sizes.

This follows from a discussion on Slack; the implementation was suggested by @Keno.
base/int.jl Outdated
@@ -506,6 +506,7 @@ if nameof(@__MODULE__) === :Base
end

rem(x::T, ::Type{T}) where {T<:Integer} = x
rem(x::Signed, ::Type{Unsigned}) = x % unsigned(typeof(x))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably also have the opposite x % Signed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done and tests are also done

@Keno Keno added the needs tests Unit tests are required for this change label Feb 25, 2020
@JeffreySarnoff
Copy link
Contributor Author

Covered News.md, src/int.jl, test/int.jl for both x::Signed % Unsigned and x::Unsigned % Signed.
When this builds, and there are no further notes, please mash-as-one these commits.

@JeffreySarnoff JeffreySarnoff changed the title rem(x::Signed, ::Type{Unsigned}) tests for x::Signed % Unsigned Feb 25, 2020
@JeffreySarnoff JeffreySarnoff changed the title tests for x::Signed % Unsigned tests for x::Signed % Unsigned Feb 25, 2020
@JeffreySarnoff JeffreySarnoff changed the title tests for x::Signed % Unsigned x % Unsigned, x % Signed Feb 25, 2020
test/int.jl Outdated
@@ -266,6 +266,14 @@ end
U in [Base.BitInteger_types..., BigInt]
@test typeof(rand(U(0):U(127)) % T) === T
end
@testset "x::Signed % Unsigned returns a T, T = $T" for T in [Base.BitSigned_types...]
@test typeof(rand(T) % Unsigned) <: Unsigned
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather this test a few interesting cases instead (or in addition). For example typemax, typemin, -typemax. Also should test for x a BigInt, since that's an interesting one for which there is no unsigned type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think? Should BigInt(x) % Unsigned always throw an error, or should it handle values that only require 128 signed bits and throw and error for larger values?

Copy link
Contributor Author

@JeffreySarnoff JeffreySarnoff Feb 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was surprised that unsigned(Int64) == UInt64 but signed(UInt64) errors. We should remedy that, defining signed(x::T) where {T<:Base.BitUnsigned_types} to be the size corresponding BitSigned_types. I am making that change in int.jl as these BitInteger pairs should be with the BitInteger, BitSigned, BitUnsigned type declarations.
Half of them, the unsigned(T) where {T<:BitSigned} are given in multinverse.jl, which is its own module. I am removing those lines from that file and adding unsigned to the short list Base sourced things given with using Base:.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why support x::BigInt % Unsigned? It doesn't really make sense, because BigInt is not a bitstype and these conversions are from one n-byte integer type (signed or unsigned) to another n-byte integer type (unsigned or signed).
The error that happens .. no method matching Unsigned(::BigInt) seems appropriate,

Copy link
Member

@StefanKarpinski StefanKarpinski Feb 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed-size integer types represent modular classes of integer values. When you do x % T you are saying "give me the element of that represents the modular class of x in T. That is a perfectly well-defined operation for x::BigInt % Unsigned. In fact, this operation is always well-defined because x represents a specific integer value which must belongs to exactly one modular class in T and the result is the unique value in T representing that class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in x::BigInt % Unsigned, how to choose the destination type? We won't define an UnsignedBigInt type. And we can't return x because it's expected that (x % Unsigned) isa Unsigned.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see what you mean—I interpreted that as being for some specific T <: Unsigned but the issue is for the literal type Unsigned. That is quite unclear so agree about it being an error. The alternative would be to choose UInt as the "default unsigned type" but that's questionable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative would be to choose UInt as the "default unsigned type" but that's questionable.

I guess it's always possible to add it later if there is demand for that, but while this makes some sense, this seems a bit weird to return a smaller type than Int128(1) % Unsigned.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Which leads to an issue if/when we ever add (U)Int256 types.

@Keno Keno removed the needs tests Unit tests are required for this change label Feb 25, 2020
@JeffreySarnoff
Copy link
Contributor Author

JeffreySarnoff commented Feb 25, 2020

With Base and this PR with or without (U)Int256 bitstypes:

  • x::Signed

    • x === x % Signed
  • x::Unsigned

    • x % Unsigned
  • x::BitInteger

    • isa(x % Signed, Signed) & isa(x % Unsigned, Unsigned)
    • sizeof(x) === sizeof(x % Signed) === sizeof(x % Unsigned)
  • x::BitUnsigned

    • isa(x, Unsigned) & isa(x, BitInteger)
    • x === x % Signed % Unsigned
  • x::BitSigned

    • isa(x, Signed) & isa(x, BitInteger)
    • x === x % Unsigned % Signed

A package exporting a new pair of Signed and Unsigned integer types (S and U) can follow suit by defining a corresponding pair of convert functions and adding

Base.unsigned(S) = U
Base.signed(U) = S
Base.rem(x::S, ::Type{U}) = convert(U, x)
Base.rem(x::U, ::Type{S}) = convert(S, x)

(this works for bitstypes, [mutable] structs, and external library types)

@Keno Keno added the triage This should be discussed on a triage call label Feb 25, 2020
@Keno
Copy link
Member

Keno commented Feb 25, 2020

I think it's worth discussing this on triage, particularly for the BigInt case.

base/int.jl Outdated
@@ -44,6 +44,22 @@ const BitUnsigned64T = Union{Type{UInt8}, Type{UInt16}, Type{UInt32}, Type{UIn

const BitIntegerType = Union{map(T->Type{T}, BitInteger_types)...}

# interconvert equilength BitInteger types

unsigned(::Type{Int8}) = UInt8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should already be defined. Why is this necessary?

Copy link
Contributor Author

@JeffreySarnoff JeffreySarnoff Feb 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsigned(::Type{IntN}) = UIntN are defined in Base/multinverses.jl.
signed(::Type{UIntN}) = IntN are not defined.
I thought the two sets of definitions belonged in the same place, and that Int.jl was more appropriate than multinverses.jl. I moved the defs from multinverses.jl to Int.jl and added the defs for signed(_) there. I added unsigned to the using Base: line in multinverses.jl.

I can revert this and I could add the signed(_) defs into multinverses.jl. multiinverses is a module, would I need to export signed after adding it there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all that is undone

JeffreySarnoff and others added 2 commits March 3, 2020 16:24
Co-Authored-By: Matt Bauman <mbauman@gmail.com>
@JeffreySarnoff
Copy link
Contributor Author

😄

@JeffreySarnoff
Copy link
Contributor Author

JeffreySarnoff commented Mar 6, 2020

error is unrelated

Error in testset Serialization:
Error During Test at none:1
  Test threw exception
  Expression: Serialization
  ProcessExitedException(3)

@StefanKarpinski
Copy link
Member

What's the status of this? Done? Waiting for CI to pass?

@JeffreySarnoff
Copy link
Contributor Author

This is done and it is good to go now

NEWS.md Outdated
@@ -92,6 +92,9 @@ New library features

* `isapprox` (or `≈`) now has a one-argument "curried" method `isapprox(x)` which returns a function, like `isequal` (or `==`)` ([#32305]).
* `Ref{NTuple{N,T}}` can be passed to `Ptr{T}`/`Ref{T}` `ccall` signatures ([#34199])
* `accumulate`, `cumsum`, and `cumprod` now support `Tuple` ([#34654]).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this line, which must come from rebasing (the correct version is indicated 3 lines below)

test/int.jl Outdated
@test unsigned(S) === U
end
end
@testset "x::[Un]Signed % [Un]Signed returns x" begin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just personal taste, but I would join the three @testsets together, as they contain the same loop, so it's a bit of a ceremony overhead.

@rfourquet
Copy link
Member

This might need updating the docstrings (I know that unsigned(::Type) is currently undocumented, but this PR publicizes the feature in the NEWS.md).

@JeffreySarnoff
Copy link
Contributor Author

JeffreySarnoff commented Mar 27, 2020

arrgh -- ok about the doc strings --

@JeffreySarnoff
Copy link
Contributor Author

I added the docstring for signed(T) where e.g signed(::Type{UInt8}) = Int8 appears.
I do not know where the corresponding unsigned(T) are, so I added that docstring before the signed(T) section using """ ... """ unsigned.

@JeffreySarnoff
Copy link
Contributor Author

@StefanKarpinski there it be ✅ .. please merge before something else changes :))

@StefanKarpinski StefanKarpinski merged commit d31b047 into JuliaLang:master Mar 27, 2020
@JeffreySarnoff JeffreySarnoff deleted the jas/remUnsigned branch March 27, 2020 20:07
oxinabox pushed a commit to oxinabox/julia that referenced this pull request Apr 8, 2020
ravibitsgoa pushed a commit to ravibitsgoa/julia that referenced this pull request Apr 9, 2020
KristofferC pushed a commit that referenced this pull request Apr 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants