Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[C/C++]C语言中math.h和cmath的pow()精度问题

[C/C++]C语言中math.h和cmath的pow()精度问题

作者头像
用户7886150
修改于 2021-02-11 10:18:18
修改于 2021-02-11 10:18:18
1.7K0
举报
文章被收录于专栏:bit哲学院bit哲学院

参考链接: C++ pow()

帮小朋友们DEBUG的时候,他们有个题无论怎么提交OJ都不给过。 

我回来后想了想,估计是因为math.h库返回值转int时精度丢失的问题。 

>测试代码 

#include <stdio.h>

#include <math.h>

//MinGW GCC 4.7.2 32-bit Release

int main(){

    printf("math.h - double pow(double, double) 精度测试\n");

    int a=3;

    printf("%d\n",(int)pow(5,3));//1.输出125

    printf("%d\n",(int)pow(5,a));//2.输出124 这里丢精度了,结合下面的[3],我估计最后的结果是float->int 124.999999999999

    printf("%d\n",(int)round(pow(5,a)));//3.输出125 在[2]的基础上补上round()四舍五入函数,结果正常 

    printf("%lf\n",pow(5,a));//4.输出125.000000 显然,如果不转型成int,结果是没问题的

    return 0;

>pow的精度问题研究 

math.h库里,pow函数是基于浮点运算的。 

试着找了一圈,没有找到源码,只在一些犄角旮旯里看到有人提到是在x86指令机上利用log和exp运算求出来的。也就是: 

double pow(double a, double b){

    //我这里简化了这个过程 大概写个伪码

    return x86_exp(a * x86_log(b));// 也就是:e^(a*ln(b))

}stackoverflow上也有关于这个问题的描述: 

https://stackoverflow.com/questions/14104711/what-algorithm-is-used-to-pow-function-in-c 

这个函数的具体实现过程则不得而知了。 

上面那段代码玄学的地方是这两行: 

    int a=3;

    printf("%d\n",(int)pow(5,3));//输出125

    printf("%d\n",(int)pow(5,a));//输出124

 这就是那种看上去都没错,但是一运行发现特么居然不对的神坑。 

你要说是幂参数a转到double b时转类型除了问题导致值不一样吧...那为啥单独给个数字3就没有任何计算问题。 

所以我想,要不然测试一下常量? 

    const int b = 3;

    printf("%d\n",(int)pow(5,b));//输出125 没错,这次是125了orz 

  Emmmmmmmmm...const int 和 int 还有这种区别啊。难不成分配的长度不一样才导致转浮点时产生了什么误差?好吧我去看看汇编: 

看着好像没啥区别,就是存的地址不一样。那好吧,已知一个int四个字节,我多定义几个试试: 

int a=3,a2=3,a3=3,a4=3;

const int b=3,b2=3,b3=3,b4=3; 

于是: 

 每次增加4,至少在内存大小分配上const int 和 int 真的没啥区别。 

那好吧,我们再对比一下这几句的汇编: 

    int a=3;

    printf("%d\n",(int)pow(5,3));//输出125 I

    printf("%d\n",(int)pow(5,a));//输出124 II

    const int b = 3;

    printf("%d\n",(int)pow(5,b));//输出125 III 

int a的汇编是: 

   0x004013be <+14>:    movl   $0x3,0x2c(%esp) 

 首先是I:printf("%d\n",(int)pow(5,3)); 

   0x004013c6 <+22>:    movl   $0x7d,0x4(%esp)

   0x004013ce <+30>:    movl   $0x403064,(%esp)

   0x004013d5 <+37>:    call   0x401c80 <printf>然后是II:printf("%d\n",(int)pow(5,a));

   0x004013da <+42>:    fildl  0x2c(%esp)

   0x004013de <+46>:    fstpl  0x8(%esp)

   0x004013e2 <+50>:    mov    $0x0,%eax

   0x004013e7 <+55>:    mov    $0x40140000,%edx

   0x004013ec <+60>:    mov    %eax,(%esp)

   0x004013ef <+63>:    mov    %edx,0x4(%esp)

   0x004013f3 <+67>:    call   0x401c88 <pow>//注意这里是调用了pow函数

   0x004013f8 <+72>:    fnstcw 0x1e(%esp)// 开始转返回值(其实这个就是一般操作)

   0x004013fc <+76>:    mov    0x1e(%esp),%ax

   0x00401401 <+81>:    mov    $0xc,%ah

   0x00401403 <+83>:    mov    %ax,0x1c(%esp)

   0x00401408 <+88>:    fldcw  0x1c(%esp)

   0x0040140c <+92>:    fistpl 0x18(%esp)

   0x00401410 <+96>:    fldcw  0x1e(%esp)

   0x00401414 <+100>:    mov    0x18(%esp),%eax//最后存到0x18xxx去了

   0x00401418 <+104>:    mov    %eax,0x4(%esp)

   0x0040141c <+108>:    movl   $0x403064,(%esp)

   0x00401423 <+115>:    call   0x401c80 <printf> 

嚯,真有够长的,再看看III吧:printf("%d\n",(int)pow(5,b)); 

   0x00401428 <+120>:    movl   $0x3,0x28(%esp)//这行做的是const int b = 3;

   0x00401430 <+128>:    movl   $0x7d,0x4(%esp)//接下来是printf

   0x00401438 <+136>:    movl   $0x403064,(%esp)

   0x0040143f <+143>:    call   0x401c80 <printf>III和I看起来一模一样,也难怪输出的都是125 了。

 那么问题又来了,为啥I和III都没有call <pow>。假如我把II注释掉,再去看汇编,是这样的: 

=> 0x004013be <+14>:    movl   $0x3,0x1c(%esp) // I

   0x004013c6 <+22>:    movl   $0x7d,0x4(%esp)

   0x004013ce <+30>:    movl   $0x403064,(%esp)

   0x004013d5 <+37>:    call   0x401c30 <printf>

=> 0x004013da <+42>:    movl   $0x3,0x18(%esp) // III

   0x004013e2 <+50>:    movl   $0x7d,0x4(%esp)

   0x004013ea <+58>:    movl   $0x403064,(%esp)

   0x004013f1 <+65>:    call   0x401c30 <printf> 

是的,依旧没有调用pow。为啥???这结果是怎么运算出来的? 

本着科学的精神,我做了这个测试: 

果然依旧没有调用pow。好吧,先放过这个问题...毕竟我的专精不在C的编译和汇编上,也许是有什么我尙不了解的知识点我还没了解到,改天去问问写C的底层大佬。 

还是回归正题,我们去考虑一下II中调用了pow进行幂运算的误差问题,毕竟I II III中,只有调用了pow的II输出了124这个错误的值。出于好奇,我去看了一下机器实现浮点运算的方法。另外,对于pow原生的参数类型double做以下测试: 

    double s=5.000,sb=3.0;

    printf("%d",(int)pow(s,sb));有: 

   0x004013be <+14>:    mov    $0x0,%eax

   0x004013c3 <+19>:    mov    $0x40140000,%edx

   0x004013c8 <+24>:    mov    %eax,0x28(%esp)

   0x004013cc <+28>:    mov    %edx,0x2c(%esp) //double s=5.0

   0x004013d0 <+32>:    mov    $0x0,%eax

   0x004013d5 <+37>:    mov    $0x40080000,%edx

   0x004013da <+42>:    mov    %eax,0x20(%esp)

   0x004013de <+46>:    mov    %edx,0x24(%esp) //double sb=3.0

   0x004013e2 <+50>:    mov    0x20(%esp),%eax //s

   0x004013e6 <+54>:    mov    0x24(%esp),%edx

   0x004013ea <+58>:    mov    %eax,0x8(%esp)

   0x004013ee <+62>:    mov    %edx,0xc(%esp)

   0x004013f2 <+66>:    mov    0x28(%esp),%eax //sb

   0x004013f6 <+70>:    mov    0x2c(%esp),%edx

   0x004013fa <+74>:    mov    %eax,(%esp)

   0x004013fd <+77>:    mov    %edx,0x4(%esp)

   0x00401401 <+81>:    call   0x401c70 <pow>

   0x00401406 <+86>:    fnstcw 0x1e(%esp)

   0x0040140a <+90>:    mov    0x1e(%esp),%ax

   0x0040140f <+95>:    mov    $0xc,%ah

   0x00401411 <+97>:    mov    %ax,0x1c(%esp)

   0x00401416 <+102>:    fldcw  0x1c(%esp)

   0x0040141a <+106>:    fistpl 0x18(%esp)

   0x0040141e <+110>:    fldcw  0x1e(%esp)

   0x00401422 <+114>:    mov    0x18(%esp),%eax

   0x00401426 <+118>:    mov    %eax,0x4(%esp)

   0x0040142a <+122>:    movl   $0x403064,(%esp)

   0x00401431 <+129>:    call   0x401c78 <printf>对比一下,基本可以确定就是传参int a的时候的问题。传参的不同,在经过pow内部的运算会产生不同的结果。由于现在pow是一个黑盒(不知道源码),我们只好动手算算按int传参之后的后果了。我们假设stackoverflow上给出的pow内部运算方法是对的,按照IEEE754对单双精度的定义及刚刚stackoverflow里某人推测给出pow的运算方法: 

fld1            // 1.0

fld             // y

fld             // x 

fyl2x           // y * log2(x)

fadd            // add 1.0

f2xm1           // 2 ^ (x-1) 

 我大概算了一下,实际上在经过运算后原本的125这个逼近值最后被存为浮点时变成01000010111110011111111111111111(但愿我没算错Orz,总之大概思路对行了),换句话说,124.999。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
《coredump问题原理探究》Linux x86版6.7节多继承
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/43635239
血狼debugeeker
2018/09/20
4560
《coredump问题原理探究》Linux x86版6.7节多继承
《coredump问题原理探究》Linux x86版5.4节C风格数据结构内存布局之数组coredump例子
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/8703902
血狼debugeeker
2018/09/20
1K0
《coredump问题原理探究》Linux x86版6.6节单继承
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/43491851
血狼debugeeker
2018/09/20
4060
《coredump问题原理探究》Linux x86版6.6节单继承
《coredump问题原理探究》Linux x86版6.4节虚函数
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/42715807
血狼debugeeker
2018/09/20
4570
《coredump问题原理探究》Linux x86版6.4节虚函数
《coredump问题原理探究》Linux x86版4.2节函数的逆向之顺序结构
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/8564337
血狼debugeeker
2018/09/20
3630
《coredump问题原理探究》Linux x86版6.3节有成员变量的类coredump例子
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/42685207
血狼debugeeker
2018/09/20
1.6K0
《coredump问题原理探究》Linux x86版4.5节函数的逆向之coredump例子
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/8596448
血狼debugeeker
2018/09/20
1.2K0
《coredump问题原理探究》Linux x86版5.3节C风格数据结构内存布局之数组
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/8675206
血狼debugeeker
2018/09/20
5100
《coredump问题原理探究》Linux x86版6.5节虚函数的coredump例子
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/42847603
血狼debugeeker
2018/09/20
6030
《coredump问题原理探究》Linux x86版7.3节List对象
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/45230141
血狼debugeeker
2018/09/20
5250
《coredump问题原理探究》Linux x86版7.3节List对象
《coredump问题原理探究》Linux x86版5.2节C风格数据结构内存布局之基本数据类型
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/8660820
血狼debugeeker
2018/09/20
3990
《coredump问题原理探究》Linux x86版7.4节List coredump例子
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/45277185
血狼debugeeker
2018/09/20
8040
《coredump问题原理探究》Linux x86版7.7节 set对象
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/46758253
血狼debugeeker
2018/09/20
6280
《coredump问题原理探究》Linux x86版7.7节 set对象
《coredump问题原理探究》Linux x86版6.2节C++风格数据结构内存布局之有成员变量的类
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/41620563
血狼debugeeker
2018/09/20
8690
《coredump问题原理探究》Linux x86版7.9节list相关的iterator对象
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/46869225
血狼debugeeker
2018/09/20
3760
缓冲区溢出攻击原理分析
《缓冲区溢出攻击实践》以实践者角度介绍了初级缓冲区溢出攻击方法,本文从原理上对该方法做原理性介绍。
全栈程序员站长
2022/09/14
1.2K0
缓冲区溢出攻击原理分析
《coredump问题原理探究》Linux x86版7.6节 Map coredump例子
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/45375265
血狼debugeeker
2018/09/20
1.4K0
《coredump问题原理探究》Linux x86版7.1节vector对象
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/43941487
血狼debugeeker
2018/09/20
5490
《coredump问题原理探究》Linux x86版7.1节vector对象
《coredump问题原理探究》Linux x86版7.8节vector相关的iterator对象
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/46821079
血狼debugeeker
2018/09/20
4130
《coredump问题原理探究》Linux x86版7.5节 Map对象
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/45319991
血狼debugeeker
2018/09/20
5380
《coredump问题原理探究》Linux x86版7.5节 Map对象
推荐阅读
《coredump问题原理探究》Linux x86版6.7节多继承
4560
《coredump问题原理探究》Linux x86版5.4节C风格数据结构内存布局之数组coredump例子
1K0
《coredump问题原理探究》Linux x86版6.6节单继承
4060
《coredump问题原理探究》Linux x86版6.4节虚函数
4570
《coredump问题原理探究》Linux x86版4.2节函数的逆向之顺序结构
3630
《coredump问题原理探究》Linux x86版6.3节有成员变量的类coredump例子
1.6K0
《coredump问题原理探究》Linux x86版4.5节函数的逆向之coredump例子
1.2K0
《coredump问题原理探究》Linux x86版5.3节C风格数据结构内存布局之数组
5100
《coredump问题原理探究》Linux x86版6.5节虚函数的coredump例子
6030
《coredump问题原理探究》Linux x86版7.3节List对象
5250
《coredump问题原理探究》Linux x86版5.2节C风格数据结构内存布局之基本数据类型
3990
《coredump问题原理探究》Linux x86版7.4节List coredump例子
8040
《coredump问题原理探究》Linux x86版7.7节 set对象
6280
《coredump问题原理探究》Linux x86版6.2节C++风格数据结构内存布局之有成员变量的类
8690
《coredump问题原理探究》Linux x86版7.9节list相关的iterator对象
3760
缓冲区溢出攻击原理分析
1.2K0
《coredump问题原理探究》Linux x86版7.6节 Map coredump例子
1.4K0
《coredump问题原理探究》Linux x86版7.1节vector对象
5490
《coredump问题原理探究》Linux x86版7.8节vector相关的iterator对象
4130
《coredump问题原理探究》Linux x86版7.5节 Map对象
5380
相关推荐
《coredump问题原理探究》Linux x86版6.7节多继承
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档