基本讲解金融数学始自百分比。y的x百分比是多少?x的多少百分比是y?我们都告诉答案:y的x百分比是x×y÷100,y是y×100÷x百分比。这是学校习的数学。上面的公式是解法比例的类似情况。
一般来说比例是以下形式的方程:a÷b = c÷d,拒绝解法比例,就是要寻找一个告诉其他三个值的值。例如可以从a,b和c中寻找d,如下右图:d = b×c÷a。
正如在上一篇文章中所展出的那样,在主流编程语言中简单明了,在Solidity中,如此非常简单的计算出来令人吃惊地具备挑战性。这有两个原因:i)实体不反对分数;ii)Solidity中的数字类型可能会阻塞。
在Javascript中,可以这样非常简单地计算出来x×y÷z:x * y / z。可信地,这样的表达式会通过安全性审查,因为对于充足大的x和y乘法可能会阻塞,因此计算结果有可能不准确。
用于SafeMath并没多大协助,因为即使最后计算结果合适256位,它也不会使事务告终。在上一篇文章中,网卓新闻网,我们称之为这种情况为“幻像阻塞”。在像x / z * y或y / 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×y mod2²⁵⁶÷z。这就是Solidity中乘法阻塞的工作方式。
当乘法结果不合适256位时,仅有回到结果的低于256位。对于x和y较小的值,当x×y 2²⁵⁶时,没差异,但是对于较小的x和y,这将产生错误的结果。
所以第一个问题是:我们如何避免阻塞?避免Solidity中乘法阻塞的常用方法是用于SafeMath库中的mul函数:function mulDiv (uint x, uint y, uint z)public pure returns (uint){ return mul (x, y) / z;}该代码确保了准确的结果,所以现在所有的拒绝或许都符合了,对吧?没有那么慢。拒绝是在结果不合适uint的情况下还原成,并且此构建或许可以满足要求。但是即使最后结果适合,当x×y不合适uint时,此构建也不会还原成。
我们称之为这种情况为“幻像阻塞”。在上一篇文章中,我们展出了如何以精确度为代价解决问题幻像阻塞,但是该解决方案在这里不起作用,因为我们必须准确的准确结果。由于无法还原成幻像阻塞,因此我们如何防止幻像阻塞维持精度?让我们展开以下更换:x = a×z + b和y = c×z + d,其中a,b,c和d是整数,0≤bz和0≤dz。
然后:x×y÷z=(a×z+b)×(c×z+d)÷z=(a×b×z²+(a×d+b×c)×z+b×d)÷z=a×b×z+a×d+b×c+b×d÷z通过分别将x和y除以z,可以将值a,b,c和d计算出来为商和余数。因此,该函数可以这样改写: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;}在这里,我们用于普通的+和*运算符以提升可读性,而现实代码不应用于SafeMath函数来避免现实(即非幻像)阻塞。在此构建中,幻像阻塞依然是有可能的,但仅在最后一项中:b*d/z。但是由于b和d都大于z,因此确保了该代码在z≤212时能长时间工作,因此可以确保b×d合适256位。
因此可以在未知z不多达2^128的情况下用于此构建。一个少见的例子是18位小数的定点乘法:x×y÷10¹⁸。
我们如何才能完全避免幻影阻塞?幻像阻塞问题的根源在于中间乘法结果不合适256位。因此,让我们用于更加普遍的类型。Solidity本身不反对小于256位的数字类型,因此我们将被迫仿真它们。
我们基本上必须两个操作者:uint×uint→wide和wide÷uint→uint。由于两个256位无符号整数的乘积无法多达512位,因此较宽的类型必需最少为512位宽。我们可以通过两个256位无符号整数对分别仿真512位无符号整数,这两个整数分别持有人整个512位数字的较低256位和低256位。因此,代码有可能如下右图: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位无符号整数DFT,以两个256位无符号整数形式传送,但以256位无符号整数形式传送,并以256位无符号整数形式回到结果。让我们以学校数学的方式构建这两个函数:function fullMul (uint x, uint y)public pure returns (uint l, uint h){ uint xl = uint128 (x); uint xh = x128; uint yl = uint128 (y); uint yh = y128; uint xlyl = xl * yl; uint xlyh = xl * yh; uint xhyl = xh * yl; uint xhyh = xh * yh; uint ll = uint128 (xlyl); uint lh = (xlyl128) + uint128 (xlyh) + uint128 (xhyl); uint hl = uint128 (xhyh) + (xlyh128) + (xhyl128); uint hh = (xhyh128); l = ll + (lh128); h = (lh128) + hl + (hh128);}和function fullDiv (uint l, uint h, uint z)public pure returns (uint r) { require (hz); uint zShift = mostSignificantBit (z); uint shiftedZ = z; if (zShift = 127) zShift = 0; else {zShift -= 127;shiftedZ = (shiftedZ - 1zShift) + 1; } while (h0) {uint lShift = mostSignificantBit (h) + 1;uint hShift = 256 - lShift;uint e = ((hhShift) + (llShift)) / shiftedZ;if (lShiftzShift) e = (lShift - zShift);else e = (zShift - lShift);r += e;(uint tl, uint th) = fullMul (e, z);h -= th;if (tll) h -= 1;l -= tl; } r += l / z;}这里的mostSignificantBit是一个函数,它回到参数最低有效地位的从零开始的索引。此功能可以通过以下方式构建:function mostSignificantBit (uint x) public pure returns (uint r) { require (x0); 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; }}上面的代码非常简单,有可能必须说明,但我们现在将跳过说明,而将重点放到有所不同的问题上。这段代码的问题是,每次调用mulDiv函数约要消耗2.5K gas,这是相当多的。
所以,我们可以低廉吗?以下代码基于Remco Bloemen叙述的令人兴奋的数学找到。首先,我们改写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 (mml) h -= 1;}每一次fullMul调用可以节省约250gas。然后我们改写mulDiv函数:function mulDiv (uint x, uint y, uint z)public pure returns (uint) { (uint l, uint h) = fullMul (x, y); require (hz); uint mm = mulmod (x, y, z); if (mml) 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;}这个构建每次mulDiv调用只消耗约550 gas,并且可以更进一步优化。
比学校数学方法好5倍。很好!但是,实质上必需取得数学博士学位才能撰写这样的代码,并且并非每个问题都具备这样的数学解决方案。如果可以的话,事情不会非常简单得多。
在Solidity中用于浮点数正如我们在本文结尾所说的,在JavaScript中,只需撰写一个*b/c,其余部分由语言来处置。如果我们能在Solidity上做到某种程度的事呢?实质上我们可以。虽然核心语言不反对浮点,但有些库反对。
例如用于ABDKMathQuad库,可以撰写:function mulDiv (uint x, uint y, uint z)public pure returns (uint) { returnABDKMathQuad.toUInt ( ABDKMathQuad.div (ABDKMathQuad.mul ( ABDKMathQuad.fromUInt (x), ABDKMathQuad.fromUInt (y)),ABDKMathQuad.fromUInt (z) ));}不像JavaScript那样高雅,不像mathemagic解决方案那样低廉(甚至比学校的数学方法极具扩展性),但是非常简单和准确,因为这里用于的四倍精度浮点数有约33个有效地小数。此构建方式消耗的气体有一半以上用作将uint值切换为浮点数和浮点数,比例计算出来本身仅有消耗大约1.4K gas。因此在所有智能合约中用于浮点数有可能比混合整数和浮点数低廉得多。结论由于阻塞和缺乏分数反对,因此百分比和比例在Solidity中有可能具备挑战性。
但是,各种数学技巧都可以准确有效地解决问题比例问题。
本文来源:欧宝体育-www.milkang.com