Most operations on a computer are not reversible… meaning that once done, you can never go back. For example, if you divide integers by 2 to get a new integer, some information is lost (whether the original number was odd or even). With fixed-size arithmetic, multiplying by two is also irreversible because you lose the value of the most significant bit.

Let us consider fixed-size integers (say 32-bit integers). We want functions that take as input one fixed integer and output another fixed-size integer.

How many reversible operations do we know?

- Trivially, you can add or subtract by a fixed quantity. To reverse the operation, just flip the sign or switch from add to subtract.
- You can compute the exclusive or (XOR) with a fixed quantity. This operation is its own inverse.
- You can multiply by an odd integer. You’d think that reversing such a multiplication could be accomplished by a simple integer division, but that is not the case. Still, it is reversible. By extension, the carryless (or polynomial) multiplication supported by modern processors can also be reversible.
- You can rotate the bits right or left using the
`ror`or`rol`instructions on an Intel processor or with a couple of shifts such as`(x >>> (-b)) | ( x << b))`or`(x << (-b)) | ( x >>> b))`in Java. To reverse, just rotate the other way. If you care about signed integers, there is an interesting variation that is also invertible: the "signed" rotate (defined as`(x >> (-b)) | ( x << b))`in Java) which propagates the signed bit of two's complement encoding. - You can XOR the rotations of a value as long as you have an odd number of them.
- You can compute the addition of a value with its shifts (e.g.,
`x + ( x << a)`). This is somewhat equivalent to multiplication by an odd integer. - You can compute the XOR of a value with its shifts (e.g.,
`x ^ ( x >> a)`or`x ^ ( x << a)`). This is somewhat equivalent to a carryless (or polynomial) multiplication. - You can reverse the bytes of an integer (
`bswap`on Intel processors). This function is its own inverse. You can also reverse the order of the bits (`rbit`on ARM processors). - (New!) Jukka Suomela points out that you can do bit interleaving (e.g., interleave the least significant 16 bits with most significant 16 bits) with instructions such as
`pdep`on Intel processors. You can also compute the lexicographically-next-bit permutation.

You can then compose these operations, generating new reversible operations.

**Pedantic note**: some of these operations are not reversible on some hardware and in some programming languages. For example, signed integer overflows are undefined in C and C++.

Is the x86-64 CRC instruction reversible on 32-bit unsigned integers?

Good question. I suspect that it is equivalent to a 32-bit carryless multiplication with 0x1edc6f41, an invertible transformation. There is also a bit reversal (bit reflection), also invertible.

aesenc

Hrm.

How many of these operations are reversible for all integer inputs?

I’d definitely not consider shifts reversible. I’d probably not even consider addition/subtraction reversible as they aren’t reversible for all inputs. They’re “sometimes reversible” but not “always reversible”.

They are always invertible, for all integer inputs. There are programming-language issues that may interfere, such as undefined behaviors under overflow for signed integers, but they are technical issues.

Encryption is reversible, although often an IV or other padding are added. Do you think most encryption methods, or at least those that don’t require any more bits for the encrypted output, can be implemented using only the ‘primitives’ you listed?

I would be very interested in knowing whether some encryption techniques use primitives we did not include.

I guess you’re looking for operations which (maybe holding some arguments fixed) take an intX_t to an intX_t (not a larger type)?

Right. If you are allowed to generate larger types, then it is a bit too easy.

Taking the int to be a vector over Z_2, multiplication by a square non-singular matrix over Z_2.

How many?

Zero, I don’t give a sh*t…

A function mapping from {1, …, 2^n} to {1, …, 2^n} is reversible if and only if it is a one-to-one mapping. Any such mapping f(x) can be represented by the sequence (f(1), …, f(2^n)). There are (2^n)! such one-to-one sequences. That is, the reversible functions are simply permutations (shufflings) of the numbers from 1 to 2^n. For example, if n = 2, there are (2^2)! = 4! = 24 reversible functions.

