我们都在学校学过浮点数。与没有小数点的整数相反。
不需要成为数学高手也能知道数字 5 是一个整数,而 5.43 是一个浮点数。显然,我们都熟悉小数点,这是两者之间的明显区别点。
当然,浮点数在编程语言中的使用并不奇怪,而且由于它在日常计算中的必要性,甚至被归类为一种独特的原始数据类型。
当您开始学习 Python 或 Java 时,您将使用 float 数据类型。特别是,当您对两个浮点数本身进行除法运算时。
在 Python 中,即使您不必声明浮点数,将这些数字中的两个相除也可以得到相同数据类型的数字,如下所示:
我们也可以在 Java 中产生相同的结果,即使我们必须为所有使用的变量显式声明“float”原始数据类型。
可以看出,这种计算对于许多应用程序都是必需的,这正是这两种编程语言中浮点数运算作为一项功能可用的原因。
完全不同的是,如果您一直在学习 Solidity,您应该已经注意到它不包括浮点数数据类型。
相反,如果您在 Solidity 中编写过任何代码,您将使用有符号或无符号整数类型的数字。
所以,如果你想执行相同的计算——五除以二——如上一节所示,作为智能合约的一部分,它的外观如下:
但是,您获得的结果不会相同,因为 Solidity 不支持 float 数据类型。至少,现在还不行。
具体来说,Solidity 会将结果四舍五入为零。在这种情况下,如上所示,这将导致值为 2。将此视为两个数字的模运算。
虽然在正常情况下,这无关紧要,但有时结果可能会导致计算错误。这就是为什么建议尽可能避免或推迟除法运算。
即使学校的 BODMAS 规则要求所有数学学生在乘法之前执行除法计算,但如果您必须在 Solidity 中执行整数除法,则不建议这样做。
让我们通过这个简单的例子来找出为什么要对三个数字进行乘法和除法:
如果你在部署智能合约时输入数字一、三、五,你应该得到相同的值 getResult 和 getResult2 函数,对吧?
如果你使用一个简单的计算器,你应该得到一个浮点值 1.666,这在 Solidity 中转换为 1,这要归功于没有浮点值。
不幸的是,当您检查 getResult 和 getResult2 函数的结果时,情况并非如此,如下所示:
如果我们先执行除法,我们得到的最终结果为零。与 1 的预期值相反,当您将该操作推迟到 getResult 函数的末尾时。
如您所知,即使我们通过预期不存在浮点值来计算此值,仍然会出现一个可能导致精度损失的错误。反过来,这可以转化为可以轻松绕过的经济损失。
那么,我们如何防止整数除法错误呢?更重要的是,我们如何提高计算的精度?让我们找出三种最常见的方法来做到这一点。
鉴于有多种方法可以避免此错误,让我们从最简单的修复开始,然后在收工前再看几个。
方法#1:使用乘数
现在,除了将除法运算放在最后之外,确保不会出现错误或不精确值的一种方法是使用乘法器。在下面的示例中,我们将使用 100 的乘数以及之前使用的相同三个数字。
现在,当您使用以下代码部署合约并调用这两个函数时,输出如下:
由于所需的输出是 1.666 或 166/100,我们可以看到当乘数与三个数字一起工作时,getResult2 值为我们提供了必要的精度。当然,如果你不使用getResult函数中的乘数,你会得到1。
当您使用 Solidity 时,默认情况下会从结果中截断 0.666。因此,要检索此值,您只需将结果除以乘数即可。
正如您可能知道的那样,Solidity 在有符号和无符号整数的情况下四舍五入时趋向于零,因此此修复也适用于有符号整数,一旦您使用参数减一部署并运行下面的代码,三和五:
当谈到函数为有符号整数生成的值时,它们是:
显然,我们也可以使用乘数来保持有符号整数的精度。不过,有一种精确的方法可以对有符号整数进行四舍五入,我们将在接下来介绍。
方法 #2:对有符号整数使用底除法
现在,如果看一下下面的数字行,两个无符号整数之间的整数除法结果四舍五入更接近于零。与获得 1.666 的情况一样,Solidity 将其四舍五入为 1,这是一个较小的数字。
但是,对于有符号整数,-1.6666 的结果将四舍五入为 -1,这是两个数字中较大的一个。因此,必须在此处应用下限除法,而不是在 Solidity 中默认实现的四舍五入为零除法。当然,为了精确。
如果 float 数据类型可用,将计算 -1.666 的值。虽然 Solidity 会将其四舍五入为 -1,但将底除法应用于有符号整数会将其降低为 -2。
当您调用 getResult 和 getResult2 函数时,我们将获得如下所示的参数减去一、三和五的值:
如您所知,getResult 使用向零四舍五入的方法计算值,而 getResult2 通过底除法将其四舍五入为此处的最小整数。
方法 #3:使用 ABDKMath64x64 库
现在,对于最后一种方法,我们将使用 ABDKMath64x64 库,它将除法运算的结果转换为定点数。
再一次,据说使用这个库可以提高准确性,而不是 Solidity 默认提供的向零舍入方法。为了理解输出,让我们比较 add 和 div 函数的结果,如下所示:
当你在部署智能合约时添加参数1、1和1,你将获得值,如下所示:
add 函数返回一个整数值 2 应该不足为奇,三个参数相加等于这个值。对于 div 函数,返回一个 int 128 值,表示一个带符号的 64.64 位定点数,您可以使用它执行通常的数字运算。
当然,除了防止整数除法错误外,ABDKMath64x64 库并不是唯一可用于提高准确性的库。
还有其他几个例子,例如Fixidity 、 DSMath和使用不同数字格式的BANKEX库。与上例中使用的 64.64 位定点数字格式不同的数字格式。因此,虽然探索这些库似乎很有用,但请记住,它们的数字格式不适用于任何其他可用的库。