Indo a extremos para evitar usar ponto flutuante. Você deveria fazer isso também!

Eis um problema que preciso resolver para o meu mais novo projeto de brinquedo: Preciso determinar se uma imagem (stream de vídeo) tem aspecto mais próximo do aspect ratio \frac{4}{3} ou do \frac{16}{9}. A solução mais “simples” é simplesmente dividir o comprimento (width) pela altura (height) e comparar o resultado com uma fração de ‘meio termo”. O “meio termo”, é claro, é a média aritmética simples dos dois aspectos:

\displaystyle \frac{\frac{16}{9} + \frac{4}{3}}{2}=\frac{16+12}{9}\cdot\frac{1}{2}=\frac{28}{18}=\frac{14}{9}

Por que a fração de “meio termo”? É que o vídeo pode ter resolução não padronizada e, neste caso, colocarei “barras pretas” horizontais ou verticais, dependendo do aspecto. Assim, temos:

int is_letterbox(unsigned int width, unsigned int height)
{
  double ratio = ( double )width / height;

  return ratio <= 14.0 / 9.0;
}

Nada mais simples, né? Se o aspecto for menor ou igual a \frac{14}{9}, então encaro o vídeo como candidato para ser convertido para letterbox, ao invés de widescreen. Ahhh… sim… note que o operador de comparação (<=) tem menor precedência que o de divisão (/)… Por isso a expressão intera faz o que queremos!

O problema é que nem a divisão de \frac{width}{height}, nem a divisão \frac{14}{9} são exatas. Bem… a primeira até pode resultar num valor exato, mas o quociente constante de \frac{14}{9} não! Isso poderia causar um “falso positivo” no caso de teste por “menor que” e um “falso negativo” no caso do teste de igualdade… Mas, aqui temos um outro problema: Imagine que o vídeo tenha resolução de 1400×901. Isso dará um aspecto de \frac{1400}{901}, necessariamente, que é muito próximo de \frac{14}{9}.

Falei “necessariamente” porque para obter a manor fração possível podemos usar o “máximo divisor comum” para fazer a divisão do dividendo e divisor (inteiras), obtendo os menores valores. Por exemplo:

#include <stdio.h>
#include <stdlib.h>

static unsigned int gcd( unsigned int, unsigned int );

int main( int argc, char *argv[] )
{
  unsigned int w, h, d;

  if ( argc != 3 )
  {
    fputs( "ERROR\n", stderr );
    return EXIT_FAILURE;
  }

  w = atoi( argv[1] );
  h = atoi( argv[2] );

  d = gcd( w, h );

  printf( "%u/%u -> %u/%u\n",
          w, h, w / d, h / d );

  return EXIT_SUCCESS;
}

unsigned int gcd( unsigned int a, unsigned int b )
{
  unsigned int t;

  if ( a < b )
  {
    t = a;
    a = b;
    b = t;
  }

  if ( ! b )
    return 1;

  while ( b )
  {
    t = b;
    b = a % b;
    a = t;
  }

  return a;
}

Compilando e testando:

$ cc -o test test.c
$ ./test 1280 720
1280/720 -> 16/9
$ ./test 1400 901
1400/901 -> 1400/901
$ ./test 1400 920
1400/920 -> 35/23

Se formos comparar o numerador e o denominador isoladamente a coisa vai complicar um bocado. Note que mesmo numa resolução maluca diferente (1400×920) o aspecto ainda tem o dividendo e divisor bem diferentes (\frac{35}{23}).

De volta ao ensino fundamental:

Como sabemos que, por exemplo, \frac{2}{3} é maior que \frac{3}{5}? A solução que você aprendeu lá no ensino fundamental é converter ambas as frações ao mesmo denominador. Daí podemos comparar diretamente os numeradores, certo? Basta multiplicar ambos o numerador e o denominador de ambas as frações pelo denominador da outra fração:

\displaystyle\frac{2}{3}\,\texttt{e}\,\frac{3}{5}\rightarrow\frac{2\cdot5}{3\cdot5}\,\texttt{e}\,\frac{3\cdot3}{5\cdot3}\rightarrow\frac{10}{15}\,\texttt{e}\,\frac{9}{15}

Agora, com ambas as frações “normalizadas”, podemos comparar diretamente os numeradores. No caso, 10 é maior que 9 e, por isso, \frac{2}{3} é maior que \frac{3}{5}

