c++ 如何环绕一个范围

0qx6xfy6  于 2023-11-19  发布在  其他
关注(0)|答案(7)|浏览(99)

在我的程序中,Angular 是用0到2pi来表示的。我想知道一种方法,如果两个Angular 相加的结果大于2pi,那么它会绕2pi到0。或者,如果我从一个Angular 减去一个Angular ,而结果小于0,那么它会绕2pi。
有没有办法做到这一点?
谢谢.

li9yvcax

li9yvcax1#

你要找的是模数。fmod函数不会工作,因为它计算余数而不是算术模数。类似这样的东西应该可以工作:

inline double wrapAngle( double angle )
{
    double twoPi = 2.0 * 3.141592865358979;
    return angle - twoPi * floor( angle / twoPi );
}

字符串
编辑:
余数通常被定义为长除法后的余数(例如,18/4的余数是2,因为18 = 4 * 4 + 2)。当你有负数时,这会变得很麻烦。找到有符号除法的余数的常用方法是让余数的符号与结果相同。(例如,-18/4的余数是-2,因为**-18 = -4 * 4 + -2**)。
x模y的定义是在方程x=y*c+m中m的最小正值,给定c是整数。因此18 mod 4将是2(其中c=4),然而**-18 mod 4**也将是2(其中c=-5)。

x mod y的最简单计算是x-y*floor(x/y),其中floor是小于或等于输入的最大整数。

lokaqttq

lokaqttq2#

出于好奇,我在其他答案中尝试了三种算法,并对它们进行了计时。
当要归一化的值接近0..2π时,while算法最快;使用fmod()的算法最慢,使用floor()的算法介于两者之间。
当要归一化的值不接近0..2π时,则while算法最慢,使用floor()的算法最快,使用fmod()的算法介于两者之间。
所以,我的结论是:

  • 如果Angular (通常)接近归一化,则使用while算法。
  • 如果Angular 不接近标准化,则使用floor()算法。

测试结果:

r1 = while,r2 = fmod(),r3 = floor()

Near Normal     Far From Normal
r1 0.000020     r1 0.000456
r2 0.000078     r2 0.000085
r3 0.000058     r3 0.000065
r1 0.000032     r1 0.000406
r2 0.000085     r2 0.000083
r3 0.000057     r3 0.000063
r1 0.000033     r1 0.000406
r2 0.000085     r2 0.000085
r3 0.000058     r3 0.000065
r1 0.000033     r1 0.000407
r2 0.000086     r2 0.000083
r3 0.000058     r3 0.000063

字符串

测试代码:

测试代码使用了PI的值。C标准没有定义π的值,但是POSIX定义了M_PI和一些相关的常数,所以我可以使用M_PI而不是PI来编写代码。

#include <math.h>
#include <stdio.h>
#include "timer.h"

static const double PI = 3.14159265358979323844;

static double r1(double angle)
{
    while (angle > 2.0 * PI)
        angle -= 2.0 * PI;
    while (angle < 0)
        angle += 2.0 * PI;
    return angle;
}

static double r2(double angle)
{
    angle = fmod(angle, 2.0 * PI);
    if (angle < 0.0)
        angle += 2.0 * PI;
    return angle;
}

static double r3(double angle)
{
    double twoPi = 2.0 * PI;
    return angle - twoPi * floor( angle / twoPi );
}

static void tester(const char * tag, double (*test)(double), int noisy)
{
    typedef struct TestSet { double start, end, increment; } TestSet;
    static const TestSet tests[] =
    {
        {   -6.0 * PI,   +6.0 * PI, 0.01 },
    //  { -600.0 * PI, +600.0 * PI, 3.00 },
    };
    enum { NUM_TESTS = sizeof(tests) / sizeof(tests[0]) };
    Clock clk;
    clk_init(&clk);
    clk_start(&clk);
    for (int i = 0; i < NUM_TESTS; i++)
    {
        for (double angle = tests[i].start; angle < tests[i].end; angle += tests[i].increment)
        {
            double result = (*test)(angle);
            if (noisy)
                printf("%12.8f : %12.8f\n", angle, result);
        }
    }
    clk_stop(&clk);
    char buffer[32];
    printf("%s %s\n", tag, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
}

int main(void)
{
    tester("r1", r1, 0);
    tester("r2", r2, 0);
    tester("r3", r3, 0);
    tester("r1", r1, 0);
    tester("r2", r2, 0);
    tester("r3", r3, 0);
    tester("r1", r1, 0);
    tester("r2", r2, 0);
    tester("r3", r3, 0);
    tester("r1", r1, 0);
    tester("r2", r2, 0);
    tester("r3", r3, 0);
    return(0);
}


使用标准/usr/bin/gcci686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.9.00))在Mac OS X 10.7.4上进行测试。显示了“接近标准化”的测试代码;“远未标准化”的测试数据是通过取消注解测试数据中的//注解而创建的。
自制GCC 4.7.1的时间是类似的(可以得出相同的结论):

Near Normal     Far From Normal
r1 0.000029     r1 0.000321
r2 0.000075     r2 0.000094
r3 0.000054     r3 0.000065
r1 0.000028     r1 0.000327
r2 0.000075     r2 0.000096
r3 0.000053     r3 0.000068
r1 0.000025     r1 0.000327
r2 0.000075     r2 0.000101
r3 0.000053     r3 0.000070
r1 0.000028     r1 0.000332
r2 0.000076     r2 0.000099
r3 0.000050     r3 0.000065

doinxwow

doinxwow3#

你可以使用这样的东西:

while (angle > 2pi)
    angle -= 2pi;

while (angle < 0)
    angle += 2pi;

字符串
基本上,你必须改变Angular 2 π,直到你确信它不超过2 π或低于0。

jogvjijk

jogvjijk4#

根据你的用例,如果你可以选择自己的Angular 表示,那么会有一些有效的特殊情况(注意,将0作为下限已经是一个考虑到效率的特殊情况)。

将Angular 表示为单位向量

如果你能够将Angular 表示为[0和1)之间的值,而不是[0和2π),那么你只需要取小数部分:

float wrap(float angle) {
    // wrap between [0 and 2*PI)
    return angle - floor(angle);
}

字符串
负Angular 正好。
您还可以规格化, Package ,然后缩放回弧度,但会损失一些精度和效率。
这在类似于许多着色器代码的代码中非常有用,特别是在“一切都是单位向量”的环境中。

将Angular 表示为限制在2的幂的无符号整数

如果你能够将Angular 表示为[0和2^n)之间的值,而不是[0和2π),那么你可以用按位与操作将它们包裹在2的幂内:

unsigned int wrap(unsigned int angle) {
    // wrap between [0 and 65,535)
    return angle & 0xffff;
}


更好的是,如果你可以选择一个2的幂,它等于一个整数类型的大小,数字就可以自然地换行。一个uint16_t总是在[0和2^16)之间换行,而一个uint32_t总是在[0和2^32)之间换行。65000个标题对任何人来说都应该足够了,对吗?(-:
我在8位时代的游戏和演示中使用了它,甚至在3D显卡之前的纹理Map中也使用了它。我想它在模拟器和复古游戏的代码中仍然有用,但也许甚至在微型微控制器上也有用?

zzlelutf

zzlelutf5#

简单的技巧:在执行fmod()之前,只需添加一个偏移量,该偏移量必须是2 pi的multiple,以将结果带入正范围。fmod()将自动将其带回范围[0,2 pi)。只要您事先知道可能获得的可能输入范围,就可以使用此方法(你经常这样做)。你应用的偏移量越大,你失去的FP精度就越多,所以你可能不想增加,比如说,20000 pi,尽管在处理非常大的越界输入方面,这肯定会“更安全”。假设没有人会传递总和在相当疯狂的范围[-8pi,+inf)之外的输入Angular ,我们将在fmod()ing之前添加8 pi。

double add_angles(float a, float b)
{
    ASSERT(a + b >= -8.0f*PI);
    return fmod(a + b + 8.0f*PI, 2.0f*PI);
}

字符串

cwtwac6a

cwtwac6a6#

同样,如果你想在-2pi到2 pi的范围内,那么fmod工作得很好。

3xiyfsfu

3xiyfsfu7#

这样做很好,也很快:

#ifndef M_PI
#define M_PI 3.14159265358979323846    
#endif // !M_PI
#ifndef M_2PI
#define M_2PI (2.0 * M_PI)
#endif // !M_2PI
#define to0_2pi(x) ((x) - ((int)((x) / (M_2PI)) - signbit(x)) * (M_2PI))

字符串

相关问题