首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >atan2f对fmodf对纯减法

atan2f对fmodf对纯减法
EN

Stack Overflow用户
提问于 2017-10-17 15:14:42
回答 2查看 204关注 0票数 4

在集成过程中,我编写了一段代码来包装一个角度,这是我正在进行的一个小型模拟的一部分,我遇到了一个问题。因此,基本上,我们的想法是防止这个角度变大,确保它总是有一个正常的价值。我尝试了三种不同的方法,我希望能得到同样的结果。大多数时候他们都这么做。但是前两个给出了角值包围点附近的工件。然后,当我从角度值生成一个波形时,由于这些精度误差,我会得到不希望得到的结果。

第一种方法是这样的(限制角度为-8PI +8PI范围):

代码语言:javascript
复制
self->state.angle = atan2f(sinf(angle / 8), cosf(angle / 8)) * 8;

这将创建如下所示的工件:

第二种方法:

代码语言:javascript
复制
self->state.angle = fmodf(angle, (float)(2.f * M_PI * 8))

创建相同的结果:

但是如果我就这样做的话:

代码语言:javascript
复制
float limit = (8 * 2 * M_PI); 
if(angle > limit) angle -= limit;           
if(angle < 0) angle += limit;               
self->state.angle = a;

然后,它按照预期工作,没有任何工件:

我在这里错过了什么?为什么其他两种方法会产生精度错误?我希望它们都能产生相同的结果(我知道角度的范围是不同的,但是当角度被进一步传递到一个sin函数时,我希望结果是相同的)。

编辑:小测试

代码语言:javascript
复制
// g++ -o test test.cc -lm && ./test
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>

int main(int argc, char **argv){
    float a1 = 0;
    float a2 = 0;
    float a3 = 0;
    float dt = 1.f / 7500.f;

    for(float t = -4.f * M_PI; t < (4.f * M_PI); t+=dt){
        a1 += dt;
        a2 += dt;
        a3 += dt;

        float b1 = a1;
        if(b1 > 2.f * M_PI) b1 -= 2.f * M_PI;
        if(b1 < 0.f) b1 += 2.f * M_PI;
        float b2 = atan2f(sinf(a2), cosf(a2));
        float b3 = fmodf(a3, 2 * M_PI);

        float x1 = sinf(b1);
        float x2 = sinf(b2);
        float x3 = sinf(b3);

        if((x1 * x2 * x3) > 1e-9){
            printf("%f: x[%f %f %f],\tx1-x2:%f x1-x3:%f x2-x3:%f]\n", t, x1, x2, x3, (x1 - x2) * 1e9, (x1 - x3) * 1e9, (x2 - x3) * 1e9);
        }
    }

    return 0;
}

输出:

代码语言:javascript
复制
-9.421306: x[0.001565 0.001565 0.001565],       x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-9.421172: x[0.001431 0.001431 0.001431],       x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-9.421039: x[0.001298 0.001298 0.001298],       x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-9.420905: x[0.001165 0.001165 0.001165],       x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-9.420772: x[0.001032 0.001032 0.001032],       x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-6.275573: x[0.001037 0.001037 0.001037],       x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.275439: x[0.001171 0.001171 0.001171],       x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.275306: x[0.001304 0.001304 0.001304],       x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.275172: x[0.001438 0.001438 0.001438],       x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.275039: x[0.001571 0.001571 0.001571],       x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.274905: x[0.001705 0.001705 0.001705],       x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.274772: x[0.001838 0.001838 0.001838],       x1-x2:0.116415 x1-x3:174.855813 x2-x3:174.739398]
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-10-17 15:39:32

如果没有更多的信息,很难给出解释,但我还是会尝试的。

使用fmod和“简单减法”(或加法)的区别是,如果值已经超出了范围(例如,800000 * M_PI ),那么加/减方法不会改变值多少(效果很小),而且一个非常大(绝对值)的角度会击中您的计算函数,没有问题,因为没有看到任何伪影。

使用fmod (或atan2)可以保证值在您定义的范围内,这不是一回事。

注意:

代码语言:javascript
复制
float limit = (8 * 2 * M_PI); 
while(angle > limit) angle -= limit;           
while(angle < 0) angle += limit;               
self->state.angle = a;

将相当于(大致) fmod (但对于大值则不如fmod,因为它由于重复加或减而引入浮点积累错误)。

因此,如果在计算中输入非常大的值会产生正确的结果,那么您可能会想,将您的角度标准化而不是将其留给数学库是明智的。

编辑:这个答案的第一部分假设这个超级越界的情况会发生,而进一步的问题编辑表明情况并非如此,所以.

fmod和2个测试的另一个不同之处是,如果调用fmod时已经在范围内,则不能保证该值是相同的

例如,如果实现类似于value - int(value/modulus)*modulus;,浮点不准确可能会将一个小值减去原始值。

使用atan2fsin .如果已经在范围内,也会更改结果。

(即使这个值稍微超出了范围,也不会像您所做的那样进行加/减,而不涉及除法/截断/乘)

因为您可以通过一次添加或细分来调整范围内的值,所以在您的情况下,使用fmodfatan2f是过分的,您可以坚持简单的sub/add (添加else将保存测试:如果您只是调整了一个太低的值,就不需要测试该值是否太大)

票数 3
EN

Stack Overflow用户

发布于 2017-10-18 13:27:04

floatdouble数学。

当然,第三种方法效果最好。它使用的是double数学。

看看b1, b3。当然,由于b3调用的缘故,float是以精确的方式计算的。

请注意,M_PI通常是一个double,因此b1 -= 2.f * M_PI;很可能是用double精确数学完成的,并且提供了一个更精确的答案。f in 2.f并不强制产品2.f * M_PI进入float --产品是double-=也是。

代码语言:javascript
复制
b1 -= 2.f * M_PI;
// same as 
b1 = (float)((double)b1 - (2.f * M_PI));

此外:通过优化和FLT_EVAL_METHOD > 0,C可以以比类型更高的精度执行FP代码。b1可以在double上计算,即使代码显示为float。由于M_PI (一个有理数)不完全是π(一个无理数),其精度更高,从而导致比fmodf(a3, 2 * M_PI);更精确的b1

代码语言:javascript
复制
float b1 = a1;
if(b1 > 2.f * M_PI) b1 -= 2.f * M_PI;  // double math
if(b1 < 0.f) b1 += 2.f * M_PI;         // double math
float b3 = fmodf(a3, 2 * M_PI);

要确保float结果,请使用volatile float b1 = a1;进行公平比较,并使用float常量(如#define M_PIf ((float) M_PI) )

再远一点。通过一个公平的比较,最好使用if(b1 < -2.f * M_PIf) b1 += 2.f * M_PIf;

推荐OP打印FLT_EVAL_METHOD以帮助进一步讨论。

代码语言:javascript
复制
#include <float.h>
printf("%d\n", FLT_EVAL_METHOD);

任择议定书有两个解决方案:

  1. 使用更广泛的数学,如double,用于敏感的弧度还原。 float b3 = fmod(a3,2* M_PI);//非fmodf
  2. 不要使用弧度,而是像度或BAM这样的角度测量,并执行精确的范围缩小。角度将需要度到弧度转换之前,三角调用。 浮动b3 = fmodf(a3,360.0f);//使用fmodf、a3、b3为度

注意:float b2 = atan2f(sinf(a2), cosf(a2));方法不是一个合理的竞争者。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46793547

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档