Какой метод работает точно так же, как Math.floorMod (), но с плавающей точкой вместо целых? - PullRequest
3 голосов
/ 17 марта 2019

Вот, например, набор входов и выходов для Math.floorMod(x, 5).

int x;

inputs:
x = -15 | -14 | -13 | -12 | -11
    -10 | -09 | -08 | -07 | -06
    -05 | -04 | -03 | -02 | -01
    +00 | +01 | +02 | +03 | +04*
    +05 | +06 | +07 | +08 | +09
    +10 | +11 | +12 | +13 | +14

outputs:
   *+00 | +01 | +02 | +03 | +04

Для ясности, все входы в столбце 1 приводят к выходу в столбце1 и т. Д.

Я тоже хочу сделать это с помощью поплавков, но я не смог найти Math.relevantMethod(), чтобы помочь мне в этом.Идея состоит в том, что все поплавки должны отображаться на промежуток между 0 и y (второй аргумент) в стиле Pac-Man.

Ответы [ 2 ]

3 голосов
/ 17 марта 2019

Решение

Мне потребовалось некоторое время, чтобы разработать алгоритм и проработать все изгибы, но вот оно.Я дублирую это floatMod().

double floatMod(double x, double y){
    // x mod y behaving the same way as Math.floorMod but with doubles
    return (x - Math.floor(x/y) * y);
}

Вот набор входов и выходов для floatMod (x, 2.0d) в качестве примера.(Я исправил небольшие ошибки округления, чтобы создать аккуратную таблицу.)

double x;

inputs:
x = -4.0 | -3.6 | -3.2 | -2.8 | -2.4
    -2.0 | -1.6 | -1.2 | -0.8 | -0.4
    +0.0 | +0.4 | +0.8 | +1.2 | +1.6*
    +2.0 | +2.4 | +2.8 | +3.2 | +3.6
    +4.0 | +4.4 | +4.8 | +5.2 | +5.6

outputs:
   *+0.0 | +0.4 | +0.8 | +1.2 | +1.6

Совет: Чтобы уменьшить ошибки округления, убедитесь, что вы вводите удвоения в floatMod(),Ввод литерала 0.8 эквивалентен вводу (double)0.8f, который менее точен, чем 0.8d.Вы получите более точный результат, даже если вы разыгрываете вывод как число с плавающей точкой.Также обратите внимание, что чем дальше от 0.0 x, тем больше будут ошибки округления.Это как поплавки работают.Итак ...

floatMod(0.1f, 1f);     //returns: 0.1
floatMod(1.1f, 1f);     //returns: 0.100000024 aka 0.1 + 0.000000024
floatMod(2.1f, 1f);     //returns: 0.099999905 aka 0.1 - 0.000000095
floatMod(10000.1f, 1f); //returns: 0.099609375 aka 0.1 - 0.000390625

floatMod(0.1d, 1d);     //returns: 0.1
floatMod(1.1d, 1d);     //returns: 0.10000000000000009 aka 0.1 + 0.00000000000000009
floatMod(2.1d, 1d);     //returns: 0.10000000000000009 aka 0.1 + 0.00000000000000009
floatMod(10000.1d, 1d); //returns: 0.10000000000036380 aka 0.1 - 0.00000000000036380

Объяснение алгоритма

Если вам интересно, как работает алгоритм x - Math.floor(x/y) * y, я постараюсь объяснить все возможное.Давайте рассмотрим пример floatMod(x, 2.0d) сверху.

Сначала , возьмем эту числовую строку возможных значений для x:

                                ●------------------------○
       |                        |                        |                        |
-2.4 -2.0 -1.6 -1.2 -0.8 -0.4 +0.0 +0.4 +0.8 +1.2 +1.6 +2.0 +2.4 +2.8 +3.2 +3.6 +4.0 +4.4

Пробелы между вертикальными линиями представляют кускидлины у сложены бок о бок в обоих направлениях.Заштрихованный кружок означает включающий, в то время как полый кружок означает исключительный, а показанные выше охватывают фрагмент 0, представленный пунктирной линией.

Далее , x/y (уcase) занимает заданную позицию на числовой линии x и дает ее в терминах кусков.Итак, 2.0 - это конец фрагмента 0 и начало фрагмента 1, поэтому 2.0 / y = 1.0.

Мы скажем x / y = c;

1.0 / y → 0.5c как 1.0 - это половина фрагмента
3.2 / y → 1.6c
-2.4 / y → -1.2c
и т. д.

Next , Math.floor(c) означаетв какой бы части мы ни находились, уменьшите c до начала указанной части.Другими словами, какой из них равен x в?

0.5c → 0.0c
1.6c → 1.0c
-1.2c → -2.0c

Далее , он умножает результат на y еще раз, чтобы получить его обратно в виде х.

0.0c * y → 0.0
1.0c * y → 2.0
-2.0c * y → -4.0

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

Еще один способ взглянуть на это: нужно вычесть дополнительные куски в x, чтобы выяснить, сколько кусков вперед или назад x от фрагмента 0, и удаляет это количество.Это удерживает его в пределах 0 и y.

1,0 - 0,0 → 1,0
3,2 - 2,0 → 1,2
-2,4 - -4,0 → 1,6


(Э-э-э ... Итак, после написания большей части объяснения алгоритма, я понял, что есть способ упростить его. После этого я понял, что он на самом деле точно такой же, как алгоритм floorMod, только с плавающей точкой.Я здесь, действуя как некий ученый, открывший единую теорию всего, когда все, что я делал, делало один дополнительный шаг из того, что было прямо у меня под носом. Я обещаю, что я ломал голову, чтобы развить это с нуля.

Первоначальный алгоритм, который у меня был, был -Math.floor(x/y) * y + x, который в какой-то момент стал довольно запутанным, чтобы объяснить. Я все еще рад, что написал это, хотя, как мне кажется, это хорошая информация, и это было очень весело.)

0 голосов
/ 17 марта 2019

Одна из проблем с использованием esp с плавающей точкой для модуля состоит в том, что вы видите значительные ошибки представления.Лучше всего округлять результаты или расчеты, чтобы получить правильные результаты.Простой способ сделать это - предположить, что вам когда-либо понадобится только N цифр точности, например 6.

public static double floorMod(double x, double y) {
    return Math.floorMod(Math.round(x * 1e6), Math.round(y * 1e6)) / 1e6;
}

. Для приведенных выше примеров вы получите

floorMod(0.1f, 1f);     //returns: 0.1
floorMod(1.1f, 1f);     //returns: 0.1
floorMod(2.1f, 1f);     //returns: 0.1
floorMod(10000.1f, 1f); //returns: 0.099609 due to the limits of float.

floorMod(0.1d, 1d);     //returns: 0.1
floorMod(1.1d, 1d);     //returns: 0.1
floorMod(2.1d, 1d);     //returns: 0.1
floorMod(10000.1d, 1d); //returns: 0.1

Другой подход - указатьточность

public static double floorMod(double x, double y, double precision) {
    double factor = Math.round(1 / precision);
    return Math.floorMod(Math.round(x * factor), Math.round(y * factor)) / factor;
}

floorMod(10000.1f, 1f, 0.1); // returns 0.1
...