Sempre dá para melhorar um bocado…

Eis duas formas de escrever uma função. A segunda forma fica meio “ofuscada”, mas, como veremos, o tamanho e performance valem a pena… A função é usada no T50, que deixei no primeiro modelo para ficar mais legível:

static const char *suffixes[] = { "st", "nd", "rd", "th" };

const char *get_ordinal_suffix(unsigned int n)
{
  /* 11, 12 & 13 have 'th' suffix, not 'st, nd or rd'. */
  if ((n < 11) || (n > 13))
    switch (n % 10)
    {
    case 1:
      return suffixes[0];
    case 2:
      return suffixes[1];
    case 3:
      return suffixes[2];
    }

  return suffixes[3];
}

Bem simples e aparentemente direto… O código gerado é esse:

bits 64

section .rodata

suffix1:   db "st",0
suffix2:   db "nd",0
suffix3:   db "rd",0
suffix4:   db "th",0

section .text

global get_oridinal_suffix
get_ordinal_suffix:
  lea  edx,[rdi-11]
  mov  eax,suffix4
  cmp  edx,2
  jbe  .L1

  ; Isso é n = n % 10!
  mov  eax,edi
  mov  edx,3435973837
  mul  edx
  shr  edx,3
  lea  eax,[rdx + rdx*4]
  add  eax,eax
  sub  edi,eax

  mov  eax,suffix2
  cmp  edi,2
  je   .L1
  mov  eax,suffix3
  cmp  edi,3
  je   .L1
  cmp  edi,1
  mov  edx,suffix4
  mov  eax,suffix1
  cmovne rax,rdx

.L1:
  ret

Não fique espantado com a mágica que o compilador faz para calcular o resto inteiro da divisão por 10. Ele prefere usar uma multiplicação do que divisão por questão de performance (multiplicações são lentas, mas divisões são verdadeiras lesmas!).

Na rotina acima não há muito como escapar do cálculo do resto da divisão por 10 por causa das duas exceções (11 e 12), mas podemos melhorar um cadinho o switch usando um macete:

const char *get_ordinal_suffix2(unsigned int n)
{
  if (n >= 11 && n <= 13) return suffixes[3];

  return suffixes["\003\000\001\002\003"
                  "\003\003\003\003\003"[n % 10]];
}

O uso de uma string literal como endereço base para a própria string é perfeitamente válido em C já que a string literal nada mais é do que a declaração dos seus bytes e o retorno é o endereço base. Note que cada “caracter” do array foi codificado como sendo o índice de outro array (suffixes) e, por isso, a rotina final será assim:

...
section .rodata

_lstr: db 3,0,1,2,3,3,3,3,3,3,3,3,3,3,3,3,0
suffixes: dq suffix1, suffix2, suffix3, suffix4

section .text
...
  global get_ordinal_suffix2
get_ordinal_suffix2:
  lea  edx,[rdi-11]
  mov  eax,suffix4
  cmp  edx,2
  jbe  .L1

  ; Isso ainda é n = n % 10!
  mov  eax,edi
  mov  edx,3435973837
  mul  edx
  shr  edx,3
  lea  eax,[rdx + rdx*4]
  add  eax,eax
  sub  edi,eax

  movsx eax,byte [_lstr + rdi]
  mov  rax,[suffixes + rax*8]
.L1:
  ret  

As 10 instruções do switch foram substituídas por apenas duas!

Aliás, quanto ao “macete” da string, acima, podemos fazer uso de coisas como essas sem problemas:

char c1 = "frederico"[5]; // c1 = 'r'.
char c2 = 5["frederico"]; // c2 = 'r';
char k = (n % 10)["9876543210"]; // desde que n seja unsigned.

Todas essas construções são perfeitamente válidas…

Anúncios