WEB3DEV

Cover image for Simulando a Divisão de Pontos Flutuantes em Solidity
Adriano P. Araujo
Adriano P. Araujo

Posted on

Simulando a Divisão de Pontos Flutuantes em Solidity

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()));

    }

}



Enter fullscreen mode Exit fullscreen mode

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.

Fig1. Demo no Remix

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()));

    }

}

Enter fullscreen mode Exit fullscreen mode

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);

    }

}

Enter fullscreen mode Exit fullscreen mode

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)