Ir para o conteúdo
OneKitly

Os limites de caracteres que realmente mordem: unidades de código, pontos de código e grafemas

Publicado a 01/07/2026 · 12 min de leitura · Ferramentas de texto e idioma

Daniel Okonkwo

Daniel OkonkwoProgramador front-end e redator de Tecnologia na OneKitly

Desempenho web · Formatos de ficheiro

Verificado a partir de 7 fontes

Ver perfil
Em resumo

A palavra caráter designa três unidades diferentes, e cada limite com que embate está expresso numa delas sem dizer qual. Uma unidade de código UTF-16 é o que devolvem .length em JavaScript e String.length() em Java. Um ponto de código é um valor escalar Unicode, o que devolve len() em Python 3. Um grafema é o que um leitor chama caráter, o que devolve String.count em Swift. Divergem assim que o texto sai do ASCII puro. O emoji do polegar para cima com tom de pele médio é 1 grafema, 2 pontos de código e 4 unidades UTF-16, e ocupa 8 bytes em UTF-8. O emoji de família de quatro é 1 grafema, 7 pontos de código, 11 unidades UTF-16 e 25 bytes. A cadeia Shipping to, bandeira francesa, today, polegar com tom, travessão e thanks mede 37 unidades UTF-16, 33 pontos de código, 31 grafemas e 47 bytes UTF-8: quatro números para uma só cadeia. As plataformas escolhem unidades diferentes: o X conta um comprimento ponderado, o Bluesky impõe 300 grafemas e 3000 bytes ao mesmo tempo, o Mastodon conta pontos de código. Um SMS leva 160 carateres em GSM-7 mas só 70 em UCS-2, e um único apóstrofo curvo converte a mensagem inteira. E VARCHAR(255) significa carateres no PostgreSQL e no MySQL, e bytes na Oracle por omissão.

Um caráter são três coisas ao mesmo tempo. Um emoji com tom de pele é 1 grafema, 2 pontos de código e 4 unidades UTF-16. Todas as contagens deste guia foram medidas em Node, mais porque é que um SMS cai de 160 para 70 e porque é que VARCHAR(255) não são 255 de coisa nenhuma em concreto.

Três unidades, todas chamadas caráter

Uma unidade de código UTF-16 são 16 bits de armazenamento. Tudo o que ultrapassa U+FFFF - ou seja, todos os emojis, todas as escritas históricas e boa parte do CJK - precisa de duas, formando um par substituto. As cadeias de JavaScript definem-se como sequências de unidades de código UTF-16, pelo que .length conta essas, tal como String.length() em Java e o tipo string do .NET em C#.

Um ponto de código é uma entrada da base de carateres Unicode, escrita U+0041 ou U+1F44D. É a unidade que devolve len() em Python 3 e a que usam o operador de propagação e a iteração for-of em JavaScript. Está mais perto da intuição do que uma unidade de código, mas continua a não ser o que um leitor vê, porque vários pontos de código se combinam rotineiramente num único sinal visível.

Um grafema - a norma chama-lhe grafema estendido, definido no anexo 29 do Unicode - é o caráter percebido pelo utilizador. É o que o cursor salta, o que uma tecla de retrocesso apaga e o que uma pessoa conta. String.count em Swift devolve grafemas; o Intl.Segmenter em JavaScript também, com granularidade grapheme. Nenhuma outra linguagem de uso corrente os devolve por omissão, e essa é a causa-raiz de quase todos os erros com emojis que já viu.

Uma cadeia, quatro números

Pegue numa mensagem curta e perfeitamente banal: Shipping to, um emoji de bandeira francesa, today, um polegar para cima com tom de pele médio, um travessão, e thanks com ponto de exclamação. Medida em Node 22, são 37 unidades de código UTF-16, 33 pontos de código, 31 grafemas e 47 bytes em UTF-8. Uma pessoa que a leia diria 31 carateres. O JavaScript dir-lhe-á 37. Uma coluna de base de dados limitada em bytes verá 47.

As diferenças vêm de dois sítios. O emoji de bandeira é um par de indicadores regionais - dois pontos de código, quatro unidades UTF-16, uma só bandeira visível - e o polegar para cima é um emoji base mais um modificador de tom, de novo dois pontos de código e quatro unidades UTF-16 para um só sinal visível. Entre os dois explicam o 37 contra o 31. Nada de exótico: é assim uma mensagem normal enviada de um telemóvel normal.

