如何环绕范围

时间:2012-08-16 03:20:55

标签: c++ range

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

有办法做到这一点吗?

感谢。

8 个答案:

答案 0 :(得分:35)

您要寻找的是模数。 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 )。

如果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是小于或等于输入的最大整数。

答案 1 :(得分:16)

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

编辑:重新阅读之后(看看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";

}

我得到的结果如下:

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版本在小角度都更快和大角度测试。

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

结论:如果您有很多关于输入的先验知识,并且知道,那么之前 >可能能够在循环中进行减法。在任何其他情况下,fmod是明确的选择。似乎没有情况,floor版本完全没有任何意义。

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

答案 2 :(得分:8)

出于好奇,我在其他答案中尝试了三种算法,计时。

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

当要标准化的值不接近0..2π的范围时,while算法最慢,使用floor()的算法最快,使用{{1}的算法介于两者之间。

所以,我得出结论:

  • 如果角度(通常)接近标准化,则使用fmod()算法。
  • 如果角度不接近标准化,则使用while算法。

测试结果:

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

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 显示的值。 C标准没有定义π的值,但是POSIX确实定义了PI和许多相关的常量,所以我可以用M_PI而不是{{1}编写我的代码}。

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/gcc注释来创建“远离标准化”的测试数据。

使用自制的GCC 4.7.1的时间安排类似(将得出相同的结论):

i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.9.00)

答案 3 :(得分:4)

您可以使用以下内容:

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

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

基本上,你必须将角度改变2pi,直到你确信它不超过2pi或低于0。

答案 4 :(得分:1)

简单技巧:只需添加一个偏移量,该偏移量必须是2pi的,才能在执行fmod()之前将结果置于正范围内。 fmod()会自动将其恢复到[0,2pi]范围内。只要您事先知道可能获得的输入范围(您经常这样做),这就有效。你应用的偏移量越大,你松散的FP精度就越多,所以你可能不想添加20000pi,尽管这肯定会更安全#34;在处理非常大的越界输入方面。假设没有人会传递超出相当疯狂范围[-8pi,+ inf]之外的输入角度,我们只需在fmod()之前加上8pi。

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

如果您愿意选择自己的角度表示形式,那么根据您的用例,可以使用一些有效的特殊情况。 (请注意,将0作为下限已经是允许效率的一种特殊情况。)

将角度表示为单位向量

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

<table id="custitindetail" class="table abc-properties" data-toggle-section="abc-properties">
    <tr>
        <th><?php echo esc_html__( 'Option A', ST_TEXTDOMAIN ) ?></th>
        <td>
        <?php echo get_post_meta( $post_id, 'custom_option_a', true ); ?>
        </td>
    </tr>               
    <tr>
        <th><?php echo esc_html__( 'Option B', ST_TEXTDOMAIN ) ?></th>
        <td>
        <?php echo get_post_meta( $post_id, 'custom_option_b', true ); ?>
        </td>
    </tr>                           
    <tr>
        <th><?php echo esc_html__( 'Option C', ST_TEXTDOMAIN ) ?></th>
        <td class="cust_holiday_itinerary">
        <?php echo get_post_meta( $post_id, 'custom_option_c', true ); ?>
        </td>
    </tr>
    <?php
    }
    ?>
</table>

负角正好起作用。

您还可以进行归一化,换行,然后按比例缩小到弧度,但会降低精度和效率。

这在与许多着色器代码相似的代码中很有用,尤其是在“一切都是单位矢量”环境中。

将角度表示为以2的幂限制的无符号整数

如果您能够将角度表示为[0和2 ^ n)之间的值,而不是[0和2π),则可以按位和操作将它们包裹在2的幂内:

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

更好的是,如果您可以选择等于整数类型大小的2的幂,则数字会自然地换行。 unsigned int wrap(unsigned int angle) { // wrap between [0 and 65,535) return angle & 0xffff; } 总是包裹在[0和2 ^ 16)之内,而uint16_t总是包裹在[0和2 ^ 32)之内。六万五千个标题对任何人都应该足够了吗? (-:

我在8位天的游戏和演示类型的东西中甚至在3D图形卡之前的纹理贴图中都使用了它。我想它在仿真器和翻录的代码中仍然有用,但也许甚至在微型微控制器上也是如此?

答案 7 :(得分:0)

这运行良好且速度足够快:

#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))