Skip to main content
added 434 characters in body
Source Link
Cody Gray
  • 3.5k
  • 16
  • 28
deleted 1 character in body
Source Link
Cody Gray
  • 3.5k
  • 16
  • 28

Not only is this solution much smaller in size than my first attempt (it even competes with Mathematica using a built-in), it will also be much faster because it is iterative, rather than recursive. Theoretical comp-sci's love for recursion is foiled once more!

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, buttrick—but XCHG is 1 fewer byte for golfing purposes.)

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!

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.)

Not only is this solution much smaller in size than my first attempt (it even competes with Mathematica using a built-in), it will also be much faster because it is iterative, rather than recursive. Theoretical comp-sci's love for recursion is foiled once more!

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.)

Source Link
Cody Gray
  • 3.5k
  • 16
  • 28

#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 MOVs 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.

  • The LOOP instruction. This is an old CISC-style instruction that is equivalent to:

     dec  ecx
     jnz  Label
    

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