Solidity 中的数学(第 4 部分:复利)

这篇具有很好参考价值的文章主要介绍了Solidity 中的数学(第 4 部分:复利)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

Solidity 中的数学(第 4 部分:复利)

 文章来源地址https://www.toymoban.com/news/detail-413062.html

介绍

在我们之前的文章中,我们讨论了百分比以及它们是如何在 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 部分:复利)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【区块链技术开发】 关于Windows10平台Solidity语言开发环境配置

    在 Windows 上配置 Solidity 语言开发环境需要进行以下步骤:

    2023年04月20日
    浏览(61)
  • 人生发展,遵循复利原则

    目录 人生发展,遵循复利原则 聚焦 脚踏实地,长期主义 以K哥例真正意义上的财富积累,是从35岁开始,从35岁到39岁这4年的收入,超过了35岁以前总和;40岁到43岁这3年的收入,又超过了40岁以前收入的总和。 人和人之间为什么会慢慢拉开差距?是家庭背景、努力程度,还是

    2024年02月02日
    浏览(34)
  • 数学建模部分算法

    Q1:模拟退火是什么算法? 模拟退火是模拟物理上退火方法,通过N次迭代(退火),逼近函数的上的一个 最值 (最大或者最小值)。 比如逼近这个函数的最大值C点: Q2:模拟退火为什么可行? 讨论这个问题需要理解一下物理原型是怎么样的,也就是原来是怎么“退火”的:

    2024年02月08日
    浏览(31)
  • 离散数学——图论部分

    目录 概述考点: 邻接矩阵,矩阵的计算及含义,完全图,补图,平面图的相关概念,欧拉图,最小生成树,最优二叉树 一.图 ​编辑   二.路和回路 2.1 2.2连通与可达 1.可达 2.连通 三.图的矩阵表示 3.1邻接矩阵 3.2可达性矩阵 3.3无向图的完全关联矩阵 3.4有向图的完全关联矩阵

    2024年02月04日
    浏览(37)
  • 彻底理解solidity中的事件

    在我之前的几篇关于智能合约的文章中,都有提到事件的用法,比如: 这里定义了两个事件,分别表示最高竞价更新了和拍卖结束了。 然后在需要的位置,调用事件,比如: 我们可以通过emit调用事件方法,然后这个事件就作为日志记录到了以太坊区块链中。日志是以太坊区

    2024年02月02日
    浏览(38)
  • 快慢指针该如何操作?本文带你认识快慢指针常见的三种用法及在链表中的实战

    很多同学都听过 快慢指针 这个名词,认为它不就是定义两个引用(指针)一前一后吗?是的,它的奥秘很深,它的作用究竟有哪些?究竟可以用来做哪些题目?下面我将一一带你了解和应用 下面的本节的大概内容,有疑惑的点,欢迎小伙伴们留言 目录 1.简述快慢指针 2.快慢

    2024年02月04日
    浏览(34)
  • Unity --- 三维数学 --- Vector类 --- 向量部分

       1.注意每一个数字都表示一段有向位移 --- 有方向的距离 1.从尾到头那一段称为向量的模长 --- magnitude (direction对应的是向量的方向) 2.一个向量有大小 -- 模长(magnitude) , 有方向(direction) 1.向量的模长等于各分量的平方和的平方根 2.由于在计算机中计算平方和要比计算平方

    2024年02月12日
    浏览(33)
  • 【AI】数学基础——高数(函数&微分部分)

    参考:https://www.bilibili.com/video/BV1mM411r7ko?p=1vd_source=260d5bbbf395fd4a9b3e978c7abde437 唐宇迪:机器学习数学基础 高数(积分部分) 表示量与量之间的关系: A = π r 2 A=pi r^2 A = π r 2 一组输入输出关系:一组输入唯一对应一组输出 y = f ( x ) { x : 自变量 y 0 = y ∣ x = x 0 = f ( x 0 ) y=f(x)le

    2024年02月11日
    浏览(58)
  • Solidity中的calldata,storage,memory

    目录 calldata memory storage 三者之间的转换 storage作为参数,赋值到memory (1) (2) (3) storage作为参数,赋值给storage memory作为参数,赋值给memory memory作为参数,赋值给storage 官方文档对calldata的描述: Calldata is a non-modifiable, non-persistent area where function arguments are stored, and beha

    2024年01月23日
    浏览(37)
  • solidity合约中的interface怎么使用

    # Interface ## 什么是 interface ? Interfaces 和抽象合约比较类似,但是他们不能实现任何功能。通过定义好的 interface 我们可以在不清楚目标合约具体实现方式的情况下,调用目标的合约 ## 如何定义 interface ? ```solidity interface Country {     // 定义接口中的方法和返回值 } ``` ## interface 中不

    2024年02月03日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包