C# Formatar Números
O caixa da lojinha calcula o total: 1234.5. Na tela, porém, a Ana quer ver R$ 1.234,50: com o símbolo do real, o ponto separando os milhares e duas casas depois da vírgula. Transformar um número num texto "bonito" se chama formatar, e o C# faz isso com letras curtas: C para moeda, N2 para número com duas casas, P para porcentagem, D3 para inteiros com zeros na frente. Nesta aula você vai conhecer cada formato, ver a saída real numa máquina em português e aprender a alinhar valores em colunas.
- Os formatos-padrão:
C(moeda),N(número),F(casas fixas),P(porcentagem),D(inteiro com zeros) eX(hexadecimal). - O número depois da letra (
N2,P1,D3) e por que é bom escrevê-lo sempre. - Três lugares onde o formato funciona:
ToString("C"),$"{x:C}"estring.Format("{0:C}", x). - Formatos personalizados (
"0.00","#,##0") e alinhar colunas com{valor,10:C}. - Como o arredondamento funciona, por que o resultado depende da cultura e os erros reais.
Todos os programas desta aula foram rodados com o .NET 10 na máquina fictícia da Ana (usuária ana, computador notebook, Ubuntu em português do Brasil, cultura pt-BR), na pasta ~/cs, com dotnet run arquivo.cs.
A ideia: a etiqueta de preço
O número guardado na memória é só uma quantidade: 1234,5. A etiqueta que vai na prateleira é outra coisa: um texto com R$, ponto, vírgula e duas casas. A mesma quantidade pode ganhar etiquetas diferentes: R$ 1.234,50 no Brasil, $1,234.50 nos Estados Unidos, 1.234,50 numa planilha.
Formatar é escolher a etiqueta. O número em si não muda; só o jeito de mostrar.
1. A primeira formatação
decimal preco = 1234.5m;
Console.WriteLine(preco);
Console.WriteLine(preco.ToString("C"));
Console.WriteLine(preco.ToString("N2"));
Console.WriteLine(preco.ToString("F2"));
Console.WriteLine($"{preco:C}");
$ dotnet run basico.cs 1234,5 « sem formato: só a vírgula da cultura pt-BR R$ 1.234,50 « C = moeda 1.234,50 « N2 = número com separador de milhar e 2 casas 1234,50 « F2 = 2 casas, sem separador de milhar R$ 1.234,50 « o mesmo C, dentro da interpolação
- preco.ToString("C")
ToString("para texto") transforma o número emstring. O texto entre aspas é o formato:"C", de currency ("moeda").- "N2"
- A letra diz o tipo de etiqueta (
N= number, número) e o número depois dela diz quantas casas decimais (2). - $"{preco:C}"
- Na interpolação (aula Interpolação), o formato vem depois de
:, dentro das chaves, sem aspas. É o jeito mais usado. - a vírgula e o R$
- Vêm da cultura do computador. A máquina da Ana está em português do Brasil, então o .NET usa vírgula decimal, ponto nos milhares e
R$. Veja a seção 7.
Missão: a etiqueta saiu Preço: 1234,5. Acrescente o formato de moeda dentro das chaves para aparecer Preço: R$ 1.234,50.
Preço: R$ 1.234,50
decimal preco = 1234.5m;
Console.WriteLine($"Preço: {preco}");
decimal preco = 1234.5m;
Console.WriteLine($"Preço: {preco:C}");
2. Os formatos de número: C, N, F, E
Veja o mesmo double com vários formatos:
double x = 1234.5678;
Console.WriteLine(x.ToString("N"));
Console.WriteLine(x.ToString("N0"));
Console.WriteLine(x.ToString("N1"));
Console.WriteLine(x.ToString("N3"));
Console.WriteLine(x.ToString("F0"));
Console.WriteLine(x.ToString("F3"));
Console.WriteLine(x.ToString("C"));
Console.WriteLine(x.ToString("C0"));
Console.WriteLine(x.ToString("E2"));
$ dotnet run tabela.cs 1.234,568 « N sem número: aqui saíram 3 casas (veja o aviso abaixo) 1.235 « N0: sem casas, arredondado 1.234,6 1.234,568 1235 « F0: sem casas e sem separador de milhar 1234,568 R$ 1.234,57 « C: 2 casas, arredondado R$ 1.235 « C0: moeda sem centavos 1,23E+003 « E2: notação científica, 1,23 × 10³
| Formato | Nome | O que faz | 1234.5678 vira |
|---|---|---|---|
C / C2 | moeda (currency) | símbolo do país, milhar, casas | R$ 1.234,57 |
N2 | número (number) | separador de milhar e N casas | 1.234,57 |
F2 | ponto fixo (fixed-point) | N casas, sem separador de milhar | 1234,57 |
E2 | exponencial | notação científica | 1,23E+003 |
Sem o número ("N", "P"), a quantidade de casas vem da cultura do sistema, e ela muda de computador para computador. No Ubuntu da Ana, "N" deu 3 casas (1.234,568); no Windows em português, o mesmo programa costuma dar 2. Com "N2", sai 1.234,57 em qualquer lugar. A exceção é o "C", que usa as casas da moeda (2 para o real).
3. Porcentagem: P
O formato P (percent) multiplica por 100 e põe o %. Por isso, a taxa de 12,5% se guarda como 0.125:
double taxa = 0.125;
Console.WriteLine(taxa.ToString("P"));
Console.WriteLine(taxa.ToString("P0"));
Console.WriteLine(taxa.ToString("P1"));
Console.WriteLine(taxa.ToString("P2"));
Console.WriteLine((0.5).ToString("P0"));
Console.WriteLine((12.5).ToString("P0"));
$ dotnet run pct.cs 12,500% « P sem número: 3 casas nesta máquina 12% « P0: o 12,5 arredondou para 12 (veja a seção 6) 12,5% 12,50% « P2 50% 1.250% « 12.5 × 100: o P não sabe que você já tinha multiplicado!
A última linha é o erro mais comum com P: guardar 12.5 pensando em "12,5%". Para o P, 12.5 é 1250%. Guarde a fração (0.125) e deixe o P multiplicar, ou guarde 12.5 e escreva o % você mesmo: $"{12.5:N1}%".
4. Inteiros: D e X
O formato D (decimal digits, "dígitos") é só para inteiros. O número depois dele é a quantidade mínima de dígitos, completada com zeros à esquerda:
int numero = 7;
Console.WriteLine(numero.ToString("D3"));
Console.WriteLine(numero.ToString("D5"));
Console.WriteLine((-42).ToString("D4"));
Console.WriteLine(1234567.ToString("N0"));
Console.WriteLine(255.ToString("X"));
Console.WriteLine(255.ToString("X4"));
$ dotnet run inteiro.cs 007 « pedido número 007 00007 -0042 « o sinal fica na frente dos zeros 1.234.567 « para separar milhares num inteiro, use N0 FF « X = hexadecimal (base 16): 255 é FF 00FF
- D3
- "Pelo menos 3 dígitos."
7vira007, mas1234continua1234: oDnunca corta. Ótimo para números de pedido, de matrícula, de chamada. - N0 com inteiro
- O
Dnão põe ponto nos milhares. Para isso, useN0. - X
- Mostra o número em base 16 (de 0 a 9 e de A a F). Aparece em cores (
#FF0000) e códigos de erro.xminúsculo dá letras minúsculas (ff).
5. Formatos personalizados e alinhamento
Monte o seu formato
Além das letras, dá para desenhar o formato com símbolos. O 0 é um dígito obrigatório (aparece zero se não houver); o # é um dígito opcional; o ponto marca onde fica a vírgula decimal, e a vírgula marca o separador de milhar. No formato, você escreve como nos Estados Unidos, e o .NET troca pelos símbolos da cultura na saída:
double x = 1234.5;
Console.WriteLine(x.ToString("0.00"));
Console.WriteLine(x.ToString("#,##0.00"));
Console.WriteLine(x.ToString("00000"));
Console.WriteLine(7.ToString("000"));
Console.WriteLine(0.5.ToString("0.#"));
Console.WriteLine(2.0.ToString("0.#"));
Console.WriteLine(0.3.ToString("0%"));
$ dotnet run custom.cs 1234,50 « 0.00: sempre duas casas 1.234,50 « #,##0.00: com separador de milhar (igual ao N2) 01235 « 00000: cinco dígitos, arredondado 007 « igual ao D3 0,5 « 0.#: uma casa, só se precisar 2 « ...e aqui não precisou 30% « % no formato também multiplica por 100
Qual usar? Prefira as letras (N2, C, P1): são curtas e todo mundo conhece. Use os personalizados quando nenhuma letra serve, como o "0.#" ("casa decimal só se precisar").
Alinhar em colunas: {valor,largura:formato}
Na interpolação, a vírgula depois do valor reserva uma largura (aula Interpolação). Junto com o formato, ela monta tabelas alinhadas, como a nota da lojinha:
Console.WriteLine($"{"Produto",-10}{"Qtd",5}{"Preço",12}");
Console.WriteLine($"{"Arroz",-10}{2,5}{5.9m,12:C}");
Console.WriteLine($"{"Feijão",-10}{1,5}{8.5m,12:C}");
Console.WriteLine($"{"Café",-10}{3,5}{17.25m,12:C}");
Console.WriteLine(new string('-', 27));
Console.WriteLine($"{"Total",-10}{6,5}{72.45m,12:C}");
$ dotnet run alinhar.cs Produto Qtd Preço Arroz 2 R$ 5,90 Feijão 1 R$ 8,50 Café 3 R$ 17,25 --------------------------- Total 6 R$ 72,45
- {"Arroz",-10}
- 10 posições, texto à esquerda (largura negativa). É o costume para nomes.
- {2,5}
- 5 posições, à direita (largura positiva). É o costume para números: as unidades ficam umas embaixo das outras.
- {5.9m,12:C}
- Largura e formato juntos: primeiro a vírgula com a largura, depois os dois-pontos com o formato. A ordem é sempre essa:
{valor,largura:formato}.
Experimente: acrescente um produto à nota, mude as larguras (o que acontece com um nome maior que 10 letras?) e troque C por N2.
Console.WriteLine($"{"Produto",-10}{"Preço",12}");
Console.WriteLine($"{"Arroz",-10}{5.9m,12:C}");
Console.WriteLine($"{"Feijão",-10}{8.5m,12:C}");
Console.WriteLine($"{"Pedido nº",-10}{7,12:D3}");
6. Arredondamento: decimal × double
Ao cortar casas, o formato arredonda. Com decimal, o .NET arredonda como na escola: 5 ou mais sobe. Com double, há duas surpresas:
Console.WriteLine(0.125m.ToString("F2"));
Console.WriteLine(2.5m.ToString("F0"));
Console.WriteLine(0.125.ToString("F2"));
Console.WriteLine(2.5.ToString("F0"));
Console.WriteLine(3.5.ToString("F0"));
Console.WriteLine(1.005.ToString("F2"));
Console.WriteLine(1.005m.ToString("F2"));
$ dotnet run arred.cs 0,13 « decimal: 0,125 sobe para 0,13, como na escola 3 « decimal: 2,5 sobe para 3 0,12 « double: empate exato vai para o número PAR (2) 2 « double: 2,5 vai para 2 (par)... 4 « ...e 3,5 vai para 4 (par) 1,00 « double: 1.005 é guardado como 1,00499999…, então desce 1,01 « decimal: 1,005 é exato, e sobe
- Empates com
double: quando o número está exatamente no meio (0,125; 2,5), o .NET arredonda para o lado do dígito par. Essa regra, o "arredondamento do banqueiro", evita que os erros se acumulem sempre para cima em milhões de contas. - Falsos empates com
double:1.005não é guardado como 1,005, e sim como um número um pouquinho menor (aula float, double e decimal). Ele nem é um empate: desce para 1,00.
Moral: para dinheiro, use decimal, e o arredondamento será o que o cliente espera. Para arredondar o valor (e não só a etiqueta), existe Math.Round (aula Matemática).
7. A cultura decide os símbolos
Vírgula ou ponto, R$ ou $: tudo vem da cultura (CultureInfo), o conjunto de regras de um país e idioma. Sem dizer nada, o .NET usa a cultura do sistema. Dá para escolher outra:
using System.Globalization;
decimal preco = 1234.5m;
Console.WriteLine(preco.ToString("C"));
Console.WriteLine(preco.ToString("C", new CultureInfo("en-US")));
Console.WriteLine(preco.ToString("C", new CultureInfo("pt-PT")));
Console.WriteLine(preco.ToString("N2", CultureInfo.InvariantCulture));
Console.WriteLine(CultureInfo.CurrentCulture.Name);
$ dotnet run cultura.cs R$ 1.234,50 « a cultura da máquina (pt-BR) $1,234.50 « Estados Unidos: ponto decimal, vírgula nos milhares 1 234,50 € « Portugal: espaço nos milhares, euro no fim 1,234.50 « invariante: regras fixas, iguais em qualquer computador pt-BR
- using System.Globalization;
- Avisa que vamos usar nomes do "pacote" de globalização, como
CultureInfo(aula Namespaces e using). - ToString("C", new CultureInfo("en-US"))
- O segundo argumento escolhe a cultura.
"en-US"é inglês dos Estados Unidos;"pt-PT", português de Portugal. - CultureInfo.InvariantCulture
- A cultura "neutra", que não muda com o computador. Use para gravar números em arquivos e trocar dados entre programas (aula Cultura).
R$ nem sempre é um espaço comum
Na máquina da Ana, o espaço entre R$ e o número é um espaço comum (o byte 20, conferido com xxd). Mas o espaço que separa os milhares em 1 234,50 € já é outro caractere: o espaço sem quebra (U+00A0, bytes c2 a0), que impede a linha de quebrar no meio do número. Em outros sistemas e versões, o próprio R$ pode vir com esse espaço especial. Por isso, não compare textos formatados (texto == "R$ 5,00") para tomar decisões: compare os números, e formate só para mostrar.
Formas diferentes de formatar
decimal preco = 9.9m;
Console.WriteLine(preco.ToString("C"));
Console.WriteLine($"{preco:C}");
Console.WriteLine(string.Format("{0:C}", preco));
Console.WriteLine("{0:C}", preco);
$ dotnet run formas.cs R$ 9,90 R$ 9,90 R$ 9,90 R$ 9,90
| Forma | Qual usar |
|---|---|
$"{preco:C}" | O padrão, quando o número vai dentro de uma frase. |
preco.ToString("C") | Quando você quer só o número formatado, numa variável: string etiqueta = preco.ToString("C");. É também o jeito de passar outra cultura. |
string.Format("{0:C}", preco) | Código antigo, ou quando o texto com as lacunas vem de fora do programa. |
Console.WriteLine("{0:C}", preco) | Tutoriais antigos. Prefira a interpolação. |
Quando dá errado
Unhandled exception. System.FormatException: Format specifier was invalid. com D
decimal preco = 9.9m;
Console.WriteLine(preco.ToString("D2"));
$ dotnet run ef1.cs Unhandled exception. System.FormatException: Format specifier was invalid. at System.Number.NumberToString[TChar](ValueListBuilder`1& vlb, NumberBuffer& number, Char format, Int32 nMaxDigits, NumberFormatInfo info) at System.Number.FormatDecimal(Decimal value, ReadOnlySpan`1 format, NumberFormatInfo info) at Program.<Main>$(String[] args) in /home/ana/cs/ef1.cs:line 2
O que significa: "o especificador de formato era inválido". O D só serve para inteiros (int, long…), e preco é decimal. O compilador não confere o texto do formato: o erro só aparece ao rodar, com código de saída 134.
Como consertar: para casas decimais, use N2 ou F2. Para zeros na frente de um número com vírgula, um formato personalizado: "000.00".
System.FormatException: Format specifier was invalid. com uma letra que não existe
int x = 5;
Console.WriteLine(x.ToString("Z"));
$ dotnet run ef2.cs Unhandled exception. System.FormatException: Format specifier was invalid. at System.Number.NumberToString[TChar](ValueListBuilder`1& vlb, NumberBuffer& number, Char format, Int32 nMaxDigits, NumberFormatInfo info) at System.Number.<FormatInt32>g__FormatInt32Slow|21_0(Int32 value, Int32 hexMask, String format, IFormatProvider provider) at Program.<Main>$(String[] args) in /home/ana/cs/ef2.cs:line 2
O que significa: não existe o formato Z. Uma letra sozinha (ou letra + número) é lida como formato-padrão, e se ela não existe, dá exceção.
Como consertar: use uma das letras da tabela de Referência.
error CS1503: Argumento 1: não é possível converter de "string" para "System.IFormatProvider?"
string preco = "12.5";
Console.WriteLine(preco.ToString("C"));
$ dotnet run ef4.cs /home/ana/cs/ef4.cs(2,34): error CS1503: Argumento 1: não é possível converter de "string" para "System.IFormatProvider?" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: preco é um texto, não um número. O ToString de uma string não aceita formatos como "C" (ele espera outra coisa, um "fornecedor de formato", e por isso a mensagem fala em IFormatProvider).
Como consertar: transforme o texto em número primeiro, com decimal.Parse ou decimal.TryParse (aula Parse e TryParse), e formate o número.
1.250%)
O que significa: o P multiplica por 100, e o valor já estava em "pontos percentuais" (12.5 em vez de 0.125).
Como consertar: guarde a fração (0.125), ou formate sem P: $"{taxa:N1}%".
O que significa: você usou "N" ou "P" sem o número de casas, e a quantidade veio da cultura do sistema.
Como consertar: escreva as casas: "N2", "P1".
Referência
| Formato | Para | Exemplo (cultura pt-BR) | Resultado |
|---|---|---|---|
C, C0 | moeda | 1234.5m.ToString("C") | R$ 1.234,50 |
N2 | número com milhar | 1234.5.ToString("N2") | 1.234,50 |
N0 | inteiro com milhar | 1234567.ToString("N0") | 1.234.567 |
F2 | casas fixas, sem milhar | 1234.5.ToString("F2") | 1234,50 |
P1 | porcentagem (× 100) | 0.125.ToString("P1") | 12,5% |
D3 | inteiro com zeros à esquerda | 7.ToString("D3") | 007 |
X, X4 | hexadecimal (inteiros) | 255.ToString("X4") | 00FF |
E2 | notação científica | 1234.5.ToString("E2") | 1,23E+003 |
"0.00" | personalizado: 2 casas | 3.14159.ToString("0.00") | 3,14 |
"#,##0" | personalizado: milhar | 1234.ToString("#,##0") | 1.234 |
"0.#" | personalizado: casa opcional | 2.0.ToString("0.#") | 2 |
{x,10:C} | largura 10, à direita, moeda | $"[{5.9m,10:C}]" | [ R$ 5,90] |
{x,-10} | largura 10, à esquerda | $"[{"oi",-4}]" | [oi ] |
As letras podem ser maiúsculas ou minúsculas ("c" = "C"), com exceção do X/x, que muda a caixa das letras de A a F.
Exercícios
1. Moeda
Complete para mostrar R$ 49,90:
2. Zeros na frente
Complete para o pedido 42 aparecer como 00042:
3. Porcentagem
Que valor guardar em taxa para taxa.ToString("P0") mostrar 30%?
4. Qual a diferença entre N2 e F2 para 1234.5?
5. O que acontece com 9.9m.ToString("D2")?
6. Em $"{valor,10:C}", o que é o 10?
Missão final: o recibo com desconto
Missão: o recibo da lojinha está cru: 100, 0,125, 87,500. Formate o preço e o total como moeda e o desconto como porcentagem com uma casa (P1), para aparecer o recibo abaixo. Não mude as contas.
Preço: R$ 100,00 Desconto: 12,5% Total: R$ 87,50
decimal preco = 100m;
decimal desconto = 0.125m;
decimal total = preco - preco * desconto;
Console.WriteLine($"Preço: {preco}");
Console.WriteLine($"Desconto: {desconto}");
Console.WriteLine($"Total: {total}");
decimal preco = 100m;
decimal desconto = 0.125m;
decimal total = preco - preco * desconto;
Console.WriteLine($"Preço: {preco:C}");
Console.WriteLine($"Desconto: {desconto:P1}");
Console.WriteLine($"Total: {total:C}");
Depois, alinhe o recibo em colunas: {"Preço:",-10}{preco,12:C}, e faça o mesmo nas outras linhas.
Resumo
- Formatar é escolher como um número aparece; o valor não muda. Use
$"{x:formato}"oux.ToString("formato"). C= moeda (R$ 1.234,50);N2= milhar e 2 casas;F2= 2 casas sem milhar;P1= porcentagem (multiplica por 100);D3= inteiro com zeros (007);X= hexadecimal.- Escreva sempre as casas (
N2,P1): sem elas, a quantidade depende do sistema. - Personalizados:
"0.00","#,##0","0.#". Alinhamento:{valor,largura:formato}; largura negativa encosta à esquerda. decimalarredonda como na escola;doublevai para o par nos empates e tem falsos empates (1.005).- Vírgula, ponto e
R$vêm da cultura (pt-BR). Não compare textos formatados; compare números. Dcomdecimalou uma letra que não existe:FormatExceptionao rodar.