如何环绕一个范围(How to wrap around a range)

2019-06-27 01:56发布

在我的程序角度在0表达了二皮。 我想一个方法来添加两个角度,并把它缠绕在二皮为0;如果结果大于二皮更高。 或者,如果我从一个角度减去的角度,这是低于0它会环绕二皮。

有没有办法做到这一点?

谢谢。

Answer 1:

你要找的是模数。 该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米的最小正数值,给定c为一个整数。 所以18模4将2(其中,c = 4),但-18模4也将是2(其中,c = -5)。

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



Answer 2:

angle = fmod(angle, 2.0 * pi);
if (angle < 0.0)
   angle += 2.0 * pi;

编辑:在重新阅读本(看着乔纳森·莱弗勒的答案)后,我有点被他的结论感到惊讶,所以我重写了代码,我认为是一个较为合适的形式(例如,计算打印出结果,以确保编译器不能只是完全丢弃的计算,因为它从来没有使用过)。 我也改成了使用Windows性能计数器(因为他没有包括他的定时器类和std::chrono::high_resolution_timer在两个编译器彻底打破了我得心应手现在)。

我也做了一些一般的代码清理(这是标记C ++,不C),得到这个:

#include <math.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <windows.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);
}

struct result {
    double sum;
    long long clocks;
    result(double d, long long c) : sum(d), clocks(c) {}

    friend std::ostream &operator<<(std::ostream &os, result const &r) {
        return os << "sum: " << r.sum << "\tticks: " << r.clocks;
    }
};

result operator+(result const &a, result const &b) {
    return result(a.sum + b.sum, a.clocks + b.clocks);
}

struct TestSet { double start, end, increment; };

template <class F>
result tester(F f, TestSet const &test, int count = 5)
{
    LARGE_INTEGER start, stop;

    double sum = 0.0;

    QueryPerformanceCounter(&start);

    for (int i = 0; i < count; i++) {
        for (double angle = test.start; angle < test.end; angle += test.increment)
            sum += f(angle);
    }
    QueryPerformanceCounter(&stop);

    return result(sum, stop.QuadPart - start.QuadPart);
}

int main() {

    std::vector<TestSet> tests {
        { -6.0 * PI, +6.0 * PI, 0.01 },
        { -600.0 * PI, +600.0 * PI, 3.00 }
    };


    std::cout << "Small angles:\n";
    std::cout << "loop subtraction: " << tester(r1, tests[0]) << "\n";
    std::cout << "            fmod: " << tester(r2, tests[0]) << "\n";
    std::cout << "           floor: " << tester(r3, tests[0]) << "\n";
    std::cout << "\nLarge angles:\n";
    std::cout << "loop subtraction: " << tester(r1, tests[1]) << "\n";
    std::cout << "            fmod: " << tester(r2, tests[1]) << "\n";
    std::cout << "           floor: " << tester(r3, tests[1]) << "\n";

}

我得到的结果如下:

Small angles:
loop subtraction: sum: 59196    ticks: 684
            fmod: sum: 59196    ticks: 1409
           floor: sum: 59196    ticks: 1885

Large angles:
loop subtraction: sum: 19786.6  ticks: 12516
            fmod: sum: 19755.2  ticks: 464
           floor: sum: 19755.2  ticks: 649

至少对我来说,结果似乎支持超过乔纳森达到一个相当不同的结论。 望着那做减法在一个循环的版本,我们可以看到两点:对大角度测试它产生之这是从其他两个(即,这是不准确的)不同,第二,这是可怕的慢。 除非你肯定知道你的投入总是开始时几乎完全正常化,这基本上是不可用的。

之间fmod版和floor版,似乎是没有空间的说法-他们都产生准确的结果,但fmod版本同时在小角度和大角度的测试速度更快。

我做更多的测试,随着重复次数和减少在大角度测试步长进行试验。 虽然我想这是可能的 ,它只是由于平台或编译器的差异,我无法找到,即使差点坚持乔纳森的结果或结论的任何情况或情况。

底线:如果你有很多关于你的输入先验知识,并知道它永远是接近归你正常化之前 ,那么你可能能够逃脱在一个循环中做减法。 在任何其他情况下, fmod是明确的选择。 人们似乎任何情况下在该floor的版本做任何意义。

Oh, for what it's worth:
OS: Windows 7 ultimate
Compiler: g++ 4.9.1
Hardware: AMD A6-6400K


Answer 3:

出于好奇,我尝试用在其他的答案三种算法,定时他们。

当被归一化的值接近范围0..2π,则while算法是最快; 在使用算法fmod()是最慢的,并且使用算法floor()是在两者之间。

当被归一化的值不接近的范围内0..2π,则while算法是最慢的,在使用算法floor()是最快的,以及使用该算法fmod()是在两者之间。

所以,我的结论是:

  • 如果角度(通常)靠近归一化时, while算法是使用一个。
  • 如果角度不接近常态化,那么floor()算法是使用一个。

检测结果:

R1 = while ,R2 = fmod() ,R 3 = 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);
}

在Mac OS X 10.7.4测试与标准/usr/bin/gcci686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.9.00) )。 在“靠近归一化的”测试代码被示出; 在“远离规范化”的测试数据是通过取消注释创建//测试数据的注释。

定时用自制的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


Answer 4:

您可以使用这样的事情:

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

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

基本上,你必须改变由二皮音乐的角度,直到你放心,它不会超过二皮或低于0。



Answer 5:

简单的一招:只要加一个偏移量,它必须是二皮的 ,带来的结果为正的范围内做FMOD前()。 所述FMOD()将让它能够重新自动地在范围[0,2PI)。 这只要你预先知道可能的输入你可能得到的范围内(你经常这样做)的作品。 较大的偏移,您申请的FP精度位数越多,你松了,所以你可能不希望添加,比如说,20000pi,尽管这肯定会是“更安全”在处理非常大的出界外的输入项。 假设没有人会通过输入角度,而疯狂的范围[-8pi,+ INF)之外的总和,我们只添加之前FMOD()ING 8PI。

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


Answer 6:

同样,如果你想在范围-2pi到2PI然后FMOD的伟大工程



文章来源: How to wrap around a range
标签: c++ range