12

私はDelphiを初めて使用します(約6か月間プログラミングを行っています)。これまでのところ、これは非常に苛立たしい経験でした。そのほとんどは、Delphiが日付と時刻を処理するのがいかに悪いかによるものです。TDateとTTimeの使い方がわからないので、悪いと思うかもしれませんが、わかりません。これが今私に起こっていることです:

// This shows 570, as expected
ShowMessage(IntToStr(MinutesBetween(StrToTime('8:00'), StrToTime('17:30'))));

// Here I would expect 630, but instead 629 is displayed. WTF!?
ShowMessage(IntToStr(MinutesBetween(StrToTime('7:00'), StrToTime('17:30'))));

これは私が使用している正確なコードではありません。すべてが変数にあり、別のコンテキストで使用されていますが、問題はわかると思います。なぜその計算が間違っているのですか?この問題をどのように回避すると思いますか?

4

2 に答える 2

20

与えられた

a := StrToTime('7:00');
b := StrToTime('17:30');

ShowMessage(FloatToStr(a));
ShowMessage(FloatToStr(b));

を使用したコードはMinutesBetween、効果的にこれを行います。

ShowMessage(IntToStr(trunc(MinuteSpan(a, b)))); // Gives 629

ただし、次のように丸める方がよい場合があります。

ShowMessage(IntToStr(round(MinuteSpan(a, b)))); // Gives 630

実際の浮動小数点値は何ですか?

ShowMessage(FloatToStr(MinuteSpan(a, b))); // Gives 630

したがって、ここでは明らかに従来の浮動小数点の問題に苦しんでいます。

アップデート:

の主な利点はRound、分のスパンが整数に非常に近い場合、丸められた値がその整数であることが保証され、切り捨てられた値が前の整数である可能性が非常に高いことです。

の主な利点Truncは、実際にこの種のロジックが必要になる可能性があることです。実際、5 日以内に 18 歳になった場合、法的にはスウェーデンの運転免許証を申請することはまだ許可されていません。

Roundの代わりに使用したい場合はTrunc、追加するだけです

function MinutesBetween(const ANow, AThen: TDateTime): Int64;
begin
  Result := Round(MinuteSpan(ANow, AThen));
end;

あなたのユニットに。次に、識別子MinutesBetweenは、同じユニット内の の代わりに、この識別子を参照しDateUtilsます。一般的なルールは、コンパイラが最後に見つけた関数を使用することです。したがって、たとえば、この関数を自分の unit の上に置くDateUtilsFixと、

implementation

uses DateUtils, DateUtilsFix

はの右側にあるMinutesBetweenため、新しい を使用します。DateUtilsFixDateUtils

更新 2:

別のもっともらしいアプローチは

function MinutesBetween(const ANow, AThen: TDateTime): Int64;
var
  spn: double;
begin
  spn := MinuteSpan(ANow, AThen);
  if SameValue(spn, round(spn)) then
    result := round(spn)
  else
    result := trunc(spn);
end;

これround(spn)は、スパンが整数のファズ範囲内にある場合に返され、trunc(spn)そうでない場合に返されます。

たとえば、このアプローチを使用して

07:00:00 and 07:00:58

trunc元のをベースにしたバージョンと同様に、スウェーデンの Trafikverket が望んでいるように、0 分になります。しかし、OPの質問を引き起こした問題に悩まされることはありません。

于 2013-02-22T19:28:58.547 に答える
8

これは、最新バージョンの Delphi で解決された問題です。そのため、アップグレードするか、単に Delphi 2010 の新しいコードを使用することができます。たとえば、次のプログラムは期待どおりの出力を生成します。

{$APPTYPE CONSOLE}
uses
  SysUtils, DateUtils;

function DateTimeToMilliseconds(const ADateTime: TDateTime): Int64;
var
  LTimeStamp: TTimeStamp;
begin
  LTimeStamp := DateTimeToTimeStamp(ADateTime);
  Result := LTimeStamp.Date;
  Result := (Result * MSecsPerDay) + LTimeStamp.Time;
end;

function MinutesBetween(const ANow, AThen: TDateTime): Int64;
begin
  Result := Abs(DateTimeToMilliseconds(ANow) - DateTimeToMilliseconds(AThen))
    div (MSecsPerSec * SecsPerMin);
end;

begin
  Writeln(IntToStr(MinutesBetween(StrToTime('7:00'), StrToTime('17:30'))));
  Readln;
end.

Delphi 2010 のコードはMinutesBetween次のようになります。

function SpanOfNowAndThen(const ANow, AThen: TDateTime): TDateTime;
begin
  if ANow < AThen then
    Result := AThen - ANow
  else
    Result := ANow - AThen;
end;

function MinuteSpan(const ANow, AThen: TDateTime): Double;
begin
  Result := MinsPerDay * SpanOfNowAndThen(ANow, AThen);
end;

function MinutesBetween(const ANow, AThen: TDateTime): Int64;
begin
  Result := Trunc(MinuteSpan(ANow, AThen));
end;

したがって、MinutesBetween事実上、2 つの日付/時刻値の浮動小数点減算になります。浮動小数点演算には固有の不正確さがあるため、この減算により、真の値よりわずかに高い値または低い値が得られる場合があります。真の値を下回っている場合は、 を使用するTruncと、前の分までさかのぼります。に置き換えるだけTruncRound問題が解決します。


たまたま最新の Delphi バージョンでは、日付/時刻の計算が完全にオーバーホールされています。に大きな変更がありますDateUtils。分析するのは少し難しいですが、新しいバージョンは に依存していDateTimeToTimeStampます。これにより、値の時間部分が午前 0 時からのミリ秒数に変換されます。そして、それは次のようになります:

function DateTimeToTimeStamp(DateTime: TDateTime): TTimeStamp;
var
  LTemp, LTemp2: Int64;
begin
  LTemp := Round(DateTime * FMSecsPerDay);
  LTemp2 := (LTemp div IMSecsPerDay);
  Result.Date := DateDelta + LTemp2;
  Result.Time := Abs(LTemp) mod IMSecsPerDay;
end;

の使用に注意してくださいRoundRoundむしろの使用は、最新の Delphi コードが堅牢な方法でTrunc処理する理由です。MinutesBetween


今すぐアップグレードできないと仮定すると、次のような問題に対処します。

  1. コードは変更しないでください。通話MinutesBetweenなどを続けます。
  2. アップグレードすると、コードMinutesBetweenなどを呼び出すコードが機能するようになります。
  3. とりあえずコードフックMinutesBetweenなどで修正。アップグレードするときは、フックを簡単に削除できます。
于 2013-02-22T19:36:11.533 に答える