O mesmo texto visível pode ter dois comprimentos diferentes

A palavra Cafe com acento agudo, seguida de um espaço, um polegar para cima com tom e um ponto de exclamação, mede 10 unidades UTF-16, 8 pontos de código, 7 grafemas e 15 bytes UTF-8 quando a letra acentuada é o ponto de código pré-composto U+00E9. Escreva a cadeia de aspeto idêntico num Mac que emite um e seguido de um acento agudo combinante e passa a 11 unidades UTF-16, 9 pontos de código, ainda 7 grafemas, e 16 bytes UTF-8.

Os mesmos píxeis, comprimento diferente, e uma verificação de bytes que passa num caso falha no outro. A solução é normalizar à entrada: aplique a forma de normalização Unicode C, que recompõe e mais acento combinante em U+00E9, antes de medir, armazenar, comparar ou fazer hash. Quase todos os erros de deteção de duplicados com nomes acentuados remontam a comparar uma cadeia NFC com uma NFD.

O truncamento é onde a abstração se parte em público

Corte essa cadeia Cafe em seis unidades UTF-16 com um slice comum e obtém C, a, f, o e acentuado, um espaço e depois U+D83D sozinho: a metade alta de um par substituto sem par. Esse substituto solto não é um caráter válido. Os motores de renderização mostram uma caixa de substituição, os codificadores JSON emitem um escape que alguns analisadores rejeitam, e as bases de dados com validação UTF-8 estrita recusam a escrita pura e simplesmente.

Truncar por pontos de código evita a falha do substituto solto, mas continua a partir um emoji de família em dois adultos e uma criança, ou a tirar o modificador de tom a um polegar que volta a sair amarelo. O único truncamento seguro à frente de um utilizador é o baseado em grafemas: segmente com Intl.Segmenter, pegue nos primeiros n segmentos e junte-os. Se o seu limite for em bytes, faça as duas coisas: conte grafemas para decidir onde é legal cortar, e bytes UTF-8 para decidir quantos pode pagar.

Que plataforma conta que unidade

O X não conta carateres de todo: calcula um comprimento ponderado em que os carateres das gamas latina, suplemento latino-1 e pontuação geral contam 1, e tudo o resto - CJK, árabe, cirílico para lá do bloco básico e cada emoji - conta 2. Uma publicação com limite de 280 leva portanto 280 letras latinas ou 140 emojis. O Bluesky impõe dois limites ao mesmo tempo no mesmo campo: 300 grafemas e 3000 bytes, pelo que uma publicação de 300 emojis de bandeira é legal em grafemas e falha em bytes. O limite por omissão de 500 do Mastodon conta-se em pontos de código, sendo qualquer URL cobrada a 23 fixos independentemente do comprimento real.

A consequência prática é que um único contador não pode servir todos os destinos. Se agendar o mesmo texto para quatro redes, precisa de quatro contagens e de uma pré-visualização que mostre a cadeia como o leitor a verá, não como a camada de armazenamento a mede. Na dúvida, conte grafemas para o limite virado ao humano e bytes UTF-8 para o virado à máquina, e desconfie de qualquer número único rotulado carateres enquanto não souber que unidade o produziu.

SMS: 160 passa a 70 assim que um caráter sai do GSM-7

O corpo de um SMS são 140 octetos. Codificado no alfabeto GSM de 7 bits definido na norma 3GPP TS 23.038, a sete bits por caráter, isso dá 160 carateres. Mas esse alfabeto tem apenas cerca de 128 lugares mais uma pequena tabela de extensão, e se um único caráter da mensagem ficar de fora, toda a mensagem tem de ser recodificada em UCS-2 a 16 bits por caráter: 140 octetos a dividir por 2 são 70 carateres. Não 70 para o caráter culpado: 70 para a mensagem inteira.

Vejamos um exemplo. Uma confirmação de reserva com 145 carateres cabe num único segmento GSM-7, porque 145 é menos de 160. Cole-a a partir de um processador de texto que converteu em silêncio o apóstrofo reto num tipográfico, ou acrescente um travessão, ou um emoji: a mensagem passa a UCS-2. Ora 145 ultrapassa 70, logo é dividida em partes concatenadas, e a concatenação rouba seis octetos por parte para o cabeçalho de segmentação, deixando 67 carateres em cada uma. 145 a dividir por 67 arredonda para 3. Uma substituição invisível transformou uma mensagem faturável em três.

