Solidity 中的数学(第 3 部分:百分比和比例)

news2024/11/15 8:29:03

   本文是关于在 Solidity 中进行数学运算的系列文章中的第三篇 。         这次的主题是:百分比和比例

 

介绍 

     金融数学从百分比开始。yx百分比是多少?y占x的多少百分比?我们都知道答案:y的x百分比是x × y ÷100,yy ×100÷ x的 x 百分比。这是学校数学。

上面的公式是求解比例的特例。通常,比例是以下形式的等式:a ÷ b = c ÷ d,求解比例就是找到已知其他三个值中的一个值。例如,d可以从abc中找到,如下所示:d = b × c ÷ a

在主流编程语言中简单明了,在 Solidity 中,这种简单的计算具有惊人的挑战性,正如我们在上一篇文章中所展示的那样。这有两个原因:i) Solidity 不支持分数;ii) Solidity 中的数字类型可能会溢出。

在 Javascript 中,可以像这样简单地计算x × y ÷ zx*y/z:在 solidity 中这样的表达式不会通过安全审计,因为对于足够大的xy乘法可能会溢出,从而计算结果可能不正确。使用 SafeMath 帮助不大,因为即使最终计算结果适合 256 位,它也可能导致交易失败。在上一篇文章中,我们将这种情况称为“幻象溢出”。在乘法之前进行除法,例如x/z*yory/z*x可以解决幻象溢出问题,但可能会导致精度下降。

在本文中,我们发现 Solidity 中有哪些更好的方法来处理百分比和比例

走向全比例

本文的目标是在 Solidity 中实现以下功能:

function mulDiv (uint x, uint y, uint z)
public pure returns (uint)

计算x × y ÷ z ,将结果向下舍入,并在z为零或结果不适合的情况下抛出uint。让我们从以下简单的解决方案开始:

function mulDiv (uint x, uint y, uint z)
public pure returns (uint)
{
  return x * y / z;
}

这个解决方案基本上满足了大部分需求:它似乎计算x × y ÷ z ,将结果向下舍入,并在z为零的情况下抛出。但是,有一个问题:它实际计算的是x × mod 2²⁵⁶ ÷ z。这就是乘法溢出在 Solidity 中的工作原理。当乘法结果不适合 256 位时,只返回结果的最低 256 位。对于较小的xy值,当x × y <2²⁵⁶ 时,没有区别,但对于较大的xy这会产生不正确的结果。所以第一个问题是:

我们如何防止溢出?

剧透:我们不应该。

在 Solidity 中防止乘法溢出的常用方法是使用mulSafeMath 库中的函数:

function mulDiv (uint x, uint y, uint z)
public pure returns (uint)
{
  return mul (x, y) / z;
}

这段代码保证了正确的结果,所以现在所有的要求似乎都满足了,对吧?没那么快。

要求是在结果不适合的情况下恢复uint,这个实现似乎可以满足它。但是,即使最终结果适合,当x × y不适合时,此实现也会恢复。uint我们称这种情况为“幻象溢出”。在上一篇文章中,我们展示了如何以精度为代价解决幻象溢出问题,但是该解决方案在这里不起作用,因为我们需要精确的结果。

由于只是恢复幻象溢出不是一种选择,那么

我们如何避免幻影溢出并保持精度?

剧透:简单的数学技巧。

让我们进行以下替换:x = a × z + by = c × z + d,其中abcd是整数,并且 0≤ b < z和 0≤ d < z。然后:

x × y ÷ z =
a × z + b )×( c × z + d )÷ z =
a ×c×  +( a × d + b × c )× z + b × d )÷ z =
a × c × z + a × d + b × c + b × d ÷ z

abcd可以通过将xy分别除以z计算为商和提醒。

因此,该函数可以这样重写:

function mulDiv (uint x, uint y, uint z)
public pure returns (uint)
{
  uint a = x / z; uint b = x % z; // x = a * z + b
  uint c = y / z; uint d = y % z; // y = c * z + d
  return a * b * z + a * d + b * c + b * d / z;
}

这里我们为了可读性使用plain +and *operators,而真正的代码应该使用SafeMath函数来防止真正的,即非幻象的溢出。

在此实现中,幻像溢出仍然是可能的,但仅限于最后一项:b * d / z但是,当z ≤2¹²⁸时,此代码保证可以正常工作,因为bd都小于z,因此b × d保证适合 256 位。因此,此实现可用于已知z不超过 2¹²⁸ 的情况。一个常见的例子是 18 位小数的定点乘法:x × y ÷10¹⁸。但,

我们如何才能完全避免幻影溢出?

剧透:使用更宽的数字。

幻象溢出问题的根源在于中间乘法结果不适合 256 位。所以,让我们使用更宽的类型。Solidity 本身不支持大于 256 位的数字类型,因此我们必须模拟它们。我们基本上需要两个操作:uint × uint → wide and wide ÷ uint → uint

由于两个 256 位无符号整数的乘积不得超过 512 位,因此较宽的类型必须至少为 512 位宽。我们可以通过一对两个 256 位无符号整数分别持有整个 512 位数字的低位和高位 256 位部分来模拟 Solidity 中的 512 位无符号整数。

因此,代码可能如下所示:

function mulDiv (uint x, uint y, uint z)
public pure returns (uint)
{
  (uint l, uint h) = fullMul (x, y);
  return fullDiv (l, h, z);
}

这里fullMul函数将两个 256 位无符号整数相乘,并将结果作为 512 位无符号整数分成两个 256 位部分返回。函数fullDiv除以 512 位无符号整数,作为两个 256 位部分传递,但 256 位无符号整数并返回结果作为 256 位无符号整数。

让我们以学校数学的方式实现这两个功能:

function fullMul (uint x, uint y)
public pure returns (uint l, uint h)
{
  uint xl = uint128 (x); uint xh = x >> 128;
  uint yl = uint128 (y); uint yh = y >> 128;
  uint xlyl = xl * yl; uint xlyh = xl * yh;
  uint xhyl = xh * yl; uint xhyh = xh * yh;

  uint ll = uint128 (xlyl);
  uint lh = (xlyl >> 128) + uint128 (xlyh) + uint128 (xhyl);
  uint hl = uint128 (xhyh) + (xlyh >> 128) + (xhyl >> 128);
  uint hh = (xhyh >> 128);
  l = ll + (lh << 128);
  h = (lh >> 128) + hl + (hh << 128);
}

function fullDiv (uint l, uint h, uint z)
public pure returns (uint r) {
  require (h < z);
  uint zShift = mostSignificantBit (z);
  uint shiftedZ = z;
  if (zShift <= 127) zShift = 0;
  else
  {
    zShift -= 127;
    shiftedZ = (shiftedZ - 1 >> zShift) + 1;
  }
  while (h > 0)
  {
    uint lShift = mostSignificantBit (h) + 1;
    uint hShift = 256 - lShift;
    uint e = ((h << hShift) + (l >> lShift)) / shiftedZ;
    if (lShift > zShift) e <<= (lShift - zShift);
    else e >>= (zShift - lShift);
    r += e;
    (uint tl, uint th) = fullMul (e, z);
    h -= th;
    if (tl > l) h -= 1;
    l -= tl;
  }
  r += l / z;
}

mostSignificantBit是一个函数,它返回参数最高有效位的从零开始的索引。该功能可以通过以下方式实现:

function mostSignificantBit (uint x) public pure returns (uint r) {
  require (x > 0);
  if (x >= 2**128) { x >>= 128; r += 128; }
  if (x >= 2**64) { x >>= 64; r += 64; }
  if (x >= 2**32) { x >>= 32; r += 32; }
  if (x >= 2**16) { x >>= 16; r += 16; }
  if (x >= 2**8) { x >>= 8; r += 8; }
  if (x >= 2**4) { x >>= 4; r += 4; }
  if (x >= 2**2) { x >>= 2; r += 2; }
  if (x >= 2**1) { x >>= 1; r += 1; }
}

上面的代码相当复杂,可能应该解释一下,但我们现在将跳过解释并专注于不同的问题。这段代码的问题是每次函数调用消耗大约 2.5K gas mulDiv,这是相当多的。所以,

我们能做得更便宜吗?

剧透:数学魔法!

下面的代码基于令人兴奋的数学发现

雷姆科布洛门

. 如果您喜欢这段代码,请点赞他的“mathemagic”文章。

首先,我们重写fullMul函数:

function fullMul (uint x, uint y)
public pure returns (uint l, uint h)
{
  uint mm = mulmod (x, y, uint (-1));
  l = x * y;
  h = mm - l;
  if (mm < l) h -= 1;
}

fullMul这样每次调用可以节省大约 250 gas 。

然后我们重写mulDiv函数:

function mulDiv (uint x, uint y, uint z)
public pure returns (uint) {
  (uint l, uint h) = fullMul (x, y);
  require (h < z);
  uint mm = mulmod (x, y, z);
  if (mm > l) h -= 1;
  l -= mm;
  uint pow2 = z & -z;
  z /= pow2;
  l /= pow2;
  l += h * ((-pow2) / pow2 + 1);
  uint r = 1;
  r *= 2 - z * r;
  r *= 2 - z * r;
  r *= 2 - z * r;
  r *= 2 - z * r;
  r *= 2 - z * r;
  r *= 2 - z * r;
  r *= 2 - z * r;
  r *= 2 - z * r;
  return l * r;
}

此实现每次调用仅消耗约 550 gas,mulDiv并且可以进一步优化。比学校数学方法好 5 倍。非常好!但是一个人真的必须获得数学博士学位才​​能写出这样的代码,而且并不是每个问题都有这样神奇的数学解决方案。如果我们可以,事情会简单得多

在 Solidity 中使用浮点数

正如我们在本文开头所说的那样,在 JavaScript 中,只需简单地编写a * b / c,语言会处理其余部分。如果我们可以在 Solidity 中做同样的事情呢?

其实我们可以。虽然核心语言不支持浮点,但有些库支持。例如,对于ABDKMathQuad库,可以这样写:

function mulDiv (uint x, uint y, uint z)
public pure returns (uint) {
  return
    ABDKMathQuad.toUInt (
      ABDKMathQuad.div (
        ABDKMathQuad.mul (
          ABDKMathQuad.fromUInt (x),
          ABDKMathQuad.fromUInt (y)
        ),
        ABDKMathQuad.fromUInt (z)
      )
    );
}

不像 JavaScript 那样优雅,不像数学魔术解决方案那样便宜(甚至比学校数学方法更广泛),但简单明了而且非常精确,因为这里使用的四精度浮点数大约有 33 位有效小数。

此实现的一半以上的气体消耗用于将uint值转换为浮点数并返回,比例计算本身仅消耗约 1.4K 气体。因此,在所有智能合约中使用浮点数可能比混合使用整数和浮点数要便宜得多。

结论

由于溢出和缺乏分数支持,百分比和比例在 Solidity 中可能具有挑战性。然而,各种数学技巧可以正确有效地解决比例问题。

库支持的浮点数可能会以 gas 和精度为代价让生活变得更好。

在我们的下一篇文章中,我们将更深入地探讨金融数学

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/171235.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GPIO 应用

应用层如何控制 GPIO&#xff0c; 譬如控制 GPIO 输出高电平、或输出低电平。应用层如何操控 GPIO与 LED 设备一样&#xff0c; GPIO 同样也是通过 sysfs 方式进行操控&#xff0c;进入到/sys/class/gpio 目录下。可以看到该目录下包含两个文件 export、 unexport 以及 5 个 gp…

面试 | 百度测试开发岗位面试题目回顾

一面题目 二面题目 面试经历详情 在招聘网站投递简历后&#xff0c;收到面试通知信息&#xff08;如下图&#xff0c;为保护个人隐私&#xff0c;面试岗位、地点、时间等隐去&#xff09;。虽然写的是高级测试开发工程师&#xff0c;但是面试官说他们部门的测试不一定都要写框架…

【年更分享】带你看看前端生态圈的技术趋势 state-of-css 2022 state-of-js 2022详细解读

各位前端开发者们大家好&#xff0c;我又来给大家解读最新一年的 state-of-css & state-of-js 技术调查了&#xff01; 往年的 state-of-css 和 state-of-js 的解读&#xff1a; state-of-js 2020 详细解读 state-of-js 2021 详细解读 state-of-css 2021 详细解读 一、写…

php 断点调试 PHPStorm Xdebug helper

安装与使用php的xdebug扩展 浏览器访问只包含<?php phpinfo();的php文件&#xff0c;查看php详情。页面搜索是否安装了xdebug扩展。 如未安装&#xff0c;则访问&#xff1a;xdebug安装向导&#xff0c;将phpinfo()的输出页面复制到此文本框中&#xff0c;点击页面下方的“…

Ubuntu18.04安装Anaconda

Ubuntu18.04安装Anaconda 文章目录Ubuntu18.04安装Anaconda1 下载Anaconda2 安装annaconda3 创建新环境conda环境配置指令pip环境&#xff0c;阿里源切换虚拟环境pytorch 安装不成功pycharm切换python版本4 国内conda源5 卸载anacondaReference1 下载Anaconda 下载Anaconda3-2…

Zerotier免费的虚拟局域网

Zerotier介绍 Zerotier是一款用于构建异地虚拟局域网的工具。 通过网页后台创建虚拟网络并进行管理。 通过电脑上的Zerotier客户端连接各个异地电脑到虚拟局域网&#xff0c;从而实现组网。 由后台分配虚拟ip&#xff0c;并且各个异地电脑可以通过虚拟IP对同一虚拟局域网下的其…

零基础学SQL(九、分组 GROUP BY)

目录 前置建表 ​编辑 一、分组的概念 二、分组案例 三、分组的过滤HAVING子句 前置建表 CREATE TABLE student (id int NOT NULL AUTO_INCREMENT COMMENT 主键,code varchar(255) NOT NULL COMMENT 学号,name varchar(255) DEFAULT NULL COMMENT 姓名,sex enum(男,女) DEF…

HTML知识梳理

文本格式化标签 标签语义加粗 <strong></strong>或者<b></b>更推荐使用<strong>标签加粗语义更强烈倾斜 <em></em>或者<i></i>更推荐使用<em>标签倾斜语义更强烈删除线 <del></del>或者<s><…

MySQL8源代码安装(CentOS8版本)

目标 在CentOS8上面源代码编译安装MySQL8. 下载源代码 打开MySQL下载页面&#xff1a; https://www.mysql.com/downloads/ 找到MySQL社区版本页面&#xff1a; 选择下载MySQL社区版服务器进行下载&#xff1a; 最后选择&#xff0c;MySQL源代码进行下载&#xff0c;如下图…

以element ui为例分析前端各种弹窗和对话框的使用场景与区别

文章目录摘要Dialog 对话框Drawer 抽屉Notice 通知MessageBox 弹框Popconfirm 气泡确认框Message 消息提示Notification 通知Dialog 对话框与Drawer 抽屉的区别MessageBox和Dialog的区别Message消息提示与Notification通知的区别摘要 本文研究分析element ui 中的各种弹窗和对…

【机器学习 吴恩达】2022课程笔记(持续更新)

一、机器学习 1.1 机器学习定义 计算机程序从经验E中学习&#xff0c;解决某一任务T&#xff0c;进行某一性能P&#xff0c;通过P测定在T上的表现因经验E而提高 eg&#xff1a;跳棋程序 E&#xff1a; 程序自身下的上万盘棋局 T&#xff1a; 下跳棋 P&#xff1a; 与新对手下…

Python离线下载whl文件,xxx.wh1 is not a supported wheel on this platform

0、问题 今天在安装 whl 文件的时候&#xff0c;由于电脑处于没有网络的情况&#xff0c;只能在有网络的电脑上下载好 whl 文件&#xff0c;导入之后进行离线安装 但是由于版本不匹配的问题。导致报如下的错误&#xff1a; ERROR&#xff1a;xxx.wh1 is not a supported whe…

创建成功的风格指南

作者&#xff1a;Sean Watson&#xff0c;ServiceNow 创建风格指南是一项艰巨的任务。风格指南包含语法标准、语音和语调指南、要使用和避免的词、复制模式以及产品品牌的基础知识。这些文档很容易变得非常乏味以至于难以使用&#xff0c;或者非常简单以至于无法满足需求。它们…

DDOS和CC如何区分

DDOS 的全称&#xff1a;Distributed Denial of Service中文意思是分布式拒绝服务。该攻击方式利用目标系统网络服务功能缺陷或者直接消耗其系统资源。使得该目标系统无法提供正常的服务。还有&#xff0c;为什么说 DDoS 攻击很泛滥&#xff1f;因为便宜啊&#xff0c;可是效果…

buuctf-web-[RoarCTF 2019]Easy Calc1

打开环境发现一个类似计算器的东西尝试一些常规的测试参数尝试单引号发现提示&#xff0c;但是并未报错&#xff0c;非sql注入查看源代码找到一段JavaScript脚本<script>$(#calc).submit(function(){$.ajax({url:"calc.php?num"encodeURIComponent($("#c…

业务-研发一体化管理平台,存在吗?

伴随着互联网在中国进程的发展&#xff0c;线上研发效能及业务应用软件也不落后于时代进步的脚步&#xff0c;中国软件行业从未停止过持续的创新。 2022年&#xff0c;业务应用开发正在简化&#xff0c;研发效能也在提升&#xff0c;其中不得不提软件在协同促进、研发一体化管…

Java 23种设计模式的分类和使用场景

听说过GoF吧&#xff1f; GoF是设计模式的经典名著Design Patterns: Elements of Reusable Object-Oriented Software&#xff08;中译本名为《设计模式——可复用面向对象软件的基础》&#xff09;的四位作者&#xff0c;他们分为是&#xff1a;Elich Gamma、Richard Helm、R…

MyBatis Plus

概述MyBatis-Plus MyBatis-Plus简称 MP是一个 MyBatis的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开 发、提高效率而生 官方文档&#xff1a;https://baomidou.com/ Hello MP 添加依赖 <!-- 如果没有整合SpringBoot&#xff0c;则需要…

python-MySQL数据库基础(二)操作数据库、数据表进行增删改查、分组、排序、连接

操作数据库(DDL) Cmder是一个软件包&#xff0c;它被创建出来用于Windows上替代模拟器的&#xff0c;cmder官网&#xff1a;https://cmder.net/&#xff0c;安装完成后把安装目录下的bin文件夹添加到环境变量里才能正常使用&#xff08;跟安装python类似&#xff09;。 连接数…

85.机器翻译与数据集

语言模型是自然语言处理的关键&#xff0c; 而机器翻译是语言模型最成功的基准测试。 因为机器翻译正是将输入序列转换成输出序列的 序列转换模型&#xff08;sequence transduction&#xff09;的核心问题。 序列转换模型在各类现代人工智能应用中发挥着至关重要的作用&#x…