如何环绕范围

Jig*_*uff 22 c++ range

程序中的角度以0到2pi表示.我想要一种方法来添加两个角度,如果结果高于2pi,则将它包围在2pi到0之间.或者,如果我从一个角度减去一个角度并且它低于0,它将环绕2pi.

有没有办法做到这一点?

谢谢.

Tom*_*mes 37

你要找的是模数.fmod函数不起作用,因为它计算余数而不是算术模数.这样的事情应该有效:

inline double wrapAngle( double angle )
{
    double twoPi = 2.0 * 3.141592865358979;
    return angle - twoPi * floor( angle / twoPi );
}
Run Code Online (Sandbox Code Playgroud)

编辑:

剩余部分通常定义为长分割后遗留的内容(例如,18/4的剩余部分为2,因为18 = 4*4 + 2).当你有负数时,这会变得毛茸茸.找到有符号除法的余数的常用方法是使余数具有与结果相同的符号(例如,-18/4的余数为-2,因为-18 = -4*4 + -2).

如果c是整数,则x模数y的定义是等式x = y*c + m中m的最小正值.因此18 mod 4将是2(其中c = 4),然而-18 mod 4也将是2(其中c = -5).

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


Jer*_*fin 16

angle = fmod(angle, 2.0 * pi);
if (angle < 0.0)
   angle += 2.0 * pi;
Run Code Online (Sandbox Code Playgroud)

编辑:在重新阅读之后(并查看Jonathan Leffler的回答)我对他的结论感到有些惊讶,所以我将代码改写为我认为更合适的形式(例如,打印出计算结果以确保编译器不能完全丢弃计算,因为它从未使用过).我也改变它使用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";

}
Run Code Online (Sandbox Code Playgroud)

我得到的结果如下:

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
Run Code Online (Sandbox Code Playgroud)

至少在我看来,结果似乎支持了一个与乔纳森达成的完全不同的结论.看一下在循环中进行减法的版本,我们看到两点:对于大角度测试,它产生的总和与其他两个不同(即,它是不准确的),其次,它非常慢.除非你确定你的输入总是开始接近标准化,否则这基本上是无法使用的.

fmod版本和floor版本之间似乎没有争论的余地 - 它们都能产生准确的结果,但是fmod在小角度和大角度测试中版本都更快.

我做了一些测试,尝试增加重复次数并减小大角度测试中的步长.虽然我认为这可能仅仅是由于平台或编译器的差异,但我无法找到任何甚至接近维护Jonathan结果或结论的情况或情况.

结论:如果你对输入有很多先验知识,并且知道它在标准化之前总是接近标准化,那么你可以在循环中进行减法.在任何其他情况下,fmod是明确的选择.似乎没有任何情况下floor版本有任何意义.

Oh, for what it's worth:
OS: Windows 7 ultimate
Compiler: g++ 4.9.1
Hardware: AMD A6-6400K
Run Code Online (Sandbox Code Playgroud)


Jon*_*ler 8

出于好奇,我在其他答案中尝试了三种算法,对它们进行计时.

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

当要归一化的值不接近0..2π的范围时,则while算法最慢,使用的算法floor()最快,并且使用的算法fmod()介于两者之间.

所以,我的结论是:

  • 如果角度(通常)接近标准化,则while算法是要使用的算法.
  • 如果角度不接近标准化,则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
Run Code Online (Sandbox Code Playgroud)

测试代码:

测试代码使用显示的值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);
}
Run Code Online (Sandbox Code Playgroud)

使用标准/usr/bin/gcc(i686-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
Run Code Online (Sandbox Code Playgroud)