Я пытаюсь сопоставить диапазон десятичных чисел с другим. В приведенном ниже примере диапазон 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>
Вы можете использовать другой подход и итерации до тех пор, пока значение будет меньше требуемых значений. Наконец, возьмите значение greates и вызовите функцию с этим значением вне цикла 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>
Нецелые значения не должны использоваться для управления контуром, если вы правильно не создали цикл для арифметики с плавающей запятой. Как правило, проще реализовать управление контуром с целочисленной арифметикой. (Если в управлении циклом используются нецелые значения, ошибки округления с плавающей запятой могут привести к тому, что значение итератора будет немного выше или ниже конечного значения в итерации, когда в идеале оно будет равно конечному значению. Это позволяет точно контролировать, какая итерация петля заканчивается на трудной.)
Для случая в вопросе, где мы хотим итерации на одну десятую, простое решение состоит в масштабировании на 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
, и нет никаких оснований ожидать, что они складываются только в один эпсилон. Так что это не правильное решение. Правильным решением является итерация с целыми числами или другими значениями, которые имеют точное представление, а затем масштабирование переменной итерации до желаемого интервала.