Сравнение с плавающей точкой в ​​цикле while с модулем - PullRequest
3 голосов
/ 28 августа 2011

Я хотел бы посоветовать, как оптимизировать следующий цикл while:

double minor_interval   = 0.1;
double major_interval   = 1.0;

double start            = 0.0;
double finish           = 10.0;

printf("Start\r\n");

while (start < finish)
{
    printf("Minor interval: %.20f\r\n", start);

    double m = fmod(start, major_interval);
    printf("m: %.20f\r\n", m);

    if (m == 0)
        printf("At major interval: %.20f\r\n", start);

    start += minor_interval;
}

printf("Finished\r\n");

По сути, я увеличиваю счетчик в цикле на второстепенный интервал и хотел бы знать каждый раз в цикле, нахожусь ли я на главном интервале. Представьте, что вы рисуете линейку с интервалом в миллиметры, и каждый раз, когда я достигаю большого интервала, я хочу нарисовать сантиметр. Учитывая неточности в арифметике с плавающей запятой, как я могу изменить вышеуказанный цикл для реализации необходимых мне функций? Я пробовал разные методы сравнения результатов модуля, используя допуск без удачи. Обратите внимание, что второстепенный и мажорный интервалы могут быть любыми значениями, т.е. минор = 0,4 и мажор = 1,6 (для рисования приращений на четверть мили).

Заранее спасибо.

Ответы [ 3 ]

6 голосов
/ 28 августа 2011

Я бы реализовал это с помощью цикла for для переменной int i. Использование целых чисел позволяет избежать проблем округления из-за ограничений арифметики с плавающей запятой. [См. Что должен знать каждый компьютерный ученый об арифметике с плавающей точкой .]

Позвольте i перейти от 0 до iFinish-1 и установить time равным start + i*minor_interval. Значение iFinish можно найти, округлив (finish-start)/minor_interval до ближайшего целого.

Обновления основных осей могут обрабатываться аналогичным образом. Округли major_interval/minor_interval до ближайшего int, скажем k. Затем обновляйте метки большой оси на каждой k-й итерации.

С точки зрения кода это выглядит так:

double round(double r) {
    return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);
}
...
int iFinish = round((finish-start)/minor_interval);
int k = round(major_interval/minor_interval);
for (int i=0; i<iFinish; i++)
{
    double time = start + i*minor_interval;
    bool isMajor = (i%k == 0);
    ...
}
0 голосов
/ 28 августа 2011

Я бы сделал цикл для цикла, использующего целые числа.Также вам следует избегать операторов == для чисел с плавающей запятой / двойников из-за неточной природы этих чисел, если числа.

Таким образом, для преобразования двойных чисел в целые числа для цикла:

numIntervals = (int)((finish - start) / minor_intervals);
majIntervalsGap = numIntervals  / (int)((finish - start) / major_intervals);
double time = start;
for (int loop = 0;; loop < numIntervals; ++loop)
{
    bool isMajor = ((loop % majIntervalsGap) == 0);

    ... do you ourput here as you have the desired info for it
    time += minor_interval;
}
0 голосов
/ 28 августа 2011

Причина, по которой ваш метод допуска не работает, заключается в том, что он учитывает только то, что сохраненное значение немного выше, чем должно быть. (например, когда 1,0001 вместо 1. Если 0,999 вместо 1, ваш метод не будет работать)

Чтобы это исправить, проверьте, находится ли он в пределах допуска на или стороне ожидаемого значения.

Итак, вместо:

if (m == 0)

использование:

if (m < small_value || major_interval - m < small_value)

где small_value - это что-то вроде 1e-8.

...