Fastest Way To Take The Average Of Two Signed Integers In X86 Assembly?
Suppose we have two register-length2 signed1 integers, say
b. We want to compute the value
(a + b) / 2, either rounded up, down, towards zero, or away from zero, whichever way is easier (i.e. we do not care about the rounding direction).
The result is another register-length signed integer (it is clear that the average must be within the range of a register-length signed integer).
What is the fastest way to perform this computation?
You may choose which registers the two integers will initially be in, and which register the average ends up being in.
Footnote 1: For unsigned integers, we can do it in two instructions. This is perhaps the fastest way, although rotate-through-carry is more than 1 uop on Intel CPUs. But only a couple when the count is only 1. An answer on a Q&A about unsigned mean discusses the efficiency.
add rdi, rsi rcr rdi, 1
The two numbers start in
rsi, and the average ends up in
rdi. But for signed numbers,
-1 + 3 would set CF, and rotate a
1 into the sign bit. Not giving the correct answer of
Footnote 2: I specified register-length signed integers so that we can't simply sign extend the integers with a
The closest I've got towards a solution uses four instructions, one of them an
rcr that's 3 uops on Intel, 1 on AMD Zen (https://uops.info/):
add rdi, rsi setge al sub al, 1 # CF = !(ge) = !(SF==OF) rcr rdi, 1 # shift CF into the top of (a+b)>>1
I think a shorter solution probably lies in combining the middle two instructions in some way, i.e. performing
CF ← SF ≠ OF.
I've seen this question, but that's not x86-specific and none of the answers seem to compile to something as good as my solution.
Depending on how we interpret your lax rounding requirements, the following may be acceptable:
sar rdi, 1 sar rsi, 1 adc rdi, rsi
This effectively divides both inputs by 2, adds the results, and adds 1 more if
rsi was odd. (Remember that
sar sets the carry flag according to the last bit shifted out.)
sar rounds to minus infinity, the result of this algorithm is:
exactly correct if rdi, rsi are both even or both odd
rounded down (toward minus infinity) if rdi is odd and rsi is even
rounded up (toward plus infinity) if rdi is even and rsi is odd
As a bonus, for random inputs, the average rounding error is zero.
It should be 3 uops on a typical CPU, with a latency of 2 cycles since the two
sar are independent.
- → What causes Visual Studio to fail to load an assembly incorrectly?
- → How do I make the manifest of a .net assembly private?
- → How to compile a DLL that does not require an external manifest file?
- → The located assembly's manifest definition does not match the assembly reference
- → Register DLL in GAC without Assembly Manifest
- → Visual Studio 2005 - C++ - What controls the manifest creation
- → How do I add an Implementation-Version value to a jar manifest using Maven's assembly plugin?
- → how to read the assembly manifest without loading the .dll
- → Errors creating WebPart subclass in another assembly
- → How to fix "Referenced assembly does not have a strong name" error
- → What is the fastest way to convert float to int on x86
- → ASP.NET Control/Page Library Question