Vocẽ reparou que não precisamos mais nos preocupar com os denominadores se simplesmente multiplicarmos um numerador pelo denominador da outra fração, né? Isso facilita um bocado a nossa rotina is_letterbox(), evitando o uso de ponto flutuante:

int is_letterbox( unsigned int width, unsigned int height )
{
  unsigned int n1, n2;

  /* Não precisamos calcular os denominadores! */
  n1 = width * 9;   // 9 de 14/9
  n2 = 14 * height; // height de width/height

  return n1 <= n2;
}

Comparando as duas rotinas:

Compilando com otimizações e gerando o código em assembly:

$ cc -O2 -mtune=native -S -masm=intel test.c

Olhem só isso, com ambos os códigos lado a lado:

is_letterbox_fp:                  is_letterbox_int:      
  mov edi, edi                      imul  esi, esi, 14
  mov esi, esi                      lea eax, [rdi+rdi*8]
  pxor  xmm0, xmm0                  cmp eax, esi
  xor eax, eax                      setbe al
  pxor  xmm1, xmm1                  movzx eax, al
  cvtsi2sdq xmm0, rdi               ret
  cvtsi2sdq xmm1, rsi
  divsd xmm0, xmm1
  movsd xmm1, [rip+.LC0]
  ucomisd xmm1, xmm0
  setnb al
  ret

.LC0:
  dq 14.0 / 9.0  ; funciona no NASM.

Pra começar, a segunda rotina não tem divisões (que são LERDAS!). Ainda, a segunda rotina, além de ser menor, é melhor otimizada pelo processador (fusões, reordenações, etc). Outra vantagem é que, na primeira rotina, se height for zero, ela nos causará problemas porque a divisão resultará em NaN, que sempre retornará falso na comparação em ponto flutuante! Mais uma: A comparação, na segunda rotina, é sempre exata!

O único problema da segunda rotina poderia ser o caso de ocorrência de overflows, mas, como as resoluções gráficas que usaremos não resultam em frações com componentes muito grandes (pelo menos, não o suficiente para, quando multiplicados por 9 e 14 causem overflows), então a rotina é bem segura. Se, mesmo assim, você quer evitar a possibilidade de overflows, mude os tipos de n1 e n2 para unsigned long int ou unsigned long long. É prudente colocar um ‘UL‘ depois das constantes ou fazer um casting das variáveis nas multiplicações:

;  int is_letterbox( unsigned int width, 
;                    unsigned int height )
;  {
;    unsigned long n1, n2;
;  
;    n1 = width * 9UL;
;    n2 = 14UL * height;
;  
;    return n1 <= n2;
;  }
is_letterbox:
  mov esi, esi         ; Apenas para zeras os 32 bits
  mov edi, edi         ; superiores dos argumentos.

  lea rax, [rsi*8]     ; RAX = height*8
  lea rdx, [rdi+rdi*8] ; RDX = width*9
  sub rax, rsi         ; RAX = height*7
  add rax, rax         ; RAX = height*14

  cmp rdx, rax
  setbe al
  movzx eax, al
  ret

Mesmo assim o código ainda é melhor que o equivalente em ponto flutuante.

Siga o conselho do Tio Fred: Fuja de ponto flutuante sempre que puder.

Meu script de conversão em C (libav) – Introdução conceitual

Well… como não tenho porra nenhuma pra fazer, vou converter meu script de conversão de videos, com todas as regras que apresentei até agora, para um código em C usando as bibliotecas do ffmpeg (libavformat, libavcodec, libavfilter, libavresample, libswscale e libavutil – aquelas mesmas informadas pelo ffmpeg):

$ ffmpeg -version
ffmpeg version 3.4.6-0ubuntu0.18.04.1 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 7 (Ubuntu 7.3.0-16ubuntu3)
configuration: --prefix=/usr --extra-version=0ubuntu0.18.04.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --disable-stripping --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librubberband --enable-librsvg --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libopencv --enable-libx264 --enable-shared
libavutil      55. 78.100 / 55. 78.100
libavcodec     57.107.100 / 57.107.100
libavformat    57. 83.100 / 57. 83.100
libavdevice    57. 10.100 / 57. 10.100
libavfilter     6.107.100 /  6.107.100
libavresample   3.  7.  0 /  3.  7.  0
libswscale      4.  8.100 /  4.  8.100
libswresample   2.  9.100 /  2.  9.100
libpostproc    54.  7.100 / 54.  7.100

