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.

Você vai aprender
  • Os formatos-padrão: C (moeda), N (número), F (casas fixas), P (porcentagem), D (inteiro com zeros) e X (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}" e string.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 em string. 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³
FormatoNomeO que faz1234.5678 vira
C / C2moeda (currency)símbolo do país, milhar, casasR$ 1.234,57
N2número (number)separador de milhar e N casas1.234,57
F2ponto fixo (fixed-point)N casas, sem separador de milhar1234,57
E2exponencialnotação científica1,23E+003
Escreva sempre o número de casas

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." 7 vira 007, mas 1234 continua 1234: o D nunca corta. Ótimo para números de pedido, de matrícula, de chamada.
N0 com inteiro
O D não põe ponto nos milhares. Para isso, use N0.
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. x minú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
  1. 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.
  2. Falsos empates com double: 1.005 nã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).
O espaço do 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
FormaQual 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.

A porcentagem saiu cem vezes maior (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 mesmo programa mostra 2 casas num computador e 3 em outro

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

FormatoParaExemplo (cultura pt-BR)Resultado
C, C0moeda1234.5m.ToString("C")R$ 1.234,50
N2número com milhar1234.5.ToString("N2")1.234,50
N0inteiro com milhar1234567.ToString("N0")1.234.567
F2casas fixas, sem milhar1234.5.ToString("F2")1234,50
P1porcentagem (× 100)0.125.ToString("P1")12,5%
D3inteiro com zeros à esquerda7.ToString("D3")007
X, X4hexadecimal (inteiros)255.ToString("X4")00FF
E2notação científica1234.5.ToString("E2")1,23E+003
"0.00"personalizado: 2 casas3.14159.ToString("0.00")3,14
"#,##0"personalizado: milhar1234.ToString("#,##0")1.234
"0.#"personalizado: casa opcional2.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:

$"{preco:}"

2. Zeros na frente

Complete para o pedido 42 aparecer como 00042:

pedido.ToString("")

3. Porcentagem

Que valor guardar em taxa para taxa.ToString("P0") mostrar 30%?

double taxa = ;

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}" ou x.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.
  • decimal arredonda como na escola; double vai 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.
  • D com decimal ou uma letra que não existe: FormatException ao rodar.