成人挑战

关于Habré的文章已经有好几篇了( 二,半篇 ),专门介绍了新的Posit浮点格式,其作者在各个方面都提出了优于标准IEEE 754 float的文章。 这种新格式还发现批评家( )声称Posit的缺点大于优点。 但是,如果我们真的有一种新的革命形式,而批评仅仅是由那些批评者的嫉妒和无能引起的呢? 好吧,找出答案的最佳方法就是自我评估。

引言


新格式的优点通过简单的示例进行了演示,这些示例具有相近顺序的数字加/乘运算,从而使精度提高了一位或两位数。 但是,在实际计算中,单次操作的正负1位误差并不重要,因为无论如何我们都以有限的精度进行计算。 操作序列导致错误累积很重要,当一段时间后低位数字的错误开始导致旧位数字的错误。 这就是我们将尝试体验的。

准备工作


为了进行测试,我从此处开始了Posit实现并带有可悲的标题。 要在Visual Studio中进行编译,我们必须在util.h文件中添加#define CLZ(n)__lzcnt(n)行,并将posit.cpp文件中的0.f / 0.f替换为std :: numeric_limits <float> :: quiet_NaN ()。 顺便说一下,在这个库中也没有找到基本数学函数的实现(当然,除了根)-这是怀疑某些东西不对的另一个原因。

测试1.复数的乘法


尽可能使用使用复杂算术计算的傅立叶变换。 首先,我想在傅立叶变换上测试Posit。 但是由于其准确性完全取决于实现,因此要进行正确的测试,您必须考虑所有基本算法,这有些耗时; 因此,您可以从一个简单的操作开始-复数的乘法。

如果我们取某个向量并将其旋转360度1°,那么最后我们应该获得相同的原始向量。 实际上,由于误差的累积,结果将略有不同-匝数越大,误差越大。 因此,使用此简单代码

complex<T> rot(cos(a), sin(a)); complex<T> vec(length, 0); for (long i = 0; i < count; i++) { vec *= rot; } cout << "error: " << stdev(vec.real() - length, vec.imag()) << endl; 

我们旋转带有不同类型数据的矢量,并将误差视为所得矢量与原始矢量的均方差(也可以解释为差矢量的长度)。

首先,将单位向量作为对Posit的最支持:
迭代4100100010,000100,000
双倍00000
飘浮00.000000360.000010380.00018580.0001961
假定00.000000730.000005340,00004110,0004468

这里还没有明显的领导者-优势是一个或另一个的两倍。 将旋转向量的长度增加到1000:
迭代4100100010,000100,000
双倍00000
飘浮00,000280.01030.180.19
假定00.002130.01880.162.45

误差值几乎相等。 继续-1,000,000:
迭代4100100010,000100,000
双倍000.000000020.000000420.0000036
飘浮00.3312.0185.8198.1
假定08.1271.0769.210706.8

在这里,Posit已经自信地落在后面,并且双重错误开始蔓延到浮动中。 让我们花更长的时间-10 10来充分理解浮点格式的优点:
迭代4100100010,000100,000
双倍0.000002450,000015360,00020410,00409510,03621497
飘浮0.000002456003.888111.81 836 254.01965083.0
假定9216.01287208.714443543,7202630144.41784050328.2

在开始时最有趣的事情是,在4次迭代中-当float给出与double相当的错误时,Posit已经具有完全错误的结果。

测试2.有理多项式的计算


由于原始库中没有数学函数,因此我们将尝试自行完成某些操作。 通过泰勒级数展开,许多函数的近似性很差,并且它们通过有理多项式的近似计算更方便。 可以通过多种方法来获得这种近似值,包括纯粹地通过Padé近似值进行分析。 此外,我们将使用足够大的系数进行测试,以使它们在计算之前也经过舍入。

使用Wolfram Mathematica和PadeApproximant命令[Sin [x],{x,0,{11,11}}]
我们得到了近似正弦的有理多项式,它在大约-2到2的范围内提供了双精度:

\ frac {-\ frac {481959816488503 x ^ {11}} {363275871831577908403200} + \ frac {23704595077729 x ^ 9} {42339845201815607040}-\ frac {2933434889971 x ^ 7} {33603051747472704704 ^ 33603051747472704} } {617703157122660}-\ frac {109061004303 x ^ 3} {722459832892} + x} {\ frac {37291724011 x ^ {10}} {11008359752472057830400} + \ frac {3924840709 x ^ 8} {2016183104848362240} x ^ 6} {168015258737363520} + \ frac {1679739379 x ^ 4} {13726736824948} + \ frac {34046903537 x ^ 2} {2167379498676} +1}


为了节省计算量, 霍纳法通常直接用于计算。 在我们的案例中(使用HornerForm)它将看起来像

 template< typename T > T padesin(T x) { T xx = x*x; return (x*(T(363275871831577908403200.) + xx*(-T(54839355237791393068800.) + xx*(T(2120649063015013090560.) + xx*(-T(31712777908498486800.) + xx*(T(203385425766914820.) - T(481959816488503.) * xx)))))) / (T(363275871831577908403200.) + xx*(T(5706623400804924998400.) + xx*(T(44454031219351353600.) + xx* (T(219578286347980560.) + xx*(T(707177798947620.) + T(1230626892363.) * xx))))); } 


让我们看看:
x = 0.5x = 1x = 2
罪(x)0.4794255386042030.84147098480789650,9092974268256817
双倍0.4794255386042030.84147098480789650,909297426825681 6
飘浮0.4794255 4950714110.84147095680 236820,9092974 066734314
假定0.47 889610379934310.84 244372695684430.9 110429435968399

x = 3x = 4x = 5
罪(x)0.1411200080598672-0.7568024953079282-0.9589242746631385
双倍0.14112000805 85958-0.75680249 60833886-0.958924 3758030122
飘浮0.1411200 165748596-0.7568024 396896362-0.9589243 531227112
假定0.14 44759201258421-0.7 614213190972805-0.9 691629931330681

如您所见,这里的Posit情况看起来很糟糕-几乎不拨打两个有效数字。

结论


不幸的是,奇迹没有发生,革命被取消了。 Posit在单个计算中展示的优势仅是一个窍门,其价格是“大量”实际计算中准确性的灾难性下降。 使用Posit代替IEEE 754浮点数或定点数唯一有意义的原因是宗教性的。 使用魔术格式,其创建者的神圣信仰确保了准确性,可以为您的程序带来许多奇迹!

PS 源代码,用于验证和批评

PPS 继续

Source: https://habr.com/ru/post/zh-CN467735/


All Articles