Tutorial de desenvolvimento blockchain 04
Uma imagem de capa sem sentido.
Atualmente, estou trabalhando em um projeto NFT, que requer divisão de pontos flutuantes para calcular a raridade. Após algumas pesquisas, aprendi que o Solidity não suporta totalmente os números de pontos flutuantes. No entanto, ainda fiz o trabalho. E neste tutorial, compartilharei minha abordagem.
O status atual
No Solidity, os Números de Pontos Fixos são do mesmo tipo que números flutuantes. No entanto, de acordo com o documentação oficial:
Os números de pontos fixos ainda não são totalmente suportados pelo Solidity. Eles podem ser declarados, mas não podem ser atribuídos a ‘de’ ou ao ‘para’.
Em outra palavra, inútil.
A solução existente
Muitas pessoas escolhem a biblioteca ABDK Math 64.64 . A função divi()
lida com a divisão de ponto flutuante. Ela recebe dois números inteiros assinados de 256 bits e retorna um número inteiro assinado de 128 bits, que simula um número do ponto fixo de 64.64- bits.
Minha solução
Depois de algumas pesquisas, criei minha solução e aqui está o código.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
import "@openzeppelin/contracts/utils/Strings.sol";
contract Division {
using Strings for uint256;
function division(uint256 decimalPlaces, uint256 numerator, uint256 denominator) public pure returns(uint256 quotient, uint256 remainder, string memory result) {
uint256 factor = 10**decimalPlaces;
quotient = numerator / denominator;
remainder = (numerator * factor / denominator) % factor;
result = string(abi.encodePacked(quotient.toString(), '.', remainder.toString()));
}
}
A ideia é simples. Um decimal tem uma parte inteira e uma parte fracionária. Para a parte inteira, a divisão fará o trabalho. Para a parte fracionária, essa função primeiro dimensiona o numerador por 10^n
vezes e em seguida divide o número usando o denominador, por último, obtém o restante usando o fator de escala 10^n
.
Além disso, há também uma versão em string do ponto flutuante retornada como result
para fins de legibilidade e exibição.
Eu implantei este contrato usando o Remix para testar. Como você pode ver, as entradas são 3, 10, 3
, o que significa que quero dividir 10 por 3 e manter 3 casas decimais. E o resultado é 3.333
.
Advertência e uma solução melhor
Enquanto escrevia a seção acima, percebi que havia um problema, o erro de arredondamento. Tentei calcular 20 por 3, isso resultará em 6.666
, enquanto o resultado esperado é 6.667
.
Para resolver o erro de arredondamento, aprimorei o código que lida com o erro de arredondamento como um profissional. Sinta-se à vontade para implantar este contrato e experimente você mesmo: )
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
import "@openzeppelin/contracts/utils/Strings.sol";
contract Division {
using Strings for uint256;
function division(uint256 decimalPlaces, uint256 numerator, uint256 denominator) public pure returns(uint256 quotient, uint256 remainder, string memory result) {
uint256 factor = 10**decimalPlaces;
quotient = numerator / denominator;
bool rounding = 2 * ((numerator * factor) % denominator) >= denominator;
remainder = (numerator * factor / denominator) % factor;
if (rounding) {
remainder += 1;
}
result = string(abi.encodePacked(quotient.toString(), '.', remainder.toString()));
}
}
Se você já tentou, pode encontrar esse problema. Calcular 34/33 com 2 casas decimais fornece o resultado 1.3, enquanto o resultado correto deve ser 1.03. O problema é que a função muda o restante da casa dos centésimos para décimos, porque o restante é de um dígito, enquanto esperamos que seja de dois dígitos. Para melhorar, essa função deve adicionar 0 antes do restante, antes de combiná-la na string de saída. E abaixo está a solução.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
import "@openzeppelin/contracts/utils/Strings.sol";
contract Division {
using Strings for uint256;
function division(uint256 decimalPlaces, uint256 numerator, uint256 denominator) public view returns(uint256 quotient, uint256 remainder, string memory result) {
uint256 factor = 10**decimalPlaces;
quotient = numerator / denominator;
bool rounding = 2 * ((numerator * factor) % denominator) >= denominator;
remainder = (numerator * factor / denominator) % factor;
if (rounding) {
remainder += 1;
}
result = string(abi.encodePacked(quotient.toString(), '.', numToFixedLengthStr(decimalPlaces, remainder)));
}
function numToFixedLengthStr(uint256 decimalPlaces, uint256 num) pure internal returns(string memory result) {
bytes memory byteString;
for (uint256 i = 0; i < decimalPlaces; i++) {
uint256 remainder = num % 10;
byteString = abi.encodePacked(remainder.toString(), byteString);
num = num/10;
}
result = string(byteString);
}
}
Palavras de despedida
Aí está! Espero que você ache este tutorial útil. Por favor, sinta-se à vontade para deixar um comentário se tiver alguma dúvida ou sugestão ( por exemplo, como melhorar o código ). Se você quiser que eu escreva outros tópicos sobre desenvolvimento blockchain, informe-me também!
Deseja se conectar? Sinta-se à vontade para entrar em contato no (meu LinkedIn).
Este artigo foi escrito por Sicong Zhao e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.
Oldest comments (0)