【问题标题】:Mapping one number range to another将一个数字范围映射到另一个
【发布时间】:2018-10-28 18:04:15
【问题描述】:

我正在尝试将一个十进制数字范围映射到另一个。在下面的示例中,范围 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>

【问题讨论】:

    标签: javascript floating-point


    【解决方案1】:

    您可以采用不同的方法并进行迭代,直到值小于所需值。最后取最大的值并在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>

    【讨论】:

    • 一般情况下,我们不能指望for (i = start; i &lt;= end; i += increment) 中的最终i 在一个epsilon 以内(相对于end,即按end 缩放或2 的最小幂大于它)即使endstartincrement 的倍数,因为有多个舍入误差,increment 的每一个加法都有一个,并且没有理由期望它们加起来只有一个ε。所以这不是一个合适的解决方案。一个合适的解决方案是使用整数或其他具有精确表示的值进行迭代,然后将迭代变量缩放到所需的间隔。
    • @EricPostpischil,你是对的。 ich 更改了 for 循环并将最终值用作单个调用。
    • @NinaScholz 很欣赏这个答案,它确实解决了这个问题,但是(很像 Eric 的 cmets)它确实感觉像是在出现问题后进行临时修复。
    • @EricPostpischil 有关如何实施您的解决方案的示例的可能性吗?
    • @garrettlynch:我已经提供了答案。
    【解决方案2】:

    除非您为浮点运算正确设计了循环,否则不应将非整数值用于循环控制。一般来说,使用整数运算更容易实现循环控制。 (当循环控制中使用非整数值时,浮点舍入误差可能会导致迭代器值略高于或低于迭代中的结束值,而理想情况下它会等于结束值。这使得精确控制哪个迭代循环以困难结束。)

    对于问题中的情况,我们想要迭代十分之一,一个简单的解决方案是缩放 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 理想情况下将StartEnd 的距离等分,但浮点运算会干扰,那么我们可以使用:

    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 被缩放并转换到从StartEnd 的区间中的对应点。这个算术会有一些舍入误差,所以RealI 通常不会完全是理想的数字,但它会很接近。舍入误差只会影响RealI 的值;它们不会影响循环计数器ProxyI。所以循环计数正确。 (获得准确的数字通常是不可能的,因为它无法以浮点格式表示。)

    这种设计解决了迭代器中的舍入错误导致它略高于或低于最终值的问题,但它提供了额外的好处,即避免多次加法时复合舍入错误。舍入误差仅限于Start + I / NumberOfIntervals * (End - Start) 中的少数操作。

    (注意:我几乎从不写 JavaScript,所以我不能保证上面的代码是正确的 JavaScript。还要注意上面计算的 RealI 的最终值可能不完全是 End,因为 End - Start + Start 在浮点运算不一定会产生End。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-03
      • 1970-01-01
      • 2021-10-11
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多