#x86 Machine Code, 12 bytes
Iterative solution; 0-indexed
31 C0
40
8D 50 01
29 D0
92
E2 FB
C3
The above function takes a single input, n (passed in the ECX
register, following Microsoft's fastcall calling convention), and returns the nth number of the Fibtraction sequence in the EAX
register.
The implementation is based on the well-known trick for calculating a Fibonacci number using two variables, which Snowman's answer in Java reminded me of.
Not only is this solution much smaller in size than my first attempt, it will also be much faster because it is iterative, rather than recursive. Theoretical comp-sci's love for recursion is foiled once more!
Ungolfed assembly mnemonics:
Fibtraction:
xor eax, eax
inc eax ; EAX = 1 (XOR+INC are 3 bytes; same as PUSH+POP)
lea edx, [eax + 1] ; EDX = 2 (LEA is 3 bytes; same as PUSH+POP)
Iterate:
sub eax, edx ; EAX = EAX - EDX
xchg eax, edx ; swap EAX and EDX
loop Iterate ; decrement ECX and keep looping as long as ECX != 0
ret ; return, with result in EAX
A couple of things worth pointing out here…
We start by loading the constants 1 and 2 into registers. These are our starting values. The standard way, of course, to load constants into registers is mov reg, imm
. That's fast, but it takes a whopping 5 bytes to encode. So, for code-golfing purposes, we make use of various other tricks to load values into registers. A 2-byte PUSH
that pushes the immediate value onto the stack, followed by a 1-byte POP
that pops the value off the top of the stack and into a register, is the workhorse trick—very common and very useful. But, if you are just setting the register to 1, you can do XOR
+INC
, which is the same 3 bytes. And, if you already have the value you want in a register, then you can build off of it using the magic LEA
instruction, which is also only 3 bytes. Both of those tricks are showcased here, since they are more interesting and somewhat faster than the good old PUSH
+POP
.
Inside of the body of the loop, we do a pretty neat optimization. The fundamental operation we're doing here is:
edx = (eax - edx)
eax -= edx
which would naively be translated to (indeed, this is what most C compilers do):
sub eax, edx ; 2 bytes
mov ebx, edx ; 2 bytes
mov edx, eax ; 2 bytes
mov eax, ebx ; 2 bytes
or, somewhat better—rewrite the subtraction as addition of a negative so that the operand order can be reversed, allowing the result to end up in the desired register without having to clobber a temporary register (ICC is able to apply this optimization):
neg edx ; 2 bytes
add edx, eax ; edx = (eax - edx) ; 2 bytes
sub eax, edx ; eax -= edx ; 2 bytes
but I have written it as:
sub eax, edx ; 2 bytes
xchg eax, edx ; 1 byte
which is conceptually similar to the naive version (only one subtraction is done, then some moves), but much more compact because of the XCHG
instruction. This avoids clobbering a register, and decreases the size of the code. (I guess you could have equally well used an XOR
to avoid clobbering a register—the famous move-without-a-temporary trick, but XCHG
is 1 fewer byte for golfing purposes.)
Now, to be fair, the MOV
s are probably much faster in real-world code, especially on modern processors that can elide moves in the front end via register renaming. Exchanges can't be elided, and aren't known for being particularly fast. If it weren't for register renaming, ICC's code (the add-a-negative approach) would probably be the fastest in the real-world, since it's compact and arithmetic operations are extremely efficient.
but is avoided in modern code (and by all compilers) because it's actually slower than the above sequence. It's also less versatile, because it has the ECX
register hard-coded as the counter. However, for code-golf purposes, it's great! And since we used a calling convention that passes the parameter in ECX
, we don't even have to do anything!
(If you wanted to use a different calling convention, you'd just have to do a MOV
or XCHG
at the very top of the function to copy the parameter into ECX
; or, you could swap out the LOOP
instruction for the expanded form shown above. Either of these would only cost a couple of bytes, maximum, so this optimization isn't really saving us much, but I think it does earn a couple of style points! :-)
Similarly, if you wanted to write this for x86-64, like my original answer, which uses a different calling convention, it would be easy to adapt. Either of these will do it, and both are only 14 bytes:
Fibtraction_64_A | Fibtraction_64_B:
push 1 | mov ecx, edi
pop rax | push 1
lea edx, [rax+1] | pop rax
Iterate: | lea edx, [rax+1]
sub eax, edx | Iterate:
xchg eax, edx | sub eax, edx
dec edi | xchg eax, edx
jg Iterate | loop Iterate
ret | ret