Отображение одного диапазона номеров в другой

1

Я пытаюсь сопоставить диапазон десятичных чисел с другим. В приведенном ниже примере диапазон 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>
Теги:
floating-point

2 ответа

1

Вы можете использовать другой подход и итерации до тех пор, пока значение будет меньше требуемых значений. Наконец, возьмите значение 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>
  • 0
    В общем, мы не можем ожидать, что окончательное значение i в for (i = start; i <= end; i += increment) будет в пределах одного эпсилона (относительно end , то есть масштабируется по end или наименьшей степени двух не больше чем это), даже если end start кратен increment , потому что есть несколько ошибок округления, по одной на каждое добавление increment , и нет никаких оснований ожидать, что они складываются только в один эпсилон. Так что это не правильное решение. Правильным решением является итерация с целыми числами или другими значениями, которые имеют точное представление, а затем масштабирование переменной итерации до желаемого интервала.
  • 0
    @EricPostpischil, ты прав. ich меняет цикл for и использует окончательное значение как одиночный вызов.
Показать ещё 7 комментариев
0

Нецелые значения не должны использоваться для управления контуром, если вы правильно не создали цикл для арифметики с плавающей запятой. Как правило, проще реализовать управление контуром с целочисленной арифметикой. (Если в управлении циклом используются нецелые значения, ошибки округления с плавающей запятой могут привести к тому, что значение итератора будет немного выше или ниже конечного значения в итерации, когда в идеале оно будет равно конечному значению. Это позволяет точно контролировать, какая итерация петля заканчивается на трудной.)

Для случая в вопросе, где мы хотим итерации на одну десятую, простое решение состоит в масштабировании на 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.)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню