如何克服当前跳过循环内某个实数的数值算法的离散性?
How can I overcome discrete nature of a numerical algorithm that is currently skipping over a certain real number inside a loop?
我有一个相当复杂的算法来执行搜索,其中我使用 [0.25 到 1.75] 范围内的 $search
变量。
根据算法,当 $search
恰好为 1 时会发生 "interesting" 事情,因为它会遇到 有时 的变量配置(但不总是)最有利的。一些代码依赖于 $search
恰好为 1 来产生最有利的结果。
更具体地说,搜索范围内通常有一些特定值,这会产生最有利的结果,但我的算法布局方式通常会跳过该特定值。在这里,我举例说明了特定值(基于其他输入和配置)恰好是 1..
问题
从数学上讲,如果 $search
是连续的而不是离散的,我就不会有这个问题。我的问题是尝试使用离散数学收敛于最有利的变量配置。这里的问题是算法。需要注意的次要问题是浮点运算,但我认为这还不是这里的问题。
基本循环:
$maxPowerOut = 0 ;
for ($increment = 0; $increment <= 500; $increment ++)
{
//vars computed elsewhere, i.e:
//MIN = 0.24651533;
//STEP = 0.00196969
$search = MIN + STEP * $increment;
//compute several coefficients (returns an array)
$coeff = $this->coefficient($search);
//design is a complex library function
list($a, $b) = $this->design($coeff);
$powerOut = $a * $b;
//keep track of max power (and other params, not shown)
if ($powerOut > $maxPowerOut)
$maxPowerOut = $PowerOut;
}
//currently prints 899.993 instead of 900 as should be expected
print "Max Power is $maxPowerOut";
当然,$search
几乎永远不会是 1。它是这样的:
- 0.99569478115682
- 0.99866447159913
- 1.0016341620414
- 1.0046038524837
- 1.0075735429261
- ...
注意上面的循环是如何跳过 1 的。为了争论起见,我们假设最有利的位置发生在 1.003000。该值 (1.003000) 也将被跳过。
问题
我如何改进、重构、重新思考、重组、重写我的循环以避免此类问题?
浮点数的精度有限(它们很谨慎),预计会有偏差。
当前方向
数字1.0
似乎很重要,可能代表default
。重新编写代码以将 1.0
作为 $search
的一部分包含在内,将其作为同一循环的一部分或作为单独的迭代注入。
一个简单的改进可能是使用迭代方法:
在您当前的循环中,您搜索区间 [0.25, 1.75] 中的 500 个值。假设您可以通过这种方式将最优值缩小到更小的区间 [0.995, 1.007]。然后再次将这个间隔分成 500 个值并重复你的循环。重复直到达到所需的精度。
从数学上讲,您想在函数 f: search -> power
的给定区间内找到最大值,该函数计算给定 search
参数的某些 power
值。请注意,您的函数 f
越平滑,这通常越容易。要了解 f
可能是什么样子,您可以根据您在循环中计算的值绘制函数。
如果你的函数表现良好并且是单峰的(只有一个 "hump"),那么例如一个简单的 golden section search 将是有效的。
这里有一个快速的 JavaScript 片段/伪代码,可以帮助您解决问题。基本上,如果您发现增量/斜率已从正切换为负,您的函数应该递归调用自身。
function findMax(low, high) {
var maxOut = Number.MIN_VALUE;
// Calculate a step based on the low and high
// Using a power of 2 since the floating point numbers are represented by binary
var step = Math.abs((high - low) / 128);
// we'll be tracking the deltas of two test values
var prevDelta;
var delta;
// loop and check two values at a time
for(var i=low; i<=(high - step); i+=step) {
// coef ...
// design ...
// for testing
var out1 = Math.cos(i);
var out2 = Math.cos(i + step);
// update the max
if(out1 > maxOut) maxOut = out1;
if(out2 > maxOut) maxOut = out2;
// calc delta
delta = out2 - out1;
if(prevDelta !== undefined) {
// If one delta is going up and
// another is going down...
// Recursively call the function
if(prevDelta > 0 && delta < 0) {
var out3 = findMax(i - step, i + step);
// update the max
if(out3 > maxOut) maxOut = out3;
}
}
prevDelta = delta;
}
return maxOut;
}
alert(findMax(-0.5, 0.5)); // returns 1
这是 JSFiddle http://jsfiddle.net/hw5f2o1s/
如果最大值位于初始低点和低点 + 步长之间,则上述方法将不起作用,因为递归是通过达到峰值然后从峰值下降来触发的。如果发生这种情况,您可能必须通过增加两个除法 (high - low).
的幂来减小 step 变量
我有一个相当复杂的算法来执行搜索,其中我使用 [0.25 到 1.75] 范围内的 $search
变量。
根据算法,当 $search
恰好为 1 时会发生 "interesting" 事情,因为它会遇到 有时 的变量配置(但不总是)最有利的。一些代码依赖于 $search
恰好为 1 来产生最有利的结果。
更具体地说,搜索范围内通常有一些特定值,这会产生最有利的结果,但我的算法布局方式通常会跳过该特定值。在这里,我举例说明了特定值(基于其他输入和配置)恰好是 1..
问题
从数学上讲,如果 $search
是连续的而不是离散的,我就不会有这个问题。我的问题是尝试使用离散数学收敛于最有利的变量配置。这里的问题是算法。需要注意的次要问题是浮点运算,但我认为这还不是这里的问题。
基本循环:
$maxPowerOut = 0 ;
for ($increment = 0; $increment <= 500; $increment ++)
{
//vars computed elsewhere, i.e:
//MIN = 0.24651533;
//STEP = 0.00196969
$search = MIN + STEP * $increment;
//compute several coefficients (returns an array)
$coeff = $this->coefficient($search);
//design is a complex library function
list($a, $b) = $this->design($coeff);
$powerOut = $a * $b;
//keep track of max power (and other params, not shown)
if ($powerOut > $maxPowerOut)
$maxPowerOut = $PowerOut;
}
//currently prints 899.993 instead of 900 as should be expected
print "Max Power is $maxPowerOut";
当然,$search
几乎永远不会是 1。它是这样的:
- 0.99569478115682
- 0.99866447159913
- 1.0016341620414
- 1.0046038524837
- 1.0075735429261
- ...
注意上面的循环是如何跳过 1 的。为了争论起见,我们假设最有利的位置发生在 1.003000。该值 (1.003000) 也将被跳过。
问题
我如何改进、重构、重新思考、重组、重写我的循环以避免此类问题?
浮点数的精度有限(它们很谨慎),预计会有偏差。
当前方向
数字1.0
似乎很重要,可能代表default
。重新编写代码以将 1.0
作为 $search
的一部分包含在内,将其作为同一循环的一部分或作为单独的迭代注入。
一个简单的改进可能是使用迭代方法:
在您当前的循环中,您搜索区间 [0.25, 1.75] 中的 500 个值。假设您可以通过这种方式将最优值缩小到更小的区间 [0.995, 1.007]。然后再次将这个间隔分成 500 个值并重复你的循环。重复直到达到所需的精度。
从数学上讲,您想在函数 f: search -> power
的给定区间内找到最大值,该函数计算给定 search
参数的某些 power
值。请注意,您的函数 f
越平滑,这通常越容易。要了解 f
可能是什么样子,您可以根据您在循环中计算的值绘制函数。
如果你的函数表现良好并且是单峰的(只有一个 "hump"),那么例如一个简单的 golden section search 将是有效的。
这里有一个快速的 JavaScript 片段/伪代码,可以帮助您解决问题。基本上,如果您发现增量/斜率已从正切换为负,您的函数应该递归调用自身。
function findMax(low, high) {
var maxOut = Number.MIN_VALUE;
// Calculate a step based on the low and high
// Using a power of 2 since the floating point numbers are represented by binary
var step = Math.abs((high - low) / 128);
// we'll be tracking the deltas of two test values
var prevDelta;
var delta;
// loop and check two values at a time
for(var i=low; i<=(high - step); i+=step) {
// coef ...
// design ...
// for testing
var out1 = Math.cos(i);
var out2 = Math.cos(i + step);
// update the max
if(out1 > maxOut) maxOut = out1;
if(out2 > maxOut) maxOut = out2;
// calc delta
delta = out2 - out1;
if(prevDelta !== undefined) {
// If one delta is going up and
// another is going down...
// Recursively call the function
if(prevDelta > 0 && delta < 0) {
var out3 = findMax(i - step, i + step);
// update the max
if(out3 > maxOut) maxOut = out3;
}
}
prevDelta = delta;
}
return maxOut;
}
alert(findMax(-0.5, 0.5)); // returns 1
这是 JSFiddle http://jsfiddle.net/hw5f2o1s/
如果最大值位于初始低点和低点 + 步长之间,则上述方法将不起作用,因为递归是通过达到峰值然后从峰值下降来触发的。如果发生这种情况,您可能必须通过增加两个除法 (high - low).
的幂来减小 step 变量