【问题标题】:Double variable as loop counter双变量作为循环计数器
【发布时间】:2015-07-30 11:55:27
【问题描述】:

我经常需要编码explicit schemes,这意味着我必须通过增加时间变量t <- t+ dt 来查看函数的演变。因此,我的循环在dt 上增加是很自然的:

int N = 7;
double t=0., T = 1., dt=T/N; // or I could have dt=0.03 for example
for(; t<T; t+= dt){
   if(T - t < dt){
      dt = T-t;
   }
   //some functions that use dt, t, T etc
} 

这背后的基本原理是,我在每一步都将t 增加一个常量dt,除非在最后一次迭代中,如果我当前的时间t 是这样的T- dt &lt; t &lt; T,那么我修改我的时间增量dt &lt;- T-t

这种程序可能存在哪些缺陷或我可以改进它的方法是什么?我确实意识到我可能会得到一个非常小的时间增量。

是否会出现任何浮动问题(我是否应该坚持整数递增)?

在优化方面,我认为这种技术并不昂贵,因为基本的分支预测几乎总是会跳过 if 块。

编辑

我意识到我的问题不是很好。通常dtCFL condition 给出,也就是说,它与其他一些参数相比足够小。

所以从逻辑上看,dt是先给定的,然后我们可以定义一个整数N=floor(T/dt),用整数循环到N,然后处理剩余的时间间隔N*dt --- T

代码是:

double dt = //given by some function;
double t=0., T = 1.;
for(; t<T; t+= dt){
   if(T - t < dt){
      dt = T-t;
   }
   //some functions that use dt, t, T etc
} 

【问题讨论】:

  • 双重和浮动比较并不容易。看看this
  • @LeFlou 我认为这个问题试图确定两个双打何时相等或有点相等。就我而言,我只是确定哪个更大。确实,我认为在操作dt=T-t;t += dt 之后,t &lt; T 是错误的。
  • 如果T-t 非常小,在舍入误差范围内,您可能希望跳过最后一次迭代。它可能不会很有意义。

标签: c++ for-loop floating-point


【解决方案1】:

首先,不需要补偿if (T - t &lt; dt),因为它的唯一目的似乎是将最后一个值设置为t == T,由于for循环条件中的...;t &lt; T;...不相等,因此不会被处理。

话虽如此,除非N 是2 的幂,否则有限差分法不能很好地处理浮点数。如果例如如果希望以 0.1f 的步长评估函数,则很可能会漏掉几个积分点。

分支预测可能会跳过条件评估,但在将浮点运算与流控制运算混合时也可能存在惩罚/延迟。

由于累积的舍入误差,优化器可能无法轻松确定迭代计数,从而不允许某些优化(循环展开甚至矢量化)。

可以简单地通过线性插值来减轻不准确性:t = c * dt;,但并不完美,因为并非适用于所有情况(dbl / N) * N == dbl。在实践中,误差应该在 epsilon 量级。要获得准确的结束值,这次必须计算 t = (range * N) / N;,确保 range * N 不会丢失最低有效位。

【讨论】:

  • 非常抱歉。我应该说dt 是由 CFL 条件提供给我们的,之后我们可能会想出一个N 来处理时间间隔N*dt ____ T。我们很少有T/dt 是无限精度的整数。而且由于 CFL 已经具有限制性,因此选择更小的 dt(比 CFL 给出的那个)太不利了,这将导致 T/dt 是无限精度的 2 的幂。再次非常抱歉,我应该强调重要的部分。
【解决方案2】:

有了dt 必须设置为固定的预定值的新信息 (至少除了最后一步),这是我的建议:

double T0 = 0.0;
double T  = 1.0;
int    N  = floor((T - T0)/dt);
double t  = T0;
for (int step_number = 0; step_number < N; ++step_number, t += dt)
{
  t = T0 + step_number * dt;
  do_one_step(t, T, dt);
}
if (t < T)
{
  do_one_step(t, T, T - t);
}

函数do_one_step 使用以下函数执行必要的计算 tTdt 用于每次迭代。 必须由函数更新的数据可以成为成员 类的变量或可以包含在函数参数中 列为非常量引用。

顺便说一句,我最后一次调用循环外的函数不在 为了节省分支条件的可能成本,但是因为 我发现这样的代码组织得更好,也更容易理解。


旧答案:

正如你所说,最后你可以很容易地得到一个非常小的时间增量, 因为结果T/N 通常不准确 (而0.03 肯定不准确)。

我更愿意像这样开发tdt

int    N  = 7;
double t  = 0.0;
double T  = 1.0;
double dt = (T - t)/N;
for (int step_number = 0; step_number < N; ++step_number, t += dt)
{
  // ... calculations with t, T, dt, etc.
}

(请注意,这表示dt = (T - t)/N,以防您决定以非零值t 开始迭代。)

如果N 非常大,则可能会更准确 (因为一旦t 变得更大,t += dt 就必须对dt 进行四舍五入):

int    N  = 7;
double T  = 1.0;
double dt = T/N;
for (int step_number = 0; step_number < N; ++step_number)
{
  double t = T0 + step_number * dt;
  // ... calculations with t, T, dt, etc.
}

【讨论】:

  • 我很抱歉。我不知道我在想什么,但是 dt 是首先定义的,然后我们可以想出一个 N 在这种情况下很重要。首先我们想到一个dt,然后我们想出足够多的迭代次数就足够了。我让我们看起来好像在一开始就给了N,但事实并非如此。再次抱歉。
  • 循环中的t+=dt 看起来像一个工件:P。你还提到它必须四舍五入。你能解释一下那个说法吗?在什么条件下会发生这种情况?我的推理:如果说t 的顺序为 1,而 dt 的顺序为 1e-10,那么在 dt 中仍然会留下大约 5 个有效数字。这也意味着我们正在执行大约 10^10 迭代,这是一个很大的数字,比 uint N 中可以存储的要大。
  • @user3371583 舍入(如果有)在您描述的数量级上。如果您不关心t 的最后几位数字(并且很可能您可以不关心),那么您可以不做一些“稍微更准确”的事情。另一方面,第二种方法允许整数循环计数器没有t+=dt;我也不特别喜欢++step, t+=dt(尽管它是一种常见的编程实践;artifact 将是程序的不正确输出)。
猜你喜欢
  • 2015-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-27
  • 2020-12-09
相关资源
最近更新 更多