Saber que carateres são seguros não é intuitivo. Várias letras acentuadas estão no conjunto básico GSM-7 - e com acento agudo, e grave, a grave, u grave, o esse alemão e as vogais com trema entre elas -, pelo que um SMS francês ou alemão não é automaticamente UCS-2. Mas a, i, o e u com acento agudo não estão, o que significa que os acentos espanhóis e portugueses costumam forçar a mudança. O sinal do euro vive na tabela de extensão e custa dois septetos em vez de um. Aspas curvas, travessões e meios-travessões, o caráter de reticências e todos os emojis simplesmente não existem ali.

VARCHAR(255) não são 255 de coisa nenhuma em concreto

No PostgreSQL, varchar(255) são 255 carateres, isto é, pontos de código, e o armazenamento cresce conforme o UTF-8 precisar. No MySQL, VARCHAR(255) conta carateres e não bytes desde a versão 5.0, pelo que com o conjunto utf8mb4 a mesma coluna pode ocupar até 1020 bytes. Na Oracle, VARCHAR2(255) significa 255 bytes a menos que escreva VARCHAR2(255 CHAR) ou altere NLS_LENGTH_SEMANTICS, e é por isso que um campo com limite 255 num sistema Oracle rejeita em silêncio um nome de 200 carateres escrito num alfabeto acentuado. No SQL Server, VARCHAR são bytes numa colação de um byte e NVARCHAR são unidades UTF-16 de dois bytes cada.

Há uma armadilha de segunda ordem específica do MySQL. O antigo limite de prefixo de índice do InnoDB de 767 bytes fazia com que uma coluna VARCHAR(255) em utf8mb4 - até 1020 bytes - não pudesse ser totalmente indexada, e daí vem o folclore do VARCHAR(191): 191 vezes 4 são 764, mesmo abaixo do limite. O InnoDB moderno com formato de linha DYNAMIC eleva esse limite para 3072 bytes e o remendo ficou obsoleto, mas as colunas de comprimento 191 que ele criou continuam em esquemas de produção por toda a parte.

Unidades UTF-16 (.length)
Cinco cadeias medidas de quatro formas em Node 22. A coluna de grafemas usa Intl.Segmenter; a coluna UTF-16 é o que devolve .length em JavaScript.
CadeiaUnidades UTF-16 (.length)Pontos de códigoGrafemasBytes UTF-8
A letra a1111
e com acento agudo, escrita como um único ponto de código U+00E91112
A mesma letra escrita como e mais um acento combinante U+0065 U+03012213
Emoji de polegar para cima com tom de pele médio (U+1F44D U+1F3FD)4218
Emoji de família: homem, mulher, menina, menino unidos por três junções de largura zero117125
Contador de caracteres para redes sociaisConte os caracteres segundo os limites de 15 redes sociais — X/Twitter, Instagram, TikTok, LinkedIn, YouTube, Threads e mais — com as regras ponderadas do X (links = 23, CJK = 2) e estimativa de fio.Experimentar a ferramenta

Perguntas frequentes

Porque é que o JavaScript diz que o meu emoji tem 2 carateres?
Porque .length conta unidades de código UTF-16, e todo o emoji está acima de U+FFFF, precisando por isso de um par substituto: duas unidades. Acrescente um modificador de tom e são quatro. Use [...str].length para contar pontos de código, ou Intl.Segmenter com granularidade grapheme para contar o que um leitor contaria. Numa linha: new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(str) dá-lhe um iterável de carateres visíveis.
Quantos bytes ocupa um caráter em UTF-8?
De um a quatro, consoante o ponto de código. O ASCII até U+007F ocupa 1 byte. Os acentos latinos, o grego e o cirílico até U+07FF ocupam 2. Quase todo o plano multilingue básico, incluindo o CJK, ocupa 3. Tudo o que ultrapassa U+FFFF, ou seja todos os emojis, ocupa 4. Assim, um polegar para cima com tom são 8 bytes, porque são dois pontos de código de 4 bytes cada, e o emoji de família de quatro são 25 bytes: quatro emojis a 4 bytes mais três junções de largura zero a 3 bytes cada.
Porque é que o meu SMS de 145 carateres foi faturado como três mensagens?
Porque um dos seus carateres não estava no alfabeto GSM de 7 bits, pelo que toda a mensagem passou a UCS-2, a 70 carateres por segmento. Acima de 70, os segmentos levam um cabeçalho de concatenação que custa seis octetos, deixando 67 carateres cada, e 145 a dividir por 67 arredonda para 3. Os culpados habituais são um apóstrofo tipográfico, um travessão, um caráter de reticências ou um emoji, todos inseridos automaticamente por processadores de texto e teclados de telemóvel. Passe o texto por um contador de bytes e codificação antes de enviar uma campanha, não depois.
Devo guardar um limite em carateres ou em bytes?
Aplique dois limites, não um. Um limite em grafemas para o que mostra ao utilizador, porque é o número que ele pode verificar a olho. Um limite em bytes para armazenamento e transporte, porque é isso que a coluna, a carga útil e o protocolo realmente restringem. Se só puder ter um, escolha bytes e faça a interface ser honesta quanto a isso: um transbordo de um único byte é uma falha de escrita, ao passo que um de grafemas é apenas um problema estético.
As mesmas regras aplicam-se a uma metadescrição ou etiqueta title?
Não, e este é precisamente o caso em que contar carateres é o instinto errado. Os motores de busca truncam títulos e descrições pela largura em píxeis renderizada, não pelo número de carateres, pelo que um título de 60 letras estreitas pode caber onde 50 largas não cabem, e um emoji num título ocupa muito mais largura do que o seu único grafema sugere. Os alvos de 60 e 155 carateres são aproximações a um orçamento de píxeis. Escreva para o orçamento de píxeis, verifique numa pré-visualização de resultados e use o contador de carateres apenas para não derivar para comprimentos claramente irrecuperáveis.

Artigos que podem interessar-lhe

Todos os guias
ExplicaçãoO que medem realmente os índices de legibilidade (e as três coisas que não veem)O Flesch Reading Ease e o nível Flesch-Kincaid contam sílabas e comprimento de frase. Mais nada. As duas fórmulas na íntegra, uma passagem pontuada de ponta a ponta e o truque da vírgula que oferece 3,9 anos de escolaridade sem mudar uma palavra.ExplicaçãoContar palavras é ambíguo, e cada ferramenta responde de forma diferenteUma contagem de palavras é uma definição, não uma medição. Contámos o mesmo parágrafo de quatro maneiras e obtivemos 25, 28, 33 e 38; depois contámos 50 000 caracteres de prosa vulgar e obtivemos acordo dentro de 4,5 %. A diferença deve-se inteiramente a compostos, algarismos e URL.GuiaO que faz um bom slug de URL: estabilidade, legibilidade e o conflito entre as duasUm slug tem duas funções que puxam em sentidos opostos: é um identificador permanente e é um texto legível. Comprimento, hífenes, palavras vazias, datas, caracteres não ASCII e o padrão identificador + slug que consegue ambas as propriedades — com os números reais de um site que localiza 1736 slugs de ferramentas em seis línguas.GuiaEtiquetas title, meta descrições, e o que os motores fazem com elasO que a própria documentação da Google diz sobre reescrever títulos e sobre a meta descrição, em vez do que diz o folclore de SEO. Depois a parte mensurável: os títulos são truncados por largura em píxeis, pelo que dois títulos de exatamente sessenta caracteres podem ser desenhados com 204,55 píxeis de diferença e só um sobrevive.ExplicaçãoA densidade de palavras-chave é uma métrica morta, e eis o que a substituiuA densidade contava ocorrências porque a recuperação contava ocorrências. TF-IDF, depois BM25 com a sua curva de saturação, depois os embeddings substituíram-na. Eis a mesma página de 800 palavras pontuada de três formas, e porque é que as três divergem.ExplicaçãoContar carateres contra um limite que outra pessoa definiuUm emoji vale 1 carácter, ou 7, ou 11, ou 25, consoante quem conta. Qual deles o seu formulário, a sua base de dados e o seu gateway de SMS querem dizer — e um teste de uma só colagem para saber com qual está a lidar.

Ferramentas relacionadas

Fontes

Detetaste um erro neste artigo?