Todos nós aprendemos sobre números de ponto flutuante na escola. Ao contrário dos números inteiros que não têm pontos decimais.
Não é preciso ser um gênio da matemática para saber que o número cinco é um número inteiro, enquanto 5,43 é um número de ponto flutuante. Claramente, estamos todos familiarizados com o ponto decimal, que é um claro ponto de diferenciação entre os dois.
Claro, não é nenhuma surpresa que floats sejam usados em linguagens de programação e até mesmo classificados como um tipo de dado primitivo distinto por causa de quão necessário é para a computação diária.
Ao começar a aprender Python ou Java, você usará o tipo de dados float. Particularmente, quando você realiza a operação de divisão entre dois números de ponto flutuante.
Em Python, mesmo que você não precise declarar um número de ponto flutuante, a divisão de dois desses números pode resultar em um número do mesmo tipo de dados, conforme mostrado abaixo:
Também podemos produzir o mesmo resultado em Java, mesmo que tenhamos que declarar explicitamente o tipo de dados primitivo 'float' para todas as variáveis usadas.
Como se pode perceber, esse tipo de cálculo é necessário para uma série de aplicações, e é exatamente por isso que as operações com números de ponto flutuante estão disponíveis como um recurso em ambas as linguagens de programação.
Bem diferente, se você está aprendendo Solidity, você deve ter notado que ele não inclui o tipo de dados de número de ponto flutuante.
Em vez disso, se você escreveu qualquer código no Solidity, você usaria números do tipo inteiro assinado ou não assinado.
Então, se você quiser fazer o mesmo cálculo - cinco dividido por dois - conforme mostrado na seção anterior, veja como ficará, como parte de um contrato inteligente:
No entanto, o resultado obtido não será o mesmo, pois o Solidity não suporta o tipo de dados float. Pelo menos, não ainda.
Especificamente, o Solidity arredondará o resultado para zero. O que, neste caso, e conforme mostrado acima, resultará em um valor de dois. Pense nisso como a operação de módulo em ambos os números.
Embora em circunstâncias normais isso não deva importar muito, há momentos em que o resultado pode levar a um erro de cálculo. É por isso que se recomenda evitar ou adiar ao máximo a operação de divisão.
Mesmo que a regra do BODMAS na escola exija que todos os alunos de matemática realizem o cálculo da divisão antes da multiplicação, isso não é recomendado se você tiver que realizar a divisão inteira no Solidity.
Vamos descobrir por que, com este exemplo simples, que realiza multiplicação e divisão com três números:
Se você inserir os números um, três e cinco ao implantar o contrato inteligente, deverá obter o mesmo valor para as funções getResult e getResult2, certo?
Se você usar uma calculadora simples, deverá obter um valor flutuante de 1,666, que se traduz em um no Solidity, graças à ausência dos valores flutuantes.
Infelizmente, não é isso que acontece, quando você verifica os resultados das funções getResult e getResult2, conforme mostrado abaixo:
Se fizermos a divisão primeiro, obteremos um resultado final igual a zero. Ao contrário do valor esperado de um, quando você adia essa operação para o fim na função getResult.
Como você pode ver, mesmo que tenhamos calculado esse valor antecipando a ausência de valores flutuantes, ainda há um erro que pode causar perda de precisão. O que, por sua vez, pode se traduzir em uma perda econômica que pode ser facilmente contornada.
Então, como evitamos o erro de divisão inteira? Mais importante, como aumentamos a precisão de nossa computação? Vamos descobrir as três maneiras mais comuns de fazer isso.
Dado que há várias abordagens para contornar esse erro, vamos começar com a correção mais simples e examinar mais algumas antes de encerrar o dia.
Método #1: Use um multiplicador
Agora, além de colocar a operação de divisão por último, uma maneira de garantir que você não acabe com erros ou valores imprecisos é usar um multiplicador. No exemplo abaixo, usaremos um multiplicador de 100 junto com os mesmos três números usados anteriormente.
Agora, quando você implanta o contrato com o código a seguir e chama as duas funções, esta é a saída:
Como a saída desejada é 1,666 ou 166/100, podemos ver que o valor getResult2 nos fornece a precisão necessária quando o multiplicador trabalha em conjunto com os três números. Obviamente, se você não usar o multiplicador como na função getResult, obterá 1.
Onde 0,666 é truncado do resultado, como esperado por padrão quando você usa o Solidity. Então, para recuperar esse valor, basta dividir o resultado pelo multiplicador.
Como você deve saber, o Solidity se move em direção a zero quando se trata de arredondar no caso de inteiros assinados e não assinados, então essa correção também funciona no caso de inteiros assinados, uma vez que você implanta e executa o código abaixo com os argumentos menos um , três e cinco:
Quando se trata dos valores gerados pelas funções para números inteiros com sinal, aqui estão eles:
Claramente, também podemos manter a precisão para números inteiros com sinal, usando um multiplicador. Embora haja uma maneira precisa de arredondar inteiros com sinal que veremos a seguir.
Método nº 2: Use a divisão de piso para números inteiros com sinal
Agora, se olharmos para a linha numérica abaixo, o resultado da divisão inteira entre dois inteiros sem sinal é arredondado para mais próximo de zero. Como no caso de obter 1,666, o Solidity arredonda para 1, que é um número menor.
No entanto, quando se trata de números inteiros com sinal, um resultado de -1,6666 será arredondado para -1, que é o maior dos dois números. Portanto, a divisão do piso deve ser aplicada aqui, em oposição à divisão arredondada para zero que é implementada no Solidity por padrão. Por uma questão de precisão, é claro.
Se o tipo de dados float estivesse disponível, o valor de -1,666 seria calculado. Enquanto o Solidity arredondaria para -1, a aplicação da divisão de piso a números inteiros com sinal reduzirá para -2.
Quando você chama as funções getResult e getResult2, obtemos o valor mostrado abaixo para os argumentos menos um, três e cinco:
Como você pode ver, getResult calcula o valor usando a abordagem de arredondamento para zero, enquanto getResult2 arredonda para o menor inteiro aqui, em virtude da divisão do piso.
Método #3: Use a biblioteca ABDKMath64x64
Agora, para o método final, usaremos a biblioteca ABDKMath64x64, que converte o resultado das operações de divisão em números de ponto fixo.
Mais uma vez, diz-se que o uso dessa biblioteca melhora a precisão em oposição ao método de arredondamento para zero que está disponível por padrão no Solidity. Para entender a saída, vamos comparar os resultados da adição com a função div, conforme mostrado abaixo:
Ao adicionar os argumentos 1, 1 e 1 ao implantar o contrato inteligente, você obtém os valores, conforme mostrado abaixo:
Não deve ser surpresa que a função add retorne um valor inteiro de dois, com os três argumentos somando tanto. Quanto à função div, é retornado um valor int 128 que representa um número de ponto fixo de 64,64 bits com sinal e com o qual você pode executar as operações numéricas usuais.
Obviamente, a biblioteca ABDKMath64x64 não é a única que pode ser usada para melhorar a precisão, além de evitar o erro de divisão inteira.
Existem vários outros com alguns exemplos sendo Fixidity , DSMath e as bibliotecas BANKEX que usam formatos numéricos diferentes. Formatos numéricos diferentes do formato numérico de ponto fixo de 64,64 bits usado no exemplo acima. Portanto, embora essas bibliotecas possam parecer úteis para explorar, lembre-se de que seus formatos numéricos não funcionarão com nenhuma das outras bibliotecas disponíveis.