x86-64 Machine Code, 30 bytes
31 C0 99 8B 4C B7 FC F6 C1 01 74 04 01 CA EB 02 01 C8 FF CE 75 ED 29 D0 99 31 D0 29 D0 C3
The above code defines a function that accepts a list/array of integer digits and returns the absolute difference between the sum of its even digits and the sum of its odd digits.
As in C, assembly language doesn't implement lists or arrays as first-class types, but rather represents them as a combination of a pointer and a length. Therefore, I have arranged for this function to accept two parameters: the first is a pointer to the beginning of the list of digits, and the second is an integer specifying the total length of the list (total number of digits, one-indexed).
The function conforms to the System V AMD64 calling convention, which is standard on Gnu/UNIX systems. In particular, the first parameter (pointer to the beginning of the list) is passed in RDI
(as this is 64-bit code, it is a 64-bit pointer), and the second parameter (length of the list) is passed in ESI
(this is only a 32-bit value, because that's more than enough digits to play with, and naturally it is assumed to be non-zero). The result is returned in the EAX
register.
If it's any clearer, this would be the C prototype (and you can use this to call the function from C):
int OddsAndEvens(int *ptrDigits, int length);
Ungolfed assembly mnemonics:
; parameter 1 (RDI) == pointer to list of integer digits
; parameter 2 (ESI) == number of integer digits in list (assumes non-zero, of course)
OddsAndEvens:
xor eax, eax ; EAX = 0 (accumulator for evens)
cdq ; EDX = 0 (accumulator for odds)
.IterateDigits:
mov ecx, [rdi+rsi*4-4] ; load next digit from list
test cl, 1 ; test last bit to see if even or odd
jz .IsEven ; jump if last bit == 0 (even)
.IsOdd: ; fall through if last bit != 0 (odd)
add edx, ecx ; add value to odds accumulator
jmp .Continue ; keep looping
.IsEven:
add eax, ecx ; add value to evens accumulator
.Continue: ; fall through
dec esi ; decrement count of digits in list
jnz .IterateDigits ; keep looping as long as there are digits left
sub eax, edx ; subtract odds accumulator from evens accumulator
; abs
cdq ; sign-extend EAX into EDX
xor eax, edx ; XOR sign bit in with the number
sub eax, edx ; subtract sign bit
ret ; return with final result in EAX
Here's a brief walk-through of the code:
- First, we zero out the
EAX
and EDX
registers, which will be used to hold the sum totals of even and odd digits. The EAX
register is cleared by XOR
ing it with itself (2 bytes), and then the EDX
register is cleared by sign-extending the EAX into it (CDQ
, 1 byte).
Then, we go into the loop that iterates through all of the digits passed in the array. It retrieves a digit, tests to see if it is even or odd (by testing the least-significant bit, which will be 0 if the value is even or 1 if it is odd), and then jumps or falls through accordingly, adding that value to the appropriate accumulator. At the bottom of the loop, we decrement the digit counter (ESI
) and continue looping as long as it is non-zero (i.e., as long as there are more digits left in the list to be retrieved).
The only thing tricky here is the initial MOV instruction, which uses the most complex addressing mode possible on x86.* It takes RDI
as the base register (the pointer to the beginning of the list), scales RSI
(the length counter, which serves as the index) by 4 (the size of an integer, in bytes) and adds that to the base, and then subtracts 4 from the total (because the length counter is one-based and we need the offset to be zero-based). This gives the address of the digit in the array, which is then loaded into the ECX
register.
After the loop has finished, we do the subtraction of the odds from the evens (EAX -= EDX
).
Finally, we compute the absolute value using a common trick—the same one used by most C compilers for the abs
function. I won't go into details about how this trick works here; see code comments for hints, or do a web search.
__
* The code can be re-written to use simpler addressing modes, but it doesn't make it any shorter. I was able to come up with an alternative implementation that dereferenced RDI
and incremented it by 8 each time through the loop, but because you still have to decrement the counter in ESI
, this turned out to be the same 30 bytes. What had initially given me hope is that add eax, DWORD PTR [rdi]
is only 2 bytes, the same as adding two enregistered values. Here is that implementation, if only to save anyone attempting to outgolf me some effort :-)
OddsAndEvens_Alt:
31 C0 xor eax, eax
99 cdq
.IterateDigits:
F6 07 01 test BYTE PTR [rdi], 1
74 04 je .IsEven
.IsOdd:
03 17 add edx, DWORD PTR [rdi]
EB 02 jmp .Continue
.IsEven:
03 07 add eax, DWORD PTR [rdi]
.Continue:
48 83 C7 08 add rdi, 8
FF CE dec esi
75 ED jne .IterateDigits
29 D0 sub eax, edx
99 cdq
31 D0 xor eax, edx
29 D0 sub eax, edx
C3 ret