x86 Machine Code (x87 FPU), 35 bytes
D9 EE 31 F6 8D 7E 0A 46 89 F0 99 F7 F7 83 FA 09 74 F5
85 C0 75 F4 89 33 DB 03 D9 E8 DE F1 DE C1 E2 E5 C3
The above bytes define a function that computes the nth partial sum of the Kempner series. The function defines a custom calling convention, whereby it accepts two arguments:
- The value n is passed in the
ecx
register (this is an unsigned 32-bit integer value).
- The address of a "scratch" memory buffer is passed in the
ebx
register (the buffer's size must be at least 4 bytes in length).
The function returns a single result, the nth partial sum, at the top of the x87 FPU stack (st(0)
).
Try it online!
In ungolfed assembly language mnemonics:
; Computes the nth partial sum of the Kempner series.
; Input(s):
; ecx = n
; ebx = address of DWORD scratch buffer
; Output(s):
; st(0) = nth partial sum
; Clobber(s):
; eax, [ebx], ecx, edx, esi, edi, st, flags
Kempner:
D9 EE fldz ; start with partial sum == 0.0 (at top of x87)
31 F6 xor esi, esi ; esi = 0
8D 7E 0A lea edi, [esi + 10] ; edi = 10
PartialSum:
46 inc esi ; esi += 1
89 F0 mov eax, esi ; eax = esi
CheckDigit:
99 cdq ; zero edx (normally, prefer xor edx, edx)
F7 F7 div edi ; edx:eax / edi
83 FA 09 cmp edx, 9 ; remainder == 9?
74 F5 je PartialSum ; skip if digit was a 9
85 C0 test eax, eax ; quotient == 0?
75 F4 jnz CheckDigit ; loop if more digits to check
89 33 mov DWORD PTR [ebx], esi ; store esi into scratch memory buffer
DB 03 fild DWORD PTR [ebx] ; push value from scratch memory buffer to top of x87
D9 E8 fld1 ; push 1.0 to top of x87
DE F1 fdivrp st(1), st(0) ; calculate 1.0 / esi
DE C1 faddp st(1), st(0) ; accumulate partial sum (result is at top of x87)
E2 E3 loop PartialSum ; decrement ecx (n), and continue looping if non-zero
C3 ret
[BONUS:] x86 Machine Code (AVX), 44 bytes
31 F6 46 0F 57 C0 8D 7E 09 F3 0F 2A D6 4E 46 89 F0 99 F7 F7 83 FA
09 74 F5 85 C0 75 F4 C5 F2 2A CE C5 EA 5E C9 C5 FA 58 C1 E2 E3 C3
In ungolfed assembly language mnemonics:
; Computes the nth partial sum of the Kempner series.
; Input(s):
; ecx = n
; Output(s):
; xmm0 = nth partial sum
; Clobber(s):
; eax, ecx, edx, esi, edi, xmm0, xmm1, xmm2, flags
Kempner:
31 F6 xor esi, esi ; \ set ESI to 1
46 inc esi ; /
0F 57 C0 xorps xmm0, xmm0 ; zero XMM0
8D 7E 09 lea edi, [esi + 9] ; set EDI to 10
F3 0F 2A D6 cvtsi2ss xmm2, esi ; set XMM2 to 1 (from ESI)
4E dec esi ; set ESI back to 0
PartialSum:
46 inc esi
89 F0 mov eax, esi
CheckDigit:
99 cdq
F7 F7 div edi
83 FA 09 cmp edx, 9
74 F5 je PartialSum
85 C0 test eax, eax
75 F4 jne CheckDigit
C5 F2 2A CE vcvtsi2ss xmm1, xmm1, esi
C5 EA 5E C9 vdivss xmm1, xmm2, xmm1
C5 FA 58 C1 vaddss xmm0, xmm0, xmm1
E2 E3 loop PartialSum
C3 ret
This is larger, since the AVX instructions require more bytes to encode than the x87 instructions. It is essentially the direct translation of the above function targeting the x87 FPU, except that I had to be a bit clever when loading "1.0" in the AVX (xmm2
) register. Basically, I eagerly set esi
to 1, load it into xmm2
using the convert-from-integer instruction, and then set esi
back to 0. Since inc
and dec
are 1-byte instructions, this is the cheapest way that I could find.