【问题标题】:Map a value from one arbitrary range to another within the limits of the given data type在给定数据类型的范围内将一个值从一个任意范围映射到另一个范围
【发布时间】:2018-09-20 20:41:48
【问题描述】:

通常,您可以使用以下公式将值 input 从一个范围 [inputMin, inputMax] 转换为另一个范围 [outputMin, outputMax]

output = (input-inputMin) * ((outputMax-outputMin)/(inputMax-inputMin)) + outputMin

但是如果inputMax = double.MaxValueinputMin = double.MinValue 呢?然后inputMax - inputMin == double.PositiveInfinity 整个公式就落伍了。

有没有更好的方法在以下限制下进行这种转换?

  • 完全在数据类型的限制范围内
  • 适用于任意范围
  • 如果inputMax >= outputMaxinputMin <= outputMin

【问题讨论】:

  • 您可能会为此使用Microsoft's BigRational library。获取它的最简单方法是通过 NuGet。驴多年没有更新了,但它应该仍然可以正常工作。
  • 正如我所说,我想保持在给定的数据类型内。主要是出于性能原因,但也因为我只是对手头的问题感兴趣。
  • 您不能在不受该数据类型限制的情况下停留在该数据类型内。您要求任意精度,显然这将需要超过 double 的 64 位(或在某些情况下为 80 位)。
  • 我不需要任意精度。范围越来越小(检查限制),所以无论如何都会损失精度。因为范围越来越小,我很确定应该有解决问题的办法。
  • 单个比例因子是不够的。 inputMax-inputMin 可能很大,比如 2^1000 左右,而 outputMax-outputMin 可能很小,比如 2^-1000 左右。那么比例因子将在 2^-2000 左右,这太小而无法以 IEEE 64 位二进制浮点格式表示。因此,一个解决方案将涉及至少两个缩放。

标签: c# floating-point


【解决方案1】:

为避免inputMax - inputMin 溢出到无穷大,缩放input, inputMin, inputMax,全部按/2。除了一些小的值外,这应该是准确的:设置了最低有效位的亚法线。

x = input/2;  // scale `input` also
x0 = inputMin/2;
x1 = inputMax/2;
dx = x1 - x0;

@aka.nice 很好地确定了以下改进精度方法的限制。稍后再审核。


要保持精度很棘手,但以 x-interpret 方式形成 interpolation 对 FP 数学具有优势。主要思想不是从y-mx*b 形成一些小的y,因为它在乘法之后进行加法。任何加法/减法都容易因取消而严重损失精度。

y = (x - x_intercept)*slope

x-intercept x_intercept = (x1*y2 - x2*y1)/(y2 - y1) 有自己的一系列问题。如果可能,这应该在扩展数学中完成。除此之外,我们可以利用在现实生活中的应用,x_intercept 预计不会是极端的,因此在最重要的时候为我们节省了x - x_intercept 的一些精度(y 会很小)。

y0 = outputMin/2;
y1 = outputMax/2;
dy = y1 - y0;

// Avoid (y1*x0 - y0*x1)/dy to prevent overflow in the multiplications
x_intercept = y1/dy*x0 - y0/dy*x1;
y = (x - x_intercept)/dx*dy;
output = 2*y; // scale `output` at the end.

outputMin == outputMax 等特殊情况可以根据需要通过测试来处理。

这种方法类似于@aka.nice。然而,代码看起来不是中间的x,而是计算 x 截距。

【讨论】:

  • x_intercept 确实比 x_middle 更健壮 wrt 取消,但是当我们不使用扩展精度时,我会尝试展示一些棘手的情况......
  • 输入[-double.MaxValue,double.MaxValue] 并将其映射到[double.MaxValue/4*3,double.MaxValue],这将拒绝x_intercept 到负无穷大(溢出)
【解决方案2】:

你可以做的是围绕中间旋转:

inputMiddle = 0.5*inputMax + 0.5*inputMin

然后,通过输入的中间范围:

inputMidRange = 0.5*inputsMax - 0.5*inputMin

计算输入距离中间有多远(在 -1 和 1 之间):

howFar = (input-inputMiddle) / inputMidRange

然后通过输出中频和输出中频,将这个类似地报告给输出

output = howFar * outputMidRange + outputMiddle

正如 Eric 所指出的,如果某些区间界限已经是次正规的,这可能会出现下溢问题,但即使降至最小次正规的两倍,这仍然可以工作。

编辑

上面的公式是关于溢出的答案,但在操作(input-inputMiddle)时仍然存在灾难性取消的问题。

例如,对于将[0,double.MaxValue] 映射到[0,double.MaxValue/2],所有“小”值都将投影到outputMin,尽管简单的input/2 操作会导致正确的输出...

【讨论】:

  • 不应该input也被缩放0.5否则input-inputMiddle看起来不对?
  • @chux,不,它不应该被缩放。它不能溢出。到中间的距离不能超过inputMidRange。它不能超过double.MaxValue,因为整个范围不能超过2*double.MaxValue。您可以检查inputMin -> howFar=-1 -> -outputMidRange+outputMiddle = outputMininputMiddle -> howFar = 0 -> outputMiddleinputMax -> howFar=1 -> +outputMidRange+outputMiddle->outputMax
  • NMDV,但建议的 input 缩放并不是为了防止溢出。保持规模是我的错误。我也试图确定为什么是 DV。
猜你喜欢
  • 2020-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 2021-01-16
  • 1970-01-01
相关资源
最近更新 更多