Algoritmos – Validação de CPF

Introdução

Hoje vamos dar uma olhada nos números que compõe um CPF, e ver como é possível calcular os dois últimos dígitos de um CPF — chamados de “verificadores” — a partir dos nove primeiros números.

Validar CPF

A fórmula para validar um número de CPF é conhecida por “módulo 11”, que consiste em gerar um número multiplicando cada dígito por um fator de acordo com a sua posição, depois realizar uma divisão por 11 e gerar o dígito verificador baseado em uma regra usando o resto da divisão. Os dois últimos números do CPF são os dígitos verificadores, cada um deles é calculado usando esta regra, porem com fatores diferentes.

O cálculo do primeiro dígito leva em conta os 9 primeiros números do CPF, o cálculo do segundo leva em conta os 9 primeiros números mais o primeiro dígito verificador. Existe um algoritmo escrito em pseudo-código na Wikipedia — vide links de referências no final do post — que mostra (de forma bem complicada por sinal) como deve ser feito o cálculo.

Fazendo em AdvPL

No último link das referências, encontrei uma explicação um pouco mais simples de como fazer o cálculo. Baseado nesta, montei a função abaixo, que calcula os dois dígitos verificadores de um CPF, partido de uma string contendo os nome primeiros números — sem pontuação ou separadores.

STATIC Function DV_CPF(cCPF)
Local nI , nVL  
Local nM1   := 10 , nM2   := 11
Local nDV1 := 0  , nDV2 := 0
Local nVA := -1  , nEQ := 0 
Local cDV1,cDV2

// Calculo dos valores dos digitos baseado 
// no fator multiplicativo de acordo com a posição 
// de cada dígito 

For nI := 1 to 9
	nVL := val( substr(cCPF,nI,1) )
	nDV1 += (  nVL * nM1 )
	nDV2 += (  nVL * nM2 )
	nM1--
	nM2--
	If nVA >= 0 .AND. nVA == nVL
		nEQ++
	Endif
	nVA := nVL
Next

If nEq == 8
	// Todos os números são iguais - CPF Invalido 
	return ""
Endif

// Determina o primeiro digito verificador 
nDV1 := nDV1 % 11
nDV1 := IIf(nDV1 > 1 , 11 - nDV1 , 0 )
cDv1 := chr(48+nDV1)

// Determina o segundo digito verificador 
nDV2 += (nDV1*nM2)
nDV2 := nDV2 % 11
nDV2 := IIf(nDV2 > 1 , 11 - nDV2 , 0 )
cDv2 := chr(48+nDV2)

// Retorna os dígitos calculados como string
Return cDv1+cDv2

A função recebe uma string contendo os 9 primeiros dígitos do CPF, e retorna os dois últimos dígitos do CPF, ou retorna uma string vazia caso todos os dígitos sejam iguais — o que também caracteriza um número de CPF inválido.

Curiosidades

Com a função acima, realizando 1,2 milhões de cálculos de dígitos de CPF, a média de cálculos por segundo no meu ambiente local variou de 75 a 80 mil cálculos por segundo. Para obter esta métrica, parti de quatro números de CPF em string na memória, para o programa não realizar nenhum I/O. Como esta função usa somente CPU, seu desempenho será proporcional à velocidade do Clock do processador do equipamento utilizado.

E, durante a pesquisa, fiquei sabendo que o último número do CPF — 9o número, antes do primeiro dígito verificador — corresponde a uma região brasileira (agrupando um ou mais estados) onde o CPF foi emitido. Para obter esta informação como String em AdvPL  a partir de um CPF, use o código abaixo:

       
STATIC aCPF_UF := { "Rio Grande do Sul",;
		"Distrito Federal, Goiás, Mato Grosso, Mato Grosso do Sul e Tocantins" ,;
		"Amazonas, Pará, Roraima, Amapá, Acre e Rondônia" ,;
		"Ceará, Maranhão e Piauí" ,;
		"Paraíba, Pernambuco, Alagoas e Rio Grande do Norte",;
		"Bahia e Sergipe" ,;
		"Minas Gerais" ,;
		"Rio de Janeiro e Espírito Santo" ,;
		"São Paulo",;
		"Paraná e Santa Catarina" }

Static Function CPF_UF(cCpf)
Local nVL := val( substr( cCpf ,9,1) ) + 1
Return 	aCPF_UF[nVL]

Por exemplo, todos os CPFs emitidos no estado de São Paulo contém o número “8” antes dos dígitos verificadores.

Conclusão

Por hora, eu acabei de concluir que “Não importa o quanto você saiba sobre um determinado assunto. Uma pesquisa pode mostrar detalhes ou particularidades que você não conhecia e pode ser útil.” — Número do CPF contém um número que identifica uma região onde o documento foi emitido ?! Eu não sabia … 😀

Referências

 

 

Data Juliana em AdvPL

Introdução

Neste final de semana um amigo me perguntou se tinha alguma função AdvPL que fosse capaz de converter uma data do nosso calendário (gregoriano) representada por dia, mês e ano, em uma data Juliana.

Problemática e Pesquisa

Na verdade nem eu sabia o que era uma “data Juliana”, até realizar uma pesquisa. Trocando em miúdos, uma data Juliana é representada em um número de dias decorridos a partir do “dia zero”, ou data de início do calendário. No caso, a “data zero” do calendário Juliano no calendário Gregoriano é  24/11/4714 ac. Logo, a data Juliana 2458588 no nosso calendário refere-se ao dia 14 de Abril de 2019, pois este foi o número de dias decorridos desde 24/11/4714 ac até 14/04/2019 (dc).

Nas pesquisas, encontrei e cheguei até a implementar uma função matemática que realizaria este cálculo, a partir de uma data qualquer, encontrar a data Juliana correspondente. Porém, estava um pouco difícil fazer o contrário — a partir de uma data Juliana, determinar sua representação no calendário gregoriano.

Solução

Por incrível que pareça, a solução para isso em AdvPL, ou qualquer outra linguagem que seja capaz de calcular um intervalo em dias a partir de uma data qualquer é muito simples. Se a linguagem permite calcular uma diferença entre datas, e permite somar ou subtrair um número de dias a partir de uma data qualquer, basta fazer o seguinte:

  1. Estipular uma “data zero” como base de cálculo. Por exemplo , 01/01/1980.
  2. Encontrar a data Juliana correspondente — no caso, 2444240.
  3. Para encontrar a data Juliana a partir de uma data qualquer, basta usar as funções de data disponíveis na sua linguagem, para determinar o número de dias decorridos desde 01/01/1980 até a data em questão, e somar 2444240.
  4. Para encontrar a data Gregoriana correspondente a uma data Juliana, basta subtrair 2444240 unidades da data Juliana para obter o número de dias decorridos desde 01/01/1980 — que pode ser um valor positivo, negativo ou mesmo zero — e depois partir da data 01/01/1980 e adicionar esta diferença, obtendo a data.

Em AdvPL

// ----------------------------------------
// Converte Data Juliana em Data AdvPL 
STATIC Function Date2DJ(dDate)
Return (dDate - ctod("01/01/1980")) + 2444240 

// ----------------------------------------
// Converte Data Juliana em Data AdvPL 
STATIC Function DJ2Date(nDJ)
Return ctod("01/01/1980") + ( nDJ - 2444240 )

Conclusão

Sim, pode ser feito, e de forma simples. Agradecimentos ao meu amigo Marcos Gomes, cuja dúvida me ajudou a conhecer mais um pouco sobre Calendários 😉

Referências