How many reversible operations do we know? For 32 bit integers, we know (2^32)! reversible operations.

I guess what you really want to know is, how many of these reversible operations are easy to define? I suppose circuit complexity might be the natural way to define “easy” reversible functions (https://en.wikipedia.org/wiki/Circuit_complexity). If you specify a precise measure of circuit complexity and set a threshold on circuit complexity, there would be an exact answer to the question of how many of the (2^32)! reversible functions have a complexity below the given threshold. However, calculating the exact answer is likely to be very time consuming.

This seems like a rather easy question. Did I miss something? All of these (2^32)! functions could be defined with NOR.

As you point out, there are 2^64 ! permutations on 64-bit words, and that’s about 2^64^{2^64} permutations. To identify one of them, I need about 2^{70} bits. We are talking about exabytes. So it is not practical to address reversible functions as an element of the set of all permutations.

I submit to you that this set might as well be infinite.

In practical software, we see a tiny fraction of all these reversible functions. The question is which ones do we commonly see?

What motivates this question? Why is this interesting? Are you trying to reduce energy consumption below the von Neumann-Landauer limit?

https://en.wikipedia.org/wiki/Reversible_computing

If I asked a programmer to write a reversible function over machine words… I’d get examples from the above blog post… plus, maybe, some minor variations. There are only so many ways to write reversible functions in software today. This comes from the fact that we work with a fairly limited set of operations.

Some of them are not immediately obvious. For example, XORing an odd number of rotations is invertible… but not an even number.

Why would anyone care about this? Well… these are used all over… to generate random numbers, to hash numbers and so forth.

Bitwise not ~

That’s just an instance of XOR with a constant

Rotate needs a tweak: (x >>> (32-b)) | ( x << b)

Mr Pedant: Care to explain?

If you are working with ints in Java, then

(x >>> (32-b)) | ( x < < b)is strictly equivalent to(x >>> (-b)) | ( x < < b). For longs, you have that(x >>> (64-b)) | ( x < < b)is strictly equivalent to(x >>> (-b)) | ( x < < b). So, the better form is(x >>> (-b)) | ( x < < b)because it works the same no matter which words you are using.In C, it is a different matter because it has undefined behaviors. Java does not.

Oops, my mistake, you are correct.

TIL In Java, the >>> operator masks the shift amount by 31.

It virtually masks the shift, yes. In reality, at least on an Intel processor, there is no shift needed since the machine instruction only uses the least significant bits during a shift.

It is not just Java. Go, JavaScript… also work the same way.

Oh sure, I’m familiar with the x86 assembly opcodes SAR and SHR. It’s just surprising to see that limitation of a particular CPU reflected in a high level language.

My assumption was that in a high level language ((x >>> a) >>> b) === (x >>> (a+b))

And indeed, in python that is the case:

python -c “print (2**65537)>>65536” # == 2

‘Go’ seems to work the same

fmt.Println(7 >> 65536) // == 0

Source: https://golang.org/ref/spec https://golang.org

Java/Javascript OTOH? My mistake… TIL.

Here are a couple of odd ones, based on TBM. x + c * (x & -x) with an even c, which is invertible because it doesn’t change what x&-x is so the same amount can be subtracted again. Of course the addition can be replaced with XOR. Here’s “proof” (not really, but I believe the bot) for c=2, http://haroldbot.nl/?q=let+c+%3D+2%2C+y+%3D+x+%2B+c+*+%28x+%26+-x%29+in+y+-+c+*+%28y+%26+-y%29 (dev version can handle unbound c)

x + c * ((x ^ (x + 1)) + 1) seems to work for any c (if I believe the bot), for a similar reason.

I can’t think of a use for them, but I found them interesting because they don’t just add a constant, but something that can be recovered.

Seems worth explicitly noting that the inverse of multiply by odd K is the product by its modulo inverse.

I wonder if there is there any accessible coverage of bijections as properties of F_2 matrices.