Já falei em como usar essas bibliotecas por aqui, mas numa versão antiga do ffmpeg (que, infelizmente, não compila mais). Agora, as libs estão padronizadas e tudo deve funcionar redondinho.

Conceitos

Do ponto de vista do ffmpeg (e da libav) um arquivo de vídeo é composto do container (chamado de “formato” pela libav), ou seja, AVI, MPEG-4, MKV, …; Dentro do container temos os streams (áudio, vídeo, legendas…); dentro de cada stream temos “pacotes” e cada pacote deve ser decodificado (ou codificado, dependendo se estamos lendo ou gravando um arquivo) por um codec. Esse passo final, na leitura, nos dará os frames (no caso de vídeo) decodificados e “descomprimidos” — um bitmap, por assim dizer.

Na nomenclatura da libav temos as estruturas AVFormat (container), AVStream, AVPacket e AVCodec… Existem outras estruturas para outros usos. Por exemplo, a biblioteca libavdevice permite o uso de outros dispositivos que não apenas arquivos — você pode abrir vídeos de um capturador de vídeos via USB, por exemplo… Já a libavfilter, que usaremos depois, permite executar filtergraphs, como nas opções -vf, -filter_complex ou -af, do ffmpeg.

Instalando os pacotes necessários

No Debian/Ubuntu:

$ sudo apt install libavformat-dev \
                   libavcodec-dev \
                   libavfilter-dev \
                   libavresample-dev \
                   libavutil-dev \
                   libswresample-dev \
                   libswscale-dev

Agora temos todos os headers e shared objects instalados no sistema e disponíveis para o GCC (ou CLANG).
Como pode ser visto abaixo, os headers estão em subdiretórios nos diretórios padrão dos headers do GCC… Os shared objects podem ser informados diretamente na linkagem como, por exemplo, -lavformat. Veremos como linkar mais tarde, mas é bom saber, também, que podemos obter esses dados via pkg-config:

$ pkg-config --list-all | grep ^libav
libavformat                    libavformat - FFmpeg container format library
libavcodec                     libavcodec - FFmpeg codec library
libavfilter                    libavfilter - FFmpeg audio/video filtering library
libavdevice                    libavdevice - FFmpeg device handling library
libavresample                  libavresample - Libav audio resampling library
libavutil                      libavutil - FFmpeg utility library

$ pkg-config --libs libavformat libavcodec libavutil
-lavformat -lavcodec -lavutil

Registrando todos os formatos e codecs e abrindo o container

Eis um início de código simples:

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
...
int main( int argc, char *argv[] )
{
  AVFormatContext *fmtctx;
  ...

  // Necessário se não quisermos selecionar, "manualmente",
  // os formatos e codecs que usaremos.
  av_register_all();  // registra todos os formatos.
  avcodec_register_all(); // registra todos os codecs.

  ...
  if ( ! ( fmtctx = avformat_alloc_context() ) )
  {
    //... processa erro de alocação de contexto aqui (aborta?).
  }

  // abre arquivo de entrada (nome em argv[1]).
  if ( avformat_open_input( &fmtctx, argv[1], NULL, NULL ) )
  {
    //... processa erro de abertura do arquivo... (aborta?).
  }

  // Obtém info dos streams do container
  if ( avformat_find_stream_info( &fmtctx, NULL ) < 0 )
  {
    //... processa erro de obtenção de info de streams... (aborta?).
  }

  ...
}

Até esse ponto estamos pronto para começar a processar os streams… Note que só lidamos com o container (formato) até agora. É útil conhecer a estrutura AVFormat porque lidaremos com alguns membros em outro artigo:

typedef struct AVFormatContext {
    const AVClass *av_class;
    struct AVInputFormat *iformat;
    struct AVOutputFormat *oformat;
    void *priv_data;
    AVIOContext *pb;
    int ctx_flags;

    unsigned int nb_streams;
    AVStream **streams;

    char filename[1024];
    int64_t start_time;
    int64_t duration;
    int64_t bit_rate;
    unsigned int packet_size;
    int max_delay;
    int flags;
    int64_t probesize;
    int64_t max_analyze_duration;
    const uint8_t *key;
    int keylen;

    unsigned int nb_programs;
    AVProgram **programs;

    enum AVCodecID video_codec_id;
    enum AVCodecID audio_codec_id;
    enum AVCodecID subtitle_codec_id;
    unsigned int max_index_size;
    unsigned int max_picture_buffer;

    unsigned int nb_chapters;
    AVChapter **chapters;

    AVDictionary *metadata;
    int64_t start_time_realtime;
    int fps_probe_size;
    int error_recognition;
    AVIOInterruptCB interrupt_callback;
    int debug;
    int64_t max_interleave_delta;
    int strict_std_compliance;
    int event_flags;
    int max_ts_probe;
    int avoid_negative_ts;
    int ts_id;
    int audio_preload;
    int max_chunk_duration;
    int max_chunk_size;
    int use_wallclock_as_timestamps;
    int avio_flags;
    enum AVDurationEstimationMethod duration_estimation_method;
    int64_t skip_initial_bytes;
    unsigned int correct_ts_overflow;
    int seek2any;
    int flush_packets;
    int probe_score;
    int format_probesize;
    char *codec_whitelist;
    char *format_whitelist;
    AVFormatInternal *internal;
    int io_repositioned;
    AVCodec *video_codec;
    AVCodec *audio_codec;
    AVCodec *subtitle_codec;
    AVCodec *data_codec;
    int metadata_header_padding;
    void *opaque;
    av_format_control_message control_message_cb;
    int64_t output_ts_offset;
    uint8_t *dump_separator;
    enum AVCodecID data_codec_id;
#if FF_API_OLD_OPEN_CALLBACKS
    attribute_deprecated
    int (*open_cb)(struct AVFormatContext *s, \
                   AVIOContext **p, \
                   const char *url, \
                   int flags, \
                   const AVIOInterruptCB *int_cb, \
                   AVDictionary **options);
#endif
    char *protocol_whitelist;
    int (*io_open)(struct AVFormatContext *s, \
                   AVIOContext **pb, \
                   const char *url,
                   int flags, \
                   AVDictionary **options);
    void (*io_close)(struct AVFormatContext *s, \
                     AVIOContext *pb);
    char *protocol_blacklist;
    int max_streams;
} AVFormatContext;

Tem muita informação ai e todas podem ser obtidas do header avformat.h ou da documentação oficial da libav (aqui), mas repare nos membros streams e nb_streams. Uma vez obtidos os streams via chamada a avformat_find_stream_info() poderemos começar a lidar com os decodecs/encodecs (codecs) e os pacotes… Deixo isso pro próximo texto.

Mais um problema com algumas TVs…

Descobri que algumas TVs não corrigem o aspect ratio da imagem de acordo com a informação que consta no container do vídeo. Existem dois aspect ratios: SAR e DAR. O SAR té dá o formado do pixel da imagem original. O DAR te dá o aspecto do pixel na imagem que será apresentada. Num vídeo de resolução 1280×536 com um SAR de 1:1, o DAR é 160:67, que é bem diferente de 16:9, ou o formato wide screen. Se você tentar assistir um vídeo com esse aspect ratio em algumas TVs, ele aparecerá distorcido.

Solução? Colocar aquelas barrinhas pretas em cima e embaixo do vídeo e forçar o aspect ratio para 16:9. Como fazer isso com o ffmpeg?

Consideremos a resolução da imagem de 1280×536. Queremos 1280×720. Então as duas barrinhas pretas devem ter 92 pixeis de largura vertical cada, ou \frac{720-536}{2}=92. Daí, só precisamos usar o filtro pad do ffmpeg:

$ ffmpeg -hwaccel cuvid -i videoin.mp4 -c:v h264_nvenc \
    -maxrate 2M -bufsize 2M -b:v 2M \
    -vf pad=w=1280:h=720:x=0:y=92:color=black,setdar=16/9 \
    -c:a copy videoout.mp4

Retire a opção -hwaccel cuvid e troque o codec para libx264 se você não tem uma placa nVidia.

O filtro pad preenche a imagem com um fundo preto (color=black) de tamanho 1280×720 (w=1280, h=720) e coloca o vídeo de entrada na posição 0;92 (x=0:y=92). Aqui forço também o DAR para 16:9 (e, automaticamente o SAR para 1:1). Como estou aumentando a resolução do vídeo, tenho que aumentar também o bitrate, daí as opções -maxrate 2M, -bufsize 2M e -b:v 2M (na verdade, se o vídeo tem framerate de 29.97 fps, precisamos de 1.998 Mb/s). Aqui, simplesmente copiei o stream de áudio…

O filtro pad poderia ser descrito apenas como pad=1280:720:0:92:black, se quiséssemos. Prefiro a descrição mais completa para não causar confusão… Ainda, para tornar o filtro agnóstico em referência à resolução gráfica, vocẽ pode usá-lo assim:

'pad=w=iw:h=(iw*9/16):x=0:y=(((iw*9)/16)-ih)/2:color=black'

Aqui iw e ih são a resolução horizontal e vertical do vídeo original, respectivamente. Isso usa o aspecto 16:9. O argumento da opção -vf deve estar todo entre aspas simples, neste caso. Tome cuiado com isso se for usar múltiplos filtros…

Uma confusão recorrente… pointeiros e arrays.

Alguns de vocês já devem ter lido por aqui e feito experiências com relação ao “endereço” base de um array. Considere o seguinte array:

int a[] = { 1, 2, 3, 4 };

O identificador a é, de forma bastante comum, encarado como o ponteiro para o início do array. De fato, a especificação ISO 9989 nos diz que o operador [] pode ser, seguramente, interpretado como a[i] = *(a + i). Mas, há uma distinção sutil aqui: O identificador a não é um ponteiro! Ele é interpretado como sendo um…

“Porra, tio Fred, assim você dá um nó na minha cachola!”. O problema aparece quando usamos o operador & (endereço-de):

#include <stdio.h>

int a[] = { 1, 2, 3 4 };

int main( void )
{
  printf( "a=%p, &a=%p\n", a, &a );
}

Ao compilar e executar isso você verá que os endereços impressos são os mesmos. Como pode um ponteiro apontar para uma coisa e um ponteiro para um ponteiro apontar para a mesma coisa? Não pode! O fato é que a, usando usado em expressões, é convertido em um ponteiro, mas ele não é um. O símbolo a aqui é um array “identificado” pelo nome a.

Parece que não há diferença, não é? Então, vejamos esse outro exemplo: Se a e &a forem, de fato, o mesmo ponteiro, apontar para o próximo item do array deveria fornecer o mesmo endereço, não é?

#include <stdio.h>

int a[] = { 1, 2, 3 4 };

int main( void )
{
  printf( "a=%p, &a=%p\n", a, &a );
  printf( "next(a)=%p, next(&a)=%p\n", a + 1, &a + 1 );
}

Compilando e executando você obtém algo assim:

$ cc -O2 -o test test.c
$ ./test
a=0x563c50e5a010, &a=0x563c50e5a010
next(a)=0x563c50e5a014, next(&a)=0x563c50e5a020

Ué?! Por que &a+1 resultou em um endereço diferente de a+1? Note que em nossa tentativa de obter o endereço do próximo item com o ponteiro, no primeiro caso obtemos, corretamente, o endereço 4 bytes além da base, mas no segundo (usando &a), obtemos o endereço além do final do array (16 bytes além da base)… Isso ocorre porque os tipos dos ponteiros convertidos à partir de a são diferentes… O primeiro ponteiro, a+1 é do tipo int *, o segundo, do tipo int (*)[4]. Ou seja, a e &a fornecem o mesmo endereço porque estamos obtendo o endereço do identificador a, que aponta para o início do array. No primeiro caso (a) o identificador é convertido para um ponteiro, no segundo (&a) obtemos o “endereço-do array identificado por a.

Sim, você pode assumir que o identificador de um array seja um ponteiro para sua base, se usar apenas o nome do identificador como ponteiro (não o seu endereço), mas, além do problema acima, existe outro: O identificador de um array definido carrega consigo, em tempo de compilação, o tamanho do array. Se você usar o operador sizeof com o identificador a, obterá 16 (4 vezes o tamanho de um int), no exemplo acima… Se a fosse apenas um ponteiro, obteria 4 (i386) ou 8 (x86-64) que, aliás, é o que obterá se tentar obter sizeof(&a).

Um aviso sobre a função socket()

Não me lembro se já falei disso por aqui. Então, lá vai:

É comum ver códigos como esse:

int fd;

fd = socket( AF_INET, SOCK_STREAM, 0 );
if ( fd < 0 ) { ... trata erro... }
...

A pergunta que você deveria se fazer é: Qual é o protocolo que será usado? Como é que o sistema sabe qual é o protocolo? Esse valor zero ai é uma indicação para que o kernel escolhe um pra você. Usualmente, com o socket do tipo SOCK_STREAM, o protocolo TCP é escolhido (IPPROTO_TCP) e, de fato, no Linux, 0 é um sinônimo para o TCP… Mas, acontece que isso não é verdadeiro em todos os casos. Vejamos esse caso:

fd = socket( AF_INET, SOCK_DGRAM, 0 );

Você pode supor que UDP será escolhido por default… Nah. Provavelmente você obterá um descritor negativo, indicando erro. A coisa piora quando queremos criar código portável. Alguns sistemas simplesmente falham em ambos os casos.

Tenha certeza de informar o protocolo correto sempre. A chamada inicial correta é:

fd = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

Essa chamada tem a vantagem de que o kernel não terá que procurar pelo protocolo (e evitando um loop) e ser portável.

Uma otimização interessante para testar se um valor é primo

Esse é um dos exercícios mais comuns na área acadêmica: Dizer se um número é primo ou não. Existe a maneira tradicional, a maneira rápida e a maneira super rápida… As duas primeiras eram minhas velhas conhecidas e já falei delas aqui, eis um resumão:

O método tradicional é dividir todos os valores inferiores ao número n, sob teste, até chegarmos a 2 e se o resto de qualquer uma das divisões der zero, isso indica uma divisibilidade e, portanto, o valor não é primo:

int isPrime( unsigned int n )
{
  unsigned int i;

  // casos especiais... 3 e 2 são primos, mas 1 e 0 não são!
  if ( n <= 3 )
    return ! ( n < 2 );

  for ( i = 2; i < n; i++ )
    if ( ( n % i ) == 0 )
      return 0;
 
 return 1;
}

O método rápido reduz o a quantidade de iterações do loop porque só precisamos verificar a divisibilidade de valores inferiores ou iguais à raiz quadrada de n. Isso é baseado na propriedade comutativa e do princípio fundamental da aritmética: 2*3 é a msma coisa que 3*2, então não precisamos testar os dois casos. Ainda, qualquer valor pode ser fatorado em primos… Outro detalhe importante para o teste é que só existe um valor par primo: 2, todos os demais são ímpares:

int isPrime( unsigned int x )
{
  unsigned int i, s;

  if ( n <= 3 )
    return ! ( n < 2 );

  if ( ( n % 2 ) == 0 )
    return 0;

  s = sqrt(n);

  for ( i = 3; i <= s; i += 2 )
    if ( ( n % i ) == 0 )
      return 0;

  return 1;
}

Quando você acha que achou o código mais rápido possível algum detalhe te supreende. O código acima pode ser acelerado um bocado se pensamos que se o valor n é divisível por 6, então ele é divisível por 3 e por 2 e, portanto, não é primo. De outro modo, podemos retirar valores que, divididos por 6, resultam em restos 2, 3 ou 4, já que, com toda certeza, eles não são primos (ou são divisíveis por 2 ou 3).

int isPrime(unsigned int n)
{
  unsigned int i, s;

  if (n <= 3)
    return ! ( n < 2 );

  // Mais um caso especial... Se for divisível por 2 ou 3, não é primo!
  if ( ( n % 2 ) == 0 || ( n % 3 ) == 0)
    return 0;
 
  s = sqrt(n);
 
  for ( i = 5; i <= s; i += 4 )
  {
    if ( ( n % i ) == 0)
      return 0;

    i += 2

    if ( ( n % i ) == 0)
      return 0;
  }

  return 1;
}

O código acima executa o loop cerca de 3 vezes mais rápido que o código anterior e é matematicamente correto. Claro, existe o problema de que duas divisões são feitas no loop, mas o compilador tende a otimizar isso…

PS: Lembrando que existe um outro método, rápido, mas que consome muita memória: Trata-se do “Crivo de Eastótenes” que encontra os valores primos tentando as divisões com os valores primos anteriormente encontrados (até a raiz quadrada de n, como acima)… O “Crivo” é o método mais rápido, mas como eu disse, tem o potencial de consumir MUITA memória, porque precisamos manter um array com os valores previamente encontrados.

Deus ex machina

Well… errar é ‘umano’, mas não checar o erros é burrice.

Aqui vai uma dica para quem quer mexer com sockets (ou arquivos!): Sembre verifique as condições de erro.

É comum observar códigos de novatos simplesmente fazendo syscalls e não verificando erros. O que os amigos noobs não se dão conta é que uma conexão passa por um fio que pode ser partido, pode aparecer ruído, o outro lado pode desconectar repentinamente (seja de propósito ou porque está com algum problema com recursos), etc, e tudo isso é refletido nas condições de erro das chamadas de rotinas que lidam com sockets… No caso de arquivos, o disco pode ficar cheio, pode haver uma falha de leitura/escrita, etc…

Primeira função: socket(). Ela sempre retorna -1 em caso de falha. Em caso de sucesso, ela retorna um file descriptor que, por definição, é sempre positivo. Assim, é impressindível fazer algo assim:

int fd;

fd = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if ( fd < 0 )
{
  // trata o erro aqui (verifique errno!).
}

Alguns erros comuns:

  • Você não tem permissão para criar o socket (isso geralmente ocorre com RAW sockets);
  • O protocolo é inválido para a família do socket;
  • Você atingiu o máximo de file descriptors que podem ser abertos para o processo;
  • Não há memória suficiente disponivel para os buffers associados ao socket;

Mas, outros podem surgir, dependendo da família, do tipo e do protocolo… Outra dica que dou é não usar 0 como protocolo. Em teoria SOCK_STREAM selecionará, automaticamente, TCP, mas isso nem sempre é verdade.

Segunda função: connect(): Retorna 0 em caso de sucesso ou -1 em caso de erro. De novo, é prudente fazer:

if ( connect( fd, 
              (const struct sockaddr *)&sin, 
              sizeof sin ) )
{
  // trata erro aqui (verifique errno!).
}

Note que o teste aqui é feito contra o zero (se o retorno não for 0, temos um erro).

Os erros mais comuns são:

  • Você não tem permissão (geralmente para broadcasting);
  • O endereço local fornecido na estrutura já está em uso (veja setsockopt());
  • A família de protoclos não é suportada;
  • O cache de roteamento não tem espaço para acomodar essa conexão;
  • A conexão foi refutada
  • A conexão já existe;
  • O destino não pode ser alcançado;
  • Time out

Funções como bind() e accept() têm, mais ou menos a mesma lista de possíveis erros. accept() retorna -1 (erro) ou um file descriptor, enquanto bind() retorna 0 (sucesso) ou -1 (erro).

Funções de recebimento e envio de dados. send(), por exemplo, se falhar, retorna -1, caso contrário retorna a quantidade de de dados bufferizados (não há garantias se os dados foram, de fato, enviados ou não)… recv() também retorna -1 em caso de erro e você deve verificar o conteúdo de errno. De qualquer maneira, verifique por -1 e errno para ver se a conexão ainda existe ou outro erro.

Essa coisa de verificar as condições de erro não serve apenas para sockets e arquivos. Muitas funções da libc oferecem alguma forma de status para condições erro… Por exemplo: scanf sempre retorna a quantidade de conversões que conseguiu fazer ou EOF em caso de “fim de arquivo”. Se retornar o valor 0, não conseguiu fazer conversão alguma… printf, por sua vez, retorna o número de caracteres “impressos” ou um valor negativo em caso de erro. A exceção é snprintf que retornará o número de caracteres que “poderiam” ser impressos (ou valor negativo em caso de erro). Já falei sobre o snprintf aqui, na implementação da função não padronizada asprintf, no Windows (aqui).

O ponto que estou querendo reforçar aqui é que você sempre deve esperar que erros ocorram (obedeça a famigerada “Lei de Murphy”!) e tratá-los de acordo… Por “erro” eu não estou falando de “exceções”, no sentido estrito. Quem mexe com C++ e adora usar try..catch, saiba que isso custa processamento. Prefiro tratar as condições de erro no estilo de C mesmo, aproveitando a API do sockets, ao invés de usar alguma classe (que é necessária para usar “exceptions” em C++)…