exp32f

opencv的exp函数和cmath的exp函数在精度上存在一定差异,通过查找源码,发现了这么一段实现。代码如下:

点击查看代码
#define EXPTAB_SCALE 6
#define EXPTAB_MASK ((1 << EXPTAB_SCALE) - 1) #define EXPPOLY_32F_A0 .9670371139572337719125840413672004409288e-2 static const double expTab[EXPTAB_MASK + 1] = {
1.0 * EXPPOLY_32F_A0,
1.0108892860517004600204097905619 * EXPPOLY_32F_A0,
1.0218971486541166782344801347833 * EXPPOLY_32F_A0,
1.0330248790212284225001082839705 * EXPPOLY_32F_A0,
1.0442737824274138403219664787399 * EXPPOLY_32F_A0,
1.0556451783605571588083413251529 * EXPPOLY_32F_A0,
1.0671404006768236181695211209928 * EXPPOLY_32F_A0,
1.0787607977571197937406800374385 * EXPPOLY_32F_A0,
1.0905077326652576592070106557607 * EXPPOLY_32F_A0,
1.1023825833078409435564142094256 * EXPPOLY_32F_A0,
1.1143867425958925363088129569196 * EXPPOLY_32F_A0,
1.126521618608241899794798643787 * EXPPOLY_32F_A0,
1.1387886347566916537038302838415 * EXPPOLY_32F_A0,
1.151189229952982705817759635202 * EXPPOLY_32F_A0,
1.1637248587775775138135735990922 * EXPPOLY_32F_A0,
1.1763969916502812762846457284838 * EXPPOLY_32F_A0,
1.1892071150027210667174999705605 * EXPPOLY_32F_A0,
1.2021567314527031420963969574978 * EXPPOLY_32F_A0,
1.2152473599804688781165202513388 * EXPPOLY_32F_A0,
1.2284805361068700056940089577928 * EXPPOLY_32F_A0,
1.2418578120734840485936774687266 * EXPPOLY_32F_A0,
1.2553807570246910895793906574423 * EXPPOLY_32F_A0,
1.2690509571917332225544190810323 * EXPPOLY_32F_A0,
1.2828700160787782807266697810215 * EXPPOLY_32F_A0,
1.2968395546510096659337541177925 * EXPPOLY_32F_A0,
1.3109612115247643419229917863308 * EXPPOLY_32F_A0,
1.3252366431597412946295370954987 * EXPPOLY_32F_A0,
1.3396675240533030053600306697244 * EXPPOLY_32F_A0,
1.3542555469368927282980147401407 * EXPPOLY_32F_A0,
1.3690024229745906119296011329822 * EXPPOLY_32F_A0,
1.3839098819638319548726595272652 * EXPPOLY_32F_A0,
1.3989796725383111402095281367152 * EXPPOLY_32F_A0,
1.4142135623730950488016887242097 * EXPPOLY_32F_A0,
1.4296133383919700112350657782751 * EXPPOLY_32F_A0,
1.4451808069770466200370062414717 * EXPPOLY_32F_A0,
1.4609177941806469886513028903106 * EXPPOLY_32F_A0,
1.476826145939499311386907480374 * EXPPOLY_32F_A0,
1.4929077282912648492006435314867 * EXPPOLY_32F_A0,
1.5091644275934227397660195510332 * EXPPOLY_32F_A0,
1.5255981507445383068512536895169 * EXPPOLY_32F_A0,
1.5422108254079408236122918620907 * EXPPOLY_32F_A0,
1.5590044002378369670337280894749 * EXPPOLY_32F_A0,
1.5759808451078864864552701601819 * EXPPOLY_32F_A0,
1.5931421513422668979372486431191 * EXPPOLY_32F_A0,
1.6104903319492543081795206673574 * EXPPOLY_32F_A0,
1.628027421857347766848218522014 * EXPPOLY_32F_A0,
1.6457554781539648445187567247258 * EXPPOLY_32F_A0,
1.6636765803267364350463364569764 * EXPPOLY_32F_A0,
1.6817928305074290860622509524664 * EXPPOLY_32F_A0,
1.7001063537185234695013625734975 * EXPPOLY_32F_A0,
1.7186192981224779156293443764563 * EXPPOLY_32F_A0,
1.7373338352737062489942020818722 * EXPPOLY_32F_A0,
1.7562521603732994831121606193753 * EXPPOLY_32F_A0,
1.7753764925265212525505592001993 * EXPPOLY_32F_A0,
1.7947090750031071864277032421278 * EXPPOLY_32F_A0,
1.8142521755003987562498346003623 * EXPPOLY_32F_A0,
1.8340080864093424634870831895883 * EXPPOLY_32F_A0,
1.8539791250833855683924530703377 * EXPPOLY_32F_A0,
1.8741676341102999013299989499544 * EXPPOLY_32F_A0,
1.8945759815869656413402186534269 * EXPPOLY_32F_A0,
1.9152065613971472938726112702958 * EXPPOLY_32F_A0,
1.9360617934922944505980559045667 * EXPPOLY_32F_A0,
1.9571441241754002690183222516269 * EXPPOLY_32F_A0,
1.9784560263879509682582499181312 * EXPPOLY_32F_A0,
}; static const double exp_prescale = 1.4426950408889634073599246810019 * (1 << EXPTAB_SCALE);
static const double exp_postscale = 1./(1 << EXPTAB_SCALE);
static const double exp_max_val = 3000.*(1 << EXPTAB_SCALE); // log10(DBL_MAX) < 3000 const double* getExpTab64f()
{
return expTab;
} const float* getExpTab32f()
{
static float expTab_f[EXPTAB_MASK+1];
static std::atomic<bool> expTab_f_initialized(false);
if (!expTab_f_initialized.load())
{
for( int j = 0; j <= EXPTAB_MASK; j++ )
expTab_f[j] = (float)expTab[j];
expTab_f_initialized = true;
}
return expTab_f;
} void exp32f( const float *_x, float *y, int n )
{
const float* const expTab_f = getExpTab32f(); const float
A4 = (float)(1.000000000000002438532970795181890933776 / EXPPOLY_32F_A0),
A3 = (float)(.6931471805521448196800669615864773144641 / EXPPOLY_32F_A0),
A2 = (float)(.2402265109513301490103372422686535526573 / EXPPOLY_32F_A0),
A1 = (float)(.5550339366753125211915322047004666939128e-1 / EXPPOLY_32F_A0); int i = 0;
const Cv32suf* x = (const Cv32suf*)_x;
float minval = (float)(-exp_max_val/exp_prescale);
float maxval = (float)(exp_max_val/exp_prescale);
float postscale = (float)exp_postscale; for( ; i < n; i++ )
{
float x0 = x[i].f;
x0 = std::min(std::max(x0, minval), maxval);
x0 *= (float)exp_prescale;
Cv32suf buf; int xi = cv::saturate_cast<int>(x0);
x0 = (x0 - xi)*postscale; int t = (xi >> EXPTAB_SCALE) + 127;
t = !(t & ~255) ? t : t < 0 ? 0 : 255;
buf.i = t << 23; y[i] = buf.f * expTab_f[xi & EXPTAB_MASK] * ((((x0 + A1)*x0 + A2)*x0 + A3)*x0 + A4);
}
}

这是从opencv4源码中摘出的一段计算\(e^x\)的函数(32位浮点)。初看起来可能有些迷糊,但仔细思考和推导之后,稍微明白了一些。想要理解这段代码,主要有三点需要清楚:第一点,从计算\(e^x\)转换到计算\(2^x\);第二点,使用四阶泰勒展开逼近\(2^x\); 第三点,expTab_f的作用。

从\(e^x\)转到\(2^x\)。这个比较简单,对输入\(x\)预先乘以\(log_2(e)\)就可以了,exp_prescale就是起这样的作用(这里预先左移了6位,相当于乘以64,这个先不管)。转换之后对\(x\)进行了整数和小数部分的分离,整数部分为xi,小数部分为x0。对于整数部分,按照IEEE754浮点数的表示方式,可以直接移动到解码部分。

int t = (xi >> EXPTAB_SCALE) + 127;

t = !(t & ~255) ? t : t < 0 ? 0 : 255;

buf.i = t << 23;

这个时候整数部分已经计算完成(即buf.f,这里用了union的存储方式,方便机器数级别的浮点和整数的转换),剩下只要计算小数部分,即\(2^{x0}\)。

y[i] = buf.f * expTab_f[xi & EXPTAB_MASK] * ((((x0 + A1)x0 + A2)x0 + A3)*x0 + A4);

小数部分的计算不是很直接,这里用到了级数近似的方式。

\(2^x\)的多项式展开。对\(2^x\)进行泰勒展开可以得到

\(2^x = 1 + xln2 + \frac{x^2ln^{2}\ 2}{2} + \frac{x^3ln^{3}\ 2}{3!} + \frac{x^4ln^{4}\ 2}{4!} + \cdots\)

\(A1 = \frac{ln^{3}\ 2}{3!A0},\)

\(A2 = \frac{ln^{2}\ 2}{2!A0},\)

\(A3 = \frac{ln2}{A0},\)

\(A4 = \frac{1}{A0}.\)

将以上代入((((x0 + A1)*x0 + A2)*x0 + A3)*x0 + A4)可以得到\(2^x\)的四阶多项式展开,不过每项系数都多除以了一个A0。这时候就需要expTab_f了。

expTab_fexpTab_f有64项,其作用除了消去\(2^x\)多项式展开中的A0,还有就是乘上之前忽略的小数部分。一开始,输入x乘以64,相当于小数点向右移动了6位,因而有6位小数移动到了整数部分(按照二进制表示来考虑),不过在计算整数阶码时没有考虑这部分,所以之后需要把这6位小数重新乘上去,根据整数部分低6位的值计算对应的小数部分,这就是expTab_f所表示的内容。比如整数的低6位为1时,表示小数\(2^{1/64}=1.0108892860517005\),低6位为3时,表示小数\(2^{1/32+1/64}=1.0330248790212284\),低6位为63时(所有位为1),表示小数\(2^{1-1/64}=1.978456026387951\)。

一些疑问

至此,这段代码基本就搞清楚了。对此,还有几个问题:第一,为什么需要预先左移6位,从运算角度考虑并没有起到什么实质作用;第二,为什么\(2^x\)多项式展开的系数都要除以A0,如果不除会有什么不同。对于第一个问题,我猜可能是精度考虑,移动到整数部分之后,这几位的小数精度可以保证,而且移动的位数越多,近似的精度越高。对于第二个问题,我猜也可能跟精度有关,但并不太清楚是怎样影响的。

最新文章

  1. phpexcel引入MVC框架会导致__autoload引入类文件失败的解决办法
  2. js 图片轮播(一)
  3. androud 自定义属性
  4. C语言:关于socket的基础知识点
  5. CUBRID学习笔记 22 插入数据
  6. GPRS连接失败问题
  7. 各大Oj平台介绍[转]
  8. GDI相关函数
  9. sql日期转换格式
  10. Asp.NET调用百度翻译
  11. bind() unbind()绑定解绑事件
  12. ajax 实现页面加载和内容的删除
  13. Open-Domain QA -paper
  14. 灵雀云CTO陈恺:从“鸿沟理论”看云原生,哪些技术能够跨越鸿沟?
  15. 从统计局采集最新的省市区镇数据,用js在浏览器中运行 V2
  16. docker安装mongodb并备份
  17. fcn+caffe+制作自己的数据集
  18. python3的unittest中使用test suite(测试套件)执行指定测试用例
  19. 并发编程---线程queue---进程池线程池---异部调用(回调机制)
  20. 自定义Exception异常

热门文章

  1. Spring Boot +Vue 项目实战笔记(三):数据库的引入
  2. Core3.1WebApi使用MongoDB
  3. 阿里云服务器部署mongodb
  4. openresty(nginx) 配置 http与https使用同一个端口,禁止 IP 直接访问
  5. Java 登录模块设计
  6. Python - 面向对象编程 - __str__()
  7. TypeScript 中高级类型的理解?有哪些?
  8. C# List集合类常用操作:四、删除
  9. 法术迸发(Spellburst)
  10. RSTP