Я пытаюсь сопоставить один диапазон десятичных чисел с другим. В приведенном ниже примере диапазон 0,0 -> 2,0 сопоставлен с 0,0 -> 0,8. Кажется, я не могу заставить выходной диапазон когда-либо заканчиваться на 0,8 - он останавливается на 0,722. Проблема, я думаю, в том, как вычисляется масштабная переменная, но я не уверен, как это исправить. Может ли кто-нибудь увидеть, где я ошибаюсь?
function myscale (num, in_min, in_max, out_min, out_max, factor)
{
// number map
var scale = Math.max(0.0, num - in_min) / (in_max - in_min);
// calculate easing curve
var r = out_min + (Math.pow(scale, factor) * (out_max - out_min));
// 64-bit floating point representation fix
r = parseFloat(r.toFixed(10));
// return mapped scale number
return r;
}
var text = "";
var i;
for (i = 0.0; i <= 2.0; i = i + 0.1)
{
text += myscale(i, 0.0, 2.0, 0.0, 0.8, 2) + "<br />";
}
document.getElementById("demo").innerHTML = text;
<!DOCTYPE html>
<html>
<body>
<b>Numbers mapped from 0 to 0.8</b>
<p id = "demo"></p>
</body>
</html>
Вы можете использовать другой подход и выполнять итерацию до тех пор, пока значение не станет меньше требуемого. Наконец, возьмите большое значение и вызовите функцию с этим значением вне цикла for
со значением без наших ошибок с плавающей запятой при сложении чисел.
function myscale (num, in_min, in_max, out_min, out_max, factor) {
// number map
var scale = Math.max(0.0, num - in_min) / (in_max - in_min);
// calculate easing curve
var r = out_min + (Math.pow(scale, factor) * (out_max - out_min));
// 64-bit floating point representation fix
r = parseFloat(r.toFixed(10));
// return mapped scale number
return r;
}
var text = "";
var i;
for (i = 0; i < 2; i += 0.1) {
text += myscale(i, 0, 2, 0, 0.8, 2) + "<br />";
}
text += myscale(2, 0, 2, 0, 0.8, 2) + "<br />";
document.getElementById("demo").innerHTML = text;
<b>Numbers mapped from 0 to 0.8</b>
<p id = "demo"></p>
@EricPostpischil, вы правы. ich изменяет цикл for и использует окончательное значение как единственный вызов.
@NinaScholz ценит ответ, и он решает его, но (во многом в соответствии с комментариями Эрика) он действительно похож на исправление смены после того, как что-то пошло не так.
@EricPostpischil есть возможность на примере реализации вашего решения?
@garrettlynch: Я дал ответ.
@NinaScholz: новый код также не является общим решением, поскольку повторное добавление приращения может дать значение, которое очень близко, но меньше конечного значения, например 1,99999999999999997779553950749686919152736663818359375. Тогда будет два выполнения подпрограммы со значением, близким к конечному значению, одно в цикле и дополнительное после него. Я уже сформулировал правильное решение: используйте целые числа.
@garrettlynch: Нет, когда я написал «Я предоставил ответ», я буквально набрал ответ с кодом в форме ответа StackOverflow и отправил его. Обновите страницу.
@EricPostpischil, по крайней мере, вам нужно получить число с плавающей запятой, даже взяв целочисленный счетчик. если окончательное значение не достигает желаемого результата, myscale
можно изменить, чтобы принять большое число (бесконечность) для получения максимального предлагаемого желаемого значения.
@NinaScholz: Я предоставил полное решение. делает преобразует целочисленный счетчик в желаемое значение с плавающей запятой. Менять myscale
не нужно, и это было бы неуместно.
Нецелочисленные значения не должны использоваться для управления циклом, если вы правильно не создали цикл для арифметики с плавающей запятой. Как правило, управление циклом проще реализовать с помощью целочисленной арифметики. (Когда в управлении циклом используются нецелочисленные значения, ошибки округления с плавающей запятой могут привести к тому, что значение итератора будет немного выше или ниже конечного значения в итерации, хотя в идеале оно должно быть равно конечному значению. Это позволяет контролировать, на какой именно итерации выполняется петля заканчивается на сложном.)
Для случая в вопросе, где мы хотим выполнить итерацию с точностью до одной десятой, простое решение - масштабировать на 10, поэтому 0, 2,0 и 0,1 становятся 0, 20 и 1:
for (var ProxyI = 0; ProxyI <= 20; ProxyI = ProxyI += 1)
{
var RealI = ProxyI / 10.0;
text += myscale(RealI, 0.0, 2.0, 0.0, 0.8, 2) + "<br />";
}
В общем, если мы хотим выполнить итерацию от Start
к End
включительно по Increment
, где Increment
в идеале равномерно делит расстояние от Start
до End
, но арифметика с плавающей запятой мешает, тогда мы можем использовать:
var NumberOfIntervals = Math.round((End - Start) / Interval);
for (var ProxyI = 0; ProxyI <= NumberOfIntervals; ProxyI = ProxyI + 1)
{
var RealI = Start + I / NumberOfIntervals * (End - Start);
…
}
Дизайн здесь таков, что NumberOfIntervals
установлен как целое число интервалов, которые мы ожидаем пройти через итерацию. Затем с ProxyI
используется целочисленная арифметика, увеличиваясь на единицу для подсчета интервалов. Эта целочисленная арифметика не имеет ошибок округления, поэтому ProxyI
правильно считает интервалы. Затем внутри цикла счетчик ProxyI
масштабируется и переводится в соответствующую точку в интервале от Start
до End
. Эта арифметика будет иметь некоторые ошибки округления, поэтому RealI
часто будет не совсем идеальным числом, но будет близким. Ошибки округления повлияют только на значение RealI
; они не повлияют на счетчик циклов ProxyI
. Таким образом, цикл считается правильно. (Получить точное число, как правило, невозможно, поскольку оно не может быть представлено в формате с плавающей запятой.)
Этот дизайн решает проблему ошибок округления в итераторе, из-за чего оно может быть немного выше или ниже конечного значения, но дает дополнительное преимущество, заключающееся в предотвращении сложных ошибок округления при множестве добавлений. Ошибки округления ограничиваются несколькими операциями в Start + I / NumberOfIntervals * (End - Start)
.
(Примечание: я почти никогда не пишу на JavaScript, поэтому я не могу гарантировать, что приведенный выше код является правильным JavaScript. Также обратите внимание, что окончательное значение RealI
, вычисленное выше, может не быть в точности End
, потому что End - Start + Start
в арифметике с плавающей запятой не обязательно дает End
.)
В общем, мы не можем ожидать, что окончательный
i
вfor (i = start; i <= end; i += increment)
будет в пределах одного эпсилона (относительноend
, то есть масштабируется с помощьюend
или наименьшей степени из двух, не превышающей его), даже еслиend
-start
кратенincrement
, потому что есть несколько ошибок округления, по одной в каждом добавленииincrement
, и нет никаких оснований ожидать, что они в сумме дадут только один эпсилон. Так что это неправильное решение. Правильным решением является итерация с целыми числами или другими значениями, которые имеют точное представление, а затем масштабирование переменной итерации до желаемого интервала.