Решение
Мне потребовалось некоторое время, чтобы разработать алгоритм и проработать все изгибы, но вот оно.Я дублирую это 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
, который в какой-то момент стал довольно запутанным, чтобы объяснить. Я все еще рад, что написал это, хотя, как мне кажется, это хорошая информация, и это было очень весело.)