1

继我关于分化的问题之后:

用 Delphi 对缓冲区进行微分

我现在正在考虑进行集成。我无法完全理解这一点。情况是我定期收到一个数据缓冲区,其中包含许多在时间上相隔固定距离的值。我需要区分它们。好久没在学校学微积分了……

我想出的是:

procedure IntegrateBuffer(ABuffer: TDoubleDynArray;
                                   var AOutBuffer: TDoubleDynArray;
                                   AVPS: integer);
const
  SumSum: double = 0.0;
  LastValue: double = NaN;
var
  i: integer;
  dt, aa, hl, hr: double;
begin
  // protect from divide by zero
  if (AVPS < 1) then exit;

  dt := 1 / AVPS;

  for i := 0 to high(ABuffer) do begin
    if (i = 0) then begin
      if (IsNaN(LastValue)) then begin
        hl := ABuffer[0];
        hr := ABuffer[0];
      end else begin
        hl := LastValue;
        hr := ABuffer[i];
      end;
    end else begin
      hl := ABuffer[i -1];
      hr := ABuffer[i];
    end;

    aa := 0.5 * dt * (hl + hr);
    SumSum := SumSum + aa;
    AOutBuffer[i] := SumSum;
  end;

  // remember the last value for next time
  LastValue := ABuffer[high(ABuffer)];
end;

我正在使用梯形规则, hl 和 hr 是梯形的左右高度。dt 是基础。

AVPS 是每秒的值。一个典型的值在 10 到 100 之间。缓冲区的长度通常是 500 到 1000 个值。

我用与前一个数据块连续的新数据一次又一次地调用缓冲区,因此下次保留块的最后一个值。

我所做的是否正确?即,它会正确整合这些价值观吗?

谢谢你。

4

1 回答 1

7

看起来您需要一些帮助来测试代码。正如评论中所讨论的,这里是一个非常简单的测试。

{$APPTYPE CONSOLE}

uses
  SysUtils, Math;

type
  TDoubleDynArray = array of Double;

var
  SumSum: double;
  LastValue: double;

procedure Clear;
begin
  SumSum := 0.0;
  LastValue := NaN;
end;

procedure IntegrateBuffer(
  ABuffer: TDoubleDynArray;
  var AOutBuffer: TDoubleDynArray;
  AVPS: integer
);
var
  i: integer;
  dt, aa, hl, hr: double;
begin
  // protect from divide by zero
  if (AVPS < 1) then exit;

  dt := 1 / AVPS;

  for i := 0 to high(ABuffer) do begin
    if (i = 0) then begin
      if (IsNaN(LastValue)) then begin
        hl := ABuffer[0];
        hr := ABuffer[0];
      end else begin
        hl := LastValue;
        hr := ABuffer[i];
      end;
    end else begin
      hl := ABuffer[i -1];
      hr := ABuffer[i];
    end;

    aa := 0.5 * dt * (hl + hr);
    SumSum := SumSum + aa;
    AOutBuffer[i] := SumSum;
  end;

  // remember the last value for next time
  LastValue := ABuffer[high(ABuffer)];
end;

var
  Buffer: TDoubleDynArray;
  OutBuffer: TDoubleDynArray;

begin
  // test y = 1 for a single call, expected output = 1, actual output = 2
  Clear;
  Buffer := TDoubleDynArray.Create(1.0, 1.0);
  SetLength(OutBuffer, Length(Buffer));
  IntegrateBuffer(Buffer, OutBuffer, 1);
  Writeln(OutBuffer[high(OutBuffer)]);

  Readln;
end.

我正在y(x) = 1[0..1] 范围内集成该功能。因此,预期输出为 1。但实际输出为 2。

那么,怎么了?您可以在调试器中解决它,但通过检查代码很容易看到。您正在对第一个样本求和一个三角形。什么时候IsNaN(LastValue)是真的,那么你不应该对积分做出贡献。此时,您还没有在 x 轴上覆盖任何距离。

所以要修复代码,让我们试试这个:

....
if (IsNaN(LastValue)) then begin
  hl := 0.0;//no contribution to sum
  hr := 0.0;
end else begin
  hl := LastValue;
  hr := ABuffer[i];
end;
....

这解决了问题。

现在让我们稍微扩展一下测试并测试y(x) = x

// test y = x, expected output = 12.5
Clear;
Buffer := TDoubleDynArray.Create(0.0, 1.0, 2.0, 3.0, 4.0, 5.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Writeln(OutBuffer[high(OutBuffer)]);

所以,看起来不错。

好的,多个调用呢:

// test y = x for multiple calls, expected output = 18
Clear;
Buffer := TDoubleDynArray.Create(0.0, 1.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Buffer := TDoubleDynArray.Create(2.0, 3.0, 4.0, 5.0, 6.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Writeln(OutBuffer[high(OutBuffer)]);

一次一个值怎么样?

// test y = x for multiple calls, one value at a time, expected 0.5
Clear;
Buffer := TDoubleDynArray.Create(0.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Buffer := TDoubleDynArray.Create(1.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Writeln(OutBuffer[high(OutBuffer)]);

传递一个空数组怎么样?

// test y = x for multiple calls, some empty arrays, expected 0.5
Clear;
Buffer := TDoubleDynArray.Create(0.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Buffer := nil;
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Buffer := TDoubleDynArray.Create(1.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Writeln(OutBuffer[high(OutBuffer)]);

呃,哦,访问冲突。如果缓冲区为空,则只需在开始时跳过该函数即可更好地保护它:

if (AVPS < 1) then exit;
if (Length(ABuffer) = 0) then exit;

好的,现在最后一个测试通过了

希望你现在明白了。我刚刚使用了基于 noddyWriteln的测试,但这并不能扩展。给自己一个单元测试框架(我推荐 DUnitX)并构建适当的测试用例。这也将迫使你考虑你的代码,以便它设计得很好。使代码可测试的通常意想不到的好处之一是它通常会导致界面设计得到改进。

对于您的下一个问题,我要求您提供带有测试代码的SSCCE!;-)


对代码的一些评论:

  1. const按或 按传递动态数组var。在您的情况下,您希望通过const.
  2. 不要使用可写类型常量。使用参数或其他一些更健全的状态管理。

同样,正如我在上一个问题中所说,编写测试来证明代码,并通过肉眼检查它。编写测试的关键是从你能想到的最简单的事情开始。如此简单的事情,你知道 100% 肯定答案。然后,一旦你让它工作,将测试扩展到更复杂的案例。

于 2013-10-04T10:19:00.907 回答