Сопоставление одного диапазона номеров с другим

Я пытаюсь сопоставить один диапазон десятичных чисел с другим. В приведенном ниже примере диапазон 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>
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
791
2

Ответы 2

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

В общем, мы не можем ожидать, что окончательный i в for (i = start; i <= end; i += increment) будет в пределах одного эпсилона (относительно end, то есть масштабируется с помощью end или наименьшей степени из двух, не превышающей его), даже если end-start кратен increment, потому что есть несколько ошибок округления, по одной в каждом добавлении increment, и нет никаких оснований ожидать, что они в сумме дадут только один эпсилон. Так что это неправильное решение. Правильным решением является итерация с целыми числами или другими значениями, которые имеют точное представление, а затем масштабирование переменной итерации до желаемого интервала.

Eric Postpischil 29.10.2018 00:39

@EricPostpischil, вы правы. ich изменяет цикл for и использует окончательное значение как единственный вызов.

Nina Scholz 29.10.2018 08:15

@NinaScholz ценит ответ, и он решает его, но (во многом в соответствии с комментариями Эрика) он действительно похож на исправление смены после того, как что-то пошло не так.

garrettlynchirl 29.10.2018 11:59

@EricPostpischil есть возможность на примере реализации вашего решения?

garrettlynchirl 29.10.2018 11:59

@garrettlynch: Я дал ответ.

Eric Postpischil 29.10.2018 12:36

@NinaScholz: новый код также не является общим решением, поскольку повторное добавление приращения может дать значение, которое очень близко, но меньше конечного значения, например 1,99999999999999997779553950749686919152736663818359375. Тогда будет два выполнения подпрограммы со значением, близким к конечному значению, одно в цикле и дополнительное после него. Я уже сформулировал правильное решение: используйте целые числа.

Eric Postpischil 29.10.2018 12:38

@garrettlynch: Нет, когда я написал «Я предоставил ответ», я буквально набрал ответ с кодом в форме ответа StackOverflow и отправил его. Обновите страницу.

Eric Postpischil 29.10.2018 12:45

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

Nina Scholz 29.10.2018 12:56

@NinaScholz: Я предоставил полное решение. делает преобразует целочисленный счетчик в желаемое значение с плавающей запятой. Менять myscale не нужно, и это было бы неуместно.

Eric Postpischil 29.10.2018 14:05

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

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

Другие вопросы по теме