Ir para conteúdo

Tutorial: Como formatar dinheiro [INCRIVELMENTE RÁPIDO]


abidux

Posts Recomendados

INTRODUÇÃO

 

Bom dia.

Durante o desenvolvimento do meu servidor (duxnation) eu estou tendo que criar um formatador de dinheiro.

 

Eu tinha um método antigo que funcionava bem, mas eu queria melhorar a performance.

Consegui criar um algoritmo que faz isso numa média de 3900ns (0,0039ms) a partir da segunda execução.

 

Nesse post vou compartilhar o processo para a criação desse algoritmo.

Se você só quiser o código, tem um título bem grande no final do post com ele.

 

ANTES DE COMEÇAR

 

Alguns algoritmos compartilham essas variáveis:

private static final DecimalFormat formatter = new DecimalFormat("#,##0.00", DecimalFormatSymbols.getInstance(Locale.forLanguageTag("pt")));
private static final String[] SUFFIXES = {"K", "M", "B", "T", "Q", "Qq", "Sx", "Sp", "Oc", "N", "D", "Un", "DD"};

 

Não vou comentar muito sobre elas, até porque não usei o formatter no final e uma array é bem tranquilo de compreender.

 

PRIMEIRO ALGORITMO (~22800ns)

 

private static String format1(double number) {
    String formatted = formatter.format(number);
    if (number < 1000) return formatted;
    String[] sp = formatted.split("\\.");
    return sp[0] + "." + sp[1].substring(0, 2) + SUFFIXES[sp.length - 2];
}

 

Esse algoritmo é bem simples:

 

Formate o número.

Se for menor que mil, retorne o valor formatado (já está pronto)

Se não for, divida esse valor formatado a cada "\\." (ponto final colocado pelo formatador a cada três números)

Retorne o primeiro pedaço, mais um ponto final, mais os dois primeiros caracteres do segundo pedaço, mais o sufixo necessário.

 

Pronto. É só isso. Esse algoritmo roda em 0,0228ms e você pode usar ele se não for formatar muitos números.

Eu poderia ter parado aí, mas queria tentar acelerar.

 

SEGUNDO ALGORITMO (~24000ns)

 

private static String format2(double number) {
    String formatted = formatter.format(number);
    if (number < 1000) return formatted;
    String[] sp = formatted.split("\\.");
    return new StringBuilder(sp[0]).append(".").append(sp[1].substring(0, 2)).append(SUFFIXES[sp.length - 2]).toString();
}

 

Esse algoritmo funciona mais ou menos como o primeiro, mas eu tentei adicionar um StringBuilder para ver se agilizava o processo.

Como deu para perceber, não deu muito certo.

Eles rodam mais ou menos no mesmo tempo, mas, na média, esse ficou com 24000ns (0,024ms)

 

TERCEIRO ALGORITMO (~5300ns)

 

private static String format3(double number) {
    if (number < 1000) return formatter.format(number);
    char[] chars = String.valueOf((int)number).toCharArray();
    int suffix = (chars.length - 1) / 3 - 1;
    int n = chars.length % 3;
    if (n == 0) n = 3;

    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < n; i++)
        builder.append(chars[i]);
    return builder.append(".").append(chars[n]).append(chars[n + 1]).append(SUFFIXES[suffix]).toString();
}

 

Você pode ver que aqui eu ainda estou usando o formatter para números menores que mil. Então isso é igual ao primeiro.

Se o número não for menor que mil, eu dou cast nele para int (lembre-se disso para depois), transformo-o numa String e depois num char[]

Depois eu faço alguns cálculos para pegar o índice do sufixo (na variável suffix) e quantos números teriam antes da vírgula (na variável n)

Em seguida, monto tudo, como no segundo algoritmo.

 

Esse algoritmo é bem rapidinho. Eu ia parar nele, porque estava satisfeito com a velocidade.

Entretanto, se eu executar com um número maior que 2147483647 (valor máximo do Integer), ele quebra, porque eu dei cast.

 

QUARTO ALGORITMO (~11600ns)

 

private static String format4(double number) {
    if (number < 1000) return String.valueOf(number - number % .01);

    double n = number - number % 1;
    long length = length(n);
  
    long p = length%3;
    if (p == 0) p = 3;
  
    double base = n / Math.pow(10, length-p);
    int suffix = (int)((length - 1) / 3 - 1);
    return new StringBuilder().append(base - base % .01).append(SUFFIXES[suffix]).toString();
}

private static long length(double number) {
    for (long i = 0;;i++) {
        if (number / Math.pow(10, i) < 10) return i+1;
    }
}

 

Você pode reparar que eu usei uma técnica diferente para limitar as casas decimais dessa vez.

Estou usando uma expressão matemática: f(x) = x - x % 0,01 (Caso não saiba "%" significa resto da divisão)

Essa função tira todos os números depois da segunda casa decimal.

 

Por que eu decidi fazer dessa forma?

Sempre que você puder usar uma função matemática na programação, use. Elas costumam ser absurdamente mais rápidas, principalmente quanto competem com uma função como "split" ou "format" do formatter.

 

Continuando...

Se o número for menor que mil, volto o valor limitando as casas decimais.

Senão, salvo o número sem as casas decimais numa variável n

 

Depois usa função length que eu criei. Ela retorna a quantidade de algarismos antes da vírgula.

Na variável p eu defino quantos números eu quero antes da vírgula no número formatado.

Na variável base eu divido por 10^(length-p)

Em seguida, calculo o sufixo necessário e junto tudo, limitando as casas decimais no final.

 

ÚLTIMO ALGORITMO (~3900ns) [FIX 26-01-2023]

 

	public static String format5(double number) {
        StringBuilder builder = new StringBuilder();
        int suffix = -1;
        if (number >= 1000) {
            double n = number - number % 1;
            long length = length(n);

            long p = length%3;
            if (p == 0) p = 3;

            number = n / Math.pow(10, length-p);
            suffix = (int)((length - 1) / 3 - 1);
        }

        int base = (int) number;
        int decimals = (int)((number - base)*100);

        builder.append(base).append(".");
        if (decimals < 10) builder.append("0");
        builder.append(decimals);
        if (suffix != -1 && suffix < SUFFIXES.length) builder.append(SUFFIXES[suffix]);
        return builder.toString();
    }

    private static long length(double number) {
        for (long i = 0;;i++) {
            if (number / Math.pow(10, i) < 10) return i+1;
        }
    }

 

Esse algoritmo é grandinho. Bom, vamos lá...

Primeiro, eu crio um StringBuilder para montar minha String depois. Também inicio a variável suffix com o valor -1

 

Se o número for maior ou igual a mil, eu vou fazer o processo para ajustar e colocar o sufixo

Para isso, pego só a parte inteira do número. Por algum motivo, isso faz o algoritmo ser alguns ns mais rápido.

 

Depois, uso a função length para calcular os algarismos antes da vírgula no número original.

Em seguida, calculo o p, para mover a vírgula.

Movo a vírgula como no último algoritmo, usando: n / 10^(length-p)

Por final, calculo o sufixo necessário.

 

Depois disso vem a mágica. Separei o double em dois int. O processo de transformar int em String é muito mais rápido do que double para String.

No final do algoritmo, junto tudo usando o builder.

 

CONCLUSÃO

 

É importante dizer que os algoritmos não são 100% precisos porque matemática não é tão precisa quando se trata de números decimais, mas os erros são mínimos.

 

Foi um processo muito divertido fazer esses algoritmos e espero que gostem desse "tutorial" que mais foi um jeito de eu compartilhar meu hobby com vocês!

Espero que tenham aprendido algo novo hoje e, se gostaram, considerem clicar no amei/gostei no post :)

 

Se quiser acompanhar meus projetos, você pode entrar no discord do meu servidor, onde posto minhas novas criações: https://discord.gg/R3dDARsRR4

 

Até mais!

Editado por abidux
Link para o comentário
Compartilhar em outros sites

Participe da Conversa

Você pode postar agora e se cadastrar mais tarde. Se você tiver uma conta, a class='ipsType_brandedLink' href='https://gamersboard.com.br/login/' data-ipsDialog data-ipsDialog-size='medium' data-ipsDialog-title='Sign In Now'>acesse agora para postar com sua conta.
Observação: sua postagem exigirá aprovação do moderador antes de ficar visível.

Visitante
Responder

×   Você colou conteúdo com formatação.   Remover formatação

  Apenas 75 emoticons são permitidos.

×   Seu link foi incorporado automaticamente.   Exibir como um link em vez disso

×   Seu conteúdo anterior foi restaurado.   Limpar Editor

×   Você não pode colar imagens diretamente. Carregar ou inserir imagens do URL.

Processando...
×
×
  • Criar Novo...