A função
package brdoc
import (
"regexp"
"strings"
)
var (
reSep = regexp.MustCompile(`[./-]`)
reCNPJ = regexp.MustCompile(`^[A-Z0-9]{12}\d{2}$`)
)
// RE2 não tem backreference, então repetidos vão por função, não por regex.
func allSame(s string) bool {
for i := 1; i < len(s); i++ {
if s[i] != s[0] {
return false
}
}
return len(s) > 0
}
func dv(base string, w []int) int {
s := 0
for i, p := range w {
s += (int(base[i]) - 48) * p
}
if r := s % 11; r >= 2 {
return 11 - r
}
return 0
}
func IsValidCNPJ(cnpj string) bool {
clean := strings.ToUpper(reSep.ReplaceAllString(cnpj, ""))
if !reCNPJ.MatchString(clean) || allSame(clean) {
return false
}
w1 := []int{5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2}
w2 := []int{6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2}
d1 := dv(clean[:12], w1)
d2 := dv(clean[:12]+string(rune('0'+d1)), w2)
return d1 == int(clean[12]-'0') && d2 == int(clean[13]-'0')
}
// IsValidCNPJ("12.ABC.345/01DE-35") -> true (alfanumérico, exemplo SERPRO)
// IsValidCNPJ("11.222.333/0001-81") -> true (numérico)O mesmo código valida os dois formatos: o numérico é um caso particular do alfanumérico. A base tem 12 posições (letras A–Z ou dígitos 0–9) e os 2 verificadores são sempre numéricos.
Por que ASCII − 48
No módulo 11 cada posição vira um número antes de multiplicar pelo peso. Para tratar letra e dígito de forma uniforme, converte-se o caractere pelo seu código menos 48: '0' vira 0, '9' vira 9 e 'A' vira 17 (segue até 'Z' = 42). É exatamente o que int(base[i]) - 48 faz dentro de dv. Os pesos vêm em duas listas (w1 para o 1º dígito, w2 para o 2º) e o resto da divisão por 11 define o verificador: r >= 2 ? 11 - r : 0. É o módulo 11 de sempre, só com a entrada normalizada.
Cuidados
- Precisa de
strings.ToUpper. O regex aceitaA–Zmaiúsculo, então a base é normalizada antes da checagem. Sem oToUpper, um CNPJ alfanumérico digitado em minúsculas seria reprovado — é um ponto fácil de esquecer ao portar de outras linguagens. - Repetidos não vão por regex. O
regexpdo Go usa RE2, que não suporta backreference — um padrão como^(.)\1{13}$nem compila. A funçãoallSamefaz o mesmo papel, comparando cada caractere com o primeiro, e descarta sequências como00000000000000. - Regex só valida formato. O
reCNPJconfirma 12 posições alfanuméricas + 2 dígitos, mas não confere o verificador — quem faz isso é a função acima. Veja regex de CNPJ. - Válido ≠ existe. A função confirma a consistência matemática, não se o CNPJ foi emitido a uma empresa real. Para isso é outra coisa: CNPJ válido vs. CNPJ real.
- Alfanumérico entra em jul/2026 (IN RFB nº 2.229/2024), só para novos registros. O código já está pronto para os dois formatos.
Continue
Perguntas frequentes
A mesma função valida CNPJ numérico e alfanumérico?
ASCII − 48 trata letra e dígito de forma uniforme, então o numérico é um caso particular do alfanumérico — um só caminho valida os dois.Por que o código tem <code>strings.ToUpper</code>?
A–Z maiúsculo, então um CNPJ digitado em minúsculas seria reprovado sem o ToUpper.Por que repetidos são pegos por <code>allSame</code> e não por regex?
regexp do Go usa RE2, que não tem backreference. Um padrão como ^(.)\1{13}$ não compila — a checagem manual com allSame faz o mesmo papel.