本文是关于在 Solidity 中进行数学运算的系列文章中的第四篇。这次的主题是:复利

介绍

在我们之前的文章中,我们讨论了百分比以及它们是如何在 Solidity 中计算的。在金融数学中,百分比通常与贷款和存款支付的利息有关。在每个时间段结束时,比如一个月或一年,本金的一定百分比将支付给贷方或存款持有人。这种模式称为单利,每期支付的百分比称为定期利率。

在计算机程序中,通常使用利率而不是利率。例如,对于 3% 的利率,该比率为 0.03。因此,一个时期的利息支付金额可以计算为利息率乘以本金金额,并且从上一篇文章中我们已经知道如何在 Solidity 中有效且准确地执行此操作。

单利模式很简单,但如果利息不是立即支付给贷方或存款持有人,而是加到本金上,事情就会变得更加复杂。在这种情况下,过去期间累积的利息会影响未来收取的利息金额。

在本文中,我们将讨论如何在 Solidity 中实现该模式,该模式的名称为:复利

定期复利

我们已经知道如何计算单利。计算复利的直接解决方案是在每个时间段结束时计算单利,然后将计算出的利息与本金相加。在高级语言中,例如 JavaScript,它看起来像这样:

principal += ratio * principal; // Do after each time period

我们使用mulDiv上一篇文章中的函数,并假设它ratio是一个定点数,点后有 18 位小数。

上面的代码在大多数情况下都可以工作,但它的+=操作可能会溢出,所以为了使代码安全,我们需要这样修改它:

principal = add (principal, mulDiv (ratio, principal, 10^18));

这种变体可能适合生产,但难以阅读。在本文中,为简单起见,我们将使用普通算术运算,就好像 Solidity 支持分数并且算术运算不会溢出一样。在实际代码中,这些操作应该被适当的函数代替。

一旦我们知道如何计算单个期间的复利,问题就是:

我们如何在每个时间段结束时触发复利?

剧透:我们不应该

与传统应用程序不同,智能合约不能有任何后台活动。合约的字节码仅在交易调用合约时执行,无论是直接调用还是通过另一个智能合约调用。可以依靠第三方服务,如Provable(以前称为 Oraclize)定期调用特定的智能合约,或者可以从经济上激励普通人这样做。

这种方法可行,但有许多缺点。首先,有人必须为汽油付费,所以它不是免费的。其次,必须在每期末计算复利,即使在接下来的时间段内没有人会使用更新后的本金。第三,时间越短,复合越频繁,消耗的gas越多。第四,对于短时间,这种方法是不准确的,因为交易挖掘时间不可预测,并且在网络负载高的时候可能会非常大。

因此,如果在每个周期结束时复利对 Solidity 来说不是一个好主意,那么

我们什么时候应该复利?

剧透:“惰性”复合

与其在每个时间段结束时复利,更好的方法是仅在有人需要获取本金或债务或存款时才复利,并在此时对自复利以来结束的所有时间段执行复利最后一次:

uint currentPeriod = block.timestamp / periodLength;
for (uint period = lastPeriod; period < currentPeriod; period++)principal += ratio * principal;
lastPeriod = currentPeriod;

此代码将所有尚未复利的利息添加到本金中,并且每次有人想要访问时都必须执行principal。这种方法被称为“惰性”复利,实际计算会推迟到有人真正需要他们的结果时才进行。

然而,上面显示的“惰性”复合的实现有一个重要问题。实际的 gas 消耗量线性取决于自上次执行复利计算以来经过了多少时间间隔。如果时间段很短,或者上一次复利是很久以前进行的,那么在所有经过的时间段内复利所需的气体量可能会超过区块气体限制,从而有效地使进一步的复利变得不可能。所以问题是:

如何更高效地进行“惰性”复利?

剧透:间隔加倍

首先我们注意到,单个时间段的组合利息可以这样重写:

principal *= 1 + ratio;

对于两个时间间隔,这将是:

principal *= (1 + ratio) * (1 + ratio);

然后我们注意到,(1+ r )²=1+(2 r + r ²),所以双倍时间间隔的有效利率为 2 r + r ²,其中r是单倍时间间隔的利率。如果我们想要复利的时间间隔数是偶数,我们可以通过将时间间隔持续时间加倍来减半时间间隔数。当时间间隔数为奇数时,我们可以只进行一次复利,从而使剩余的时间间隔数为偶数。这是代码:

function compound (uint principal, uint ratio, uint n)
public pure returns (uint) {while (n > 0) {if (n % 2 == 1) {principal += principal * ratio;n -= 1;} else {ratio = 2 * ratio + ratio * ratio;n /= 2;}}return principal;
}

上面的代码具有对数复杂度,并且在principalratio较大时运行良好,因此principal * ratioproduct 具有足够的有效小数位以获得不错的精度。但是,如果principalratio很小,上面的代码可能会产生不准确的结果。现在的问题是:

如何提高惰性复利的精度?

剧透:通过平方求幂

在上面显示的代码中,以下单独的代码丢失了精度:

principal += principal * ratio;

这是因为我们假设principal是整数,所以赋值必须舍入计算值。舍入可能会执行多次,舍入误差会累加。

为了解决这个问题,我们可能会注意到,对于 n 个时间间隔,利息可能会像这样复合:

principal *= (1 + ratio) ** n;

如果 Solidity 支持分数,这段代码就可以工作,但只要不支持,我们就需要自己实现求幂。我们使用与上一节中相同的对数复杂度方法,因此代码非常相似:

function pow (uint x, uint n)
public pure returns (uint r) {r = 1.0;while (n > 0) {if (n % 2 == 1) {r *= x;n -= 1;} else {x *= x;n /= 2;}}
}
function compound (uint principal, uint ratio, uint n)
public pure returns (uint) {return principal * pow (1 + ratio, n);
}

请注意该表达式:r = 1.0. 在这里要记住,我们在这里使用分数,就好像 Solidity 确实支持它们,但实际上并不支持它们。人们将不得不用实现分数数学的函数替换所有算术运算。例如,下面是使用ABDK Math 64.64库实现 64.64 位定点数算术运算的真实代码:

function pow (int128 x, uint n)
public pure returns (int128 r) {r = ABDKMath64x64.fromUInt (1);while (n > 0) {if (n % 2 == 1) {r = ABDKMath64x64.mul (r, x);n -= 1;} else {x = ABDKMath64x64.mul (x, x);n /= 2;}}
}
function compound (uint principal, uint ratio, uint n)
public pure returns (uint) {return ABDKMath64x64.mulu (pow (ABDKMath64x64.add (ABDKMath64x64.fromUInt (1), ABDKMath64x64.divu (ratio,10**18)),n),principal);
}

实际上,这个库已经有pow功能,可以用来代替我们的实现。

上面的代码非常精确和直接,但它仅适用于离散时间间隔。如果我们需要对任意时间间隔进行复利怎么办?这种模式被称为

连续复利

连续复利的想法是计算任意而不是固定时间段的利息。实现此目的的一种方法是使用小数周期。我们已经知道如何计算n期的复利:

principal *= (1 + ratio) ** n; 

假设时间段为一年,我们要计算 1 个月的复利,即一年的 1/12。那么公式应该是:

principal *= (1 + ratio) ** (1 / 12);

不幸的是,Solidity 和pow上面介绍的函数都不支持小数指数。我们可以通过整数幂和根,或者通过固定底数的对数和指数来实现它们,但是

有没有更简单的方法来进行连续复利?

剧透:是的:不要这样做

现实世界中的时间是连续的,或者至少看起来是这样。以太坊中的时间是离散的。它以秒为单位,用整数表示。因此,周期为 1 秒的周期性复利相当于连续复利,因为没有人可能在一个周期的中间观察到本金值。

每秒复利的想法乍一看可能很奇怪,但在以太坊上它的效果出奇地好。3% 的年利率实际上相当于每秒利率 0.000000093668115524%,或 0.000000000936681155 每秒利率,以 18 位小数表示。这里我们假设 1 年有 31556952 秒。

使用上述函数计算 1 年(31556952 个周期)的复利,该比率给出 2.99999999895% 的年利率,因此精确度接近 10 位有效数字。对于大多数应用来说已经足够了。使用 128.128 位定点数而不是 64.64 位甚至浮点数可以实现更高的精度。

在我们的实验中,复合周期性每秒利息 1 年消耗了大约 90K gas。对于大多数应用程序来说,这可能是负担得起的,但通常是相当高的。在我们的下一篇文章中,我们将介绍提供大致相同精度的更便宜的方法。

结论

由于缺乏本机分数支持,复杂的分数计算,例如复合定期利率所需的那些,在 Solidity 中可能具有挑战性。

然而,复利仍然可以通过平方算法和模拟定点数使用求幂来有效计算。

建议的方法足够强大,可以在 1 年(甚至更长)的时间跨度上复合每秒的利率。然而,这种方法非常耗气。

在下一篇文章中,我们将介绍更好的方法

Solidity 中的数学(第 4 部分:复利)相关推荐

  1. linux shell数学计算器,技术|使用 GNU bc 在 Linux Shell 中进行数学运算

    在 shell 中使用 bc 更好地做算数,它是一种用于高级计算的数学语言. 大多数 POSIX 系统带有 GNU bc,这是一种任意精度的数字处理语言.它的语法类似于 C,但是它也支持交互式执行语句 ...

  2. linux中bc用法英文,使用GNU bc在Linux Shell中进行数学运算

    在 shell 中使用 bc 更好地做算数,它是一种用于高级计算的数学语言. 大多数 POSIX 系统带有 GNU bc,这是一种任意精度的数字处理语言.它的语法类似于 C,但是它也支持交互式执行语句 ...

  3. 12种主要的Dropout方法:用于DNNs,CNNs,RNNs中的数学和可视化解释

    深入了解DNNs,CNNs以及RNNs中的Dropout来进行正则化,蒙特卡洛不确定性和模型压缩的方法. 动机 在深度机器学习中训练一个模型的主要挑战之一是协同适应.这意味着神经元是相互依赖的.他们对 ...

  4. 使用编译器——Solidity中文文档(8)

    写在前面:HiBlock区块链社区成立了翻译小组,翻译区块链相关的技术文档及资料,本文为Solidity文档翻译的第八部分<使用编译器>,特发布出来邀请solidity爱好者.开发者做公开 ...

  5. 机器学习中的数学 人工智能深度学习技术丛书

    作者:孙博 著 出版社:中国水利水电出版社 品牌:智博尚书 出版时间:2019-11-01 机器学习中的数学 人工智能深度学习技术丛书 ISBN:9787517077190

  6. 机器学习中的数学:一份新鲜出炉的热门草稿

    来源:机器之心 本文约1500字,建议阅读5分钟. 本文为你分享近日<Mathematics for Machine Learning>的全部草稿已放出,我们整理了这本书的简要概述. 近日 ...

  7. 12种Dropout方法:应用于DNNs,CNNs,RNNs中的数学和可视化解释

    2020-06-30 13:41:41 作者:Axel Thevenot 编译:ronghuaiyang 导读 深入了解DNNs,CNNs以及RNNs中的Dropout来进行正则化,蒙特卡洛不确定性和 ...

  8. 已知bug列表——Solidity中文文档(12)

    写在前面:HiBlock区块链社区成立了翻译小组,翻译区块链相关的技术文档及资料,本文为Solidity文档翻译的第十二部分<已知bug列表>,特发布出来邀请solidity爱好者.开发者 ...

  9. 浙江大学-计算机中的数学(诙谐幽默的短视频)

    视频优酷网址:http://list.youku.com/albumlist/show?id=19465801&ascending=1&page=1 1.计算机中的数学[01]_< ...

最新文章

  1. linux 9.0挂载驱动
  2. python语言入门u-[学习总结] python语言学习总结 (一)
  3. 深度学习时代的目标检测算法综述
  4. python管理系统web版_Python学生管理系统(web网页版)-Go语言中文社区
  5. angular cli_使用Angular CLI连接到服务器的最佳方法
  6. linux操作系统的训练目的,《西南交大-Linux操作系统应用训练》.doc
  7. 【转】Prewitt 算子
  8. 源码分析ElasticJob任务错过机制(misfire)与幂等性
  9. cse7761电能计量芯片驱动程序
  10. Mac使用系列之软件安装权限
  11. 水水水水水水水水水水水水水水水水水水水
  12. 大数据学习总结(2021版)---Mysql基础
  13. Rabbitmq消息中心_消息中心总体方案
  14. 淘宝。京东 模拟登陆
  15. codeforces 1177B
  16. DORIS单节点部署
  17. 新手上路之自增(例如i++)
  18. /',‘\\’与‘\’的区别
  19. 最大回撤率MaxDawndown算法(Python3)
  20. PHP Laravel报错No application encryption key has been specified

热门文章

  1. 智能cv5200模块方案,无线远距离WiFi传输,无线通信技术应用
  2. 巨头挤压生存空间,应该怎么办?
  3. 如何运用Common Neighbor方法进行链路预测
  4. 【HTML5+CSS3】盒子的大小
  5. 网站虚拟空间购买指南
  6. 使用C语言网络编程API
  7. 百度站长平台使用教程:robots检测
  8. FreeeBSD磁盘分区格式
  9. 风信子网络工作室介绍
  10. 2018-09-10 关于UPDATE 将库里面所有数据修改之后的处理方法