x86-64 machine code function, 40 bytes.
Or 37 bytes if 0 vs. non-zero is allowed as "truthy", like strcmp.
Thanks to Karl Napf's C answer for the bitmap idea, which x86 can do very efficiently with BTS.
Function signature: _Bool cube_digits_same(uint64_t n);
, using the x86-64 System V ABI. (n
in RDI, boolean return value (0 or 1) in AL).
_Bool
is defined by ISO C11, and is typically used by #include <stdbool.h>
to define bool
with the same semantics as C++ bool
.
Potential savings:
- 3 bytes: Returning the inverse condition (non-zero if there's a difference). Or from inline asm: returning a flag condition (which is possible with gcc6)
- 1 byte: If we could clobber EBX (doing so would give this function a non-standard calling convention). (could do that from inline asm)
- 1 byte: the RET instruction (from inline asm)
All of these are possible if this was an inline-asm fragment instead of a function, which would make it 35 bytes for inline-asm.
0000000000000000 <cube_digits_same>:
0: 89 f8 mov eax,edi
2: 48 f7 e7 mul rdi # can't avoid a REX prefix: 2642245^2 doesn't fit in 32 bits
5: 48 f7 e7 mul rdi # rax = n^3, rdx=0
8: 44 8d 52 0a lea r10d,[rdx+0xa] # EBX would save a REX prefix, but it's call-preserved in this ABI.
c: 8d 4a 02 lea ecx,[rdx+0x2]
000000000000000f <cube_digits_same.repeat>:
f: 31 f6 xor esi,esi
0000000000000011 <cube_digits_same.cube_digits>:
11: 31 d2 xor edx,edx
13: 49 f7 f2 div r10 ; rax = quotient. rdx=LSB digit
16: 0f ab d6 bts esi,edx ; esi |= 1<<edx
19: 48 85 c0 test rax,rax ; Can't skip the REX: (2^16 * 10)^3 / 10 has all-zero in the low 32.
1c: 75 f3 jne 11 <cube_digits_same.cube_digits>
; 1st iter: 2nd iter: both:
1e: 96 xchg esi,eax ; eax=n^3 bitmap eax=n bitmap esi=0
1f: 97 xchg edi,eax ; edi=n^3 bitmap, eax=n edi=n bmp, eax=n^3 bmp
20: e2 ed loop f <cube_digits_same.repeat>
22: 39 f8 cmp eax,edi
24: 0f 94 d0 sete al
;; The ABI says it's legal to leave garbage in the high bytes of RAX for narrow return values
;; so leaving the high 2 bits of the bitmap in AH is fine.
27: c3 ret
0x28: end of function.
LOOP seems like the smallest way to repeat once. I also looked at just repeating the loop (without REX prefixes, and a different bitmap register), but that's slightly larger. I also tried using PUSH RSI, and using test spl, 0xf
/ jz
to loop once (since the ABI requires that RSP is 16B aligned before CALL, so one push aligns it, and another misaligns it again). There's no test r32, imm8
encoding, so the smallest way was with a 4B TEST instruction (including a REX prefix) to test just the low byte of RSP against an imm8. Same size as LEA + LOOP, but with extra PUSH/POP instructions required.
Tested for all n in the test range, vs. steadybox's C implementation (since it uses a different algorithm). In the two cases of different results that I looked at, my code was correct and steadybox's was wrong. I think my code is correct for all n.
_Bool cube_digits_same(unsigned long long n);
#include <stdio.h>
#include <stdbool.h>
int main()
{
for(unsigned n=0 ; n<= 2642245 ; n++) {
bool c = f(n);
bool asm_result = cube_digits_same(n);
if (c!=asm_result)
printf("%u problem: c=%d asm=%d\n", n, (int)c, (int)asm_result);
}
}
The only lines printed have c=1 asm=0: false-positives for the C algorithm.
Also tested against a uint64_t
version of Karl's C implementation of the same algorithm, and the results match for all inputs.
2103869 -> True
. This (or a larger one) is necessary to test a language with along
datatype. \$\endgroup\$