Delphi Tips 
-----------------------------

キーワード:計算

>> Index

07/24 集合型変数の内部構造が知りたい/数値として処理したい。
07/10 浮動小数点数を整数に丸めるときの注意
09/19 Sqr()の結果が負になる!?
09/05 数値の2進数への変換で効率的な方法
02/11 和暦を西暦に直したい
02/08 time_t を TDateTime に変換する

最終更新: 7553 日前

0277  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/09/28 西坂良幸 rev 1.2
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2003/07/24 西坂良幸 編集
集合型変数の内部構造が知りたい/数値として処理したい。

集合型の変数は、フラッグの集まりです。
例えば、
type
  TDbit  = 0..25;
  TDbits = set of TDbit;

と定義し、
  Var D:TDbit;
  D := [1,3,4,6];  の場合

  内部的には,00000000 00000000 00000000 01011010B
  とビット表現されています               ~~~~~~~~~
  したがって、Integer(D)とキャスト可能で、この値は90です

集合で使うバイト数は次の式で計算します。
  
  ByteSize = (Max div 8) - (Min div 8) + 1
      ※ 最大255要素ですから、最大32バイト

Min と Max は集合の基本型の下限と上限です。
特定の要素 E が何バイト目かは次の式でわかります。

  ByteNumber = (E div 8) - (Min div 8)

そのバイト内で何ビット目かは次の式でわかります。

  BitNumber = E mod 8
     ※ E は要素の順序値を表します。

GetLogicalDrives関数は、 現在利用可能なディスクドライブを表すビットマスクを返しますので、これで、試してみましょう。返り値のビット位置0 (最下位ビット) はドライブAに対応します。

バイトサイズ → (25 div 8) - (0 div 8) + 1 = 4
で、4バイト、DWord型やLongint型が使えます。

TDBitsの最小値は → 0
   0 mod 8      → 0  ですから、そのまま対応できます

procedure TForm1.Button1Click(Sender: TObject);
var
  d: TDbit;
  drvs: TDbits;
  DName:string;
begin
  ListBox1.Items.Clear;
  // マシンのドライブをえる
  DWORD(drvs) := GetLogicalDrives;
  for d := Low(TDbit) to High(TDbit) do
  begin
    if d in drvs then
    begin
      DName := Chr(Integer('A') + d) + ':\';
      // ドライブタイプをえる
      case GetDriveType(PChar(Char(Ord('A')+d)+':\')) of
      DRIVE_REMOVABLE: DName := DName + 'リマーブルデスク';
      DRIVE_FIXED: DName := DName + 'ハードデスク';
      DRIVE_REMOTE: DName := DName + 'ネットワークデスク';
      DRIVE_CDROM: DName := DName + 'CDROMデスク';
      DRIVE_RAMDISK: DName := DName + 'ラムデスク';
      end;
      ListBox1.Items.Add(DName);
    end;
  end;
end;

次に、
Type
  TDrive  = 'A'..'Z';
  TDrives = Set of TDrive;
を使えばどうでしょうか。

TDrivesの最小値は → 'A'  順序値 65
   65 mod 8      → 1  ですから、
返り値の0ビットは、TDravesの1ビット目に対応します。

procedure TForm1.Button1Click(Sender: TObject);
var
  d: TDrive;
  drvs: TDrives;
  DName:string;
begin
  ListBox1.Items.Clear;
  // マシンのドライブをえる−−左に1ビットシフトする
  DWORD(drvs) := GetLogicalDrives shl 1;
  for d := Low(TDrive) to High(TDrive) do
  begin
    if d in drvs then
    begin
      DName := Char(d) + ':\';
      // ドライブタイプをえる
      case GetDriveType(PChar(d +':\')) of
      DRIVE_REMOVABLE: DName := DName + 'リマーブルデスク';
      DRIVE_FIXED: DName := DName + 'ハードデスク';
      DRIVE_REMOTE: DName := DName + 'ネットワークデスク';
      DRIVE_CDROM: DName := DName + 'CDROMデスク';
      DRIVE_RAMDISK: DName := DName + 'ラムデスク';
      end;
      ListBox1.Items.Add(DName);
    end;
  end;
end;

集合型を内部表現で使うのは、注意が必要です。
なるべく集合型として使いましょう。

なお、集合型の演算子として +,*,-,IN が定義されていますので、
not  は、 全定義 - 値
xor  は、 和集合 - 積集合で代用できます。

要素数は、4バイトまでの集合なら以下のようなぐあいですか?

function GetElements(Drvs: TDrives):DWORD;
var
  i:TDrive;
begin
  result := 0;
  for i := Low(TDrive) to High(TDrive) do
  begin
    inc(result,DWORD(Drvs) and 1);
    DWORD(Drvs) := DWORD(drvs) shr 1;
  end;
end;


*リマーブルデスクって・・・(ぷ
参照: [Delphi-ML:9105] [Delphi-ML:42690] <PASCAL>

0073  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.2
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2003/07/10 yamamoto 編集
浮動小数点数を整数に丸めるときの注意

Delphiには四捨五入用の関数として Round() がありますが、この関数は「Banker's Rounding」(銀行家の丸め)をするため、普通の四捨五入とは結果が異なることがあるため注意が必要です。
具体的には、引数がちょうど ???.5 となるとき、通常の四捨五入では答は 1 繰り上げた数になりますが、Round() では結果の一の位が偶数になるように切り上げ/切り捨てが行われます。(例: 0.5 は 0,1.5 は 2 になる)[Delphi-ML:4727][Delphi-ML:12693]から始まるスレッドも参考になります。

これに対して Trunc() はいつも小数部分を切り捨てて返しますので、普通の四捨五入をするには、

function Roundoff(X: Extended): Longint;
begin
    if x >= 0 then Result := Trunc(x + 0.5)
              else Result := Trunc(x - 0.5);
end;

のような関数を作って使用します。

Professional Edition 以上では、Math ライブラリに Floor()/Ceil() の関数もあります。
Int() 関数は Trunc() と同じ答を Extended 型で返します。
参照: [Delphi-ML:4727] [Delphi-ML:12693] [Delphi-ML:20194] [Delphi-ML:20205] <PASCAL>

0024  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.5
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/09/19 西坂良幸 編集
Sqr()の結果が負になる!?

Sqr()関数は引数にIntegerを渡すと結果にIntegerが返ってきます。

ヘルプで
    function Sqr(X: Real): Real;
と書いてあるにもかかわらずです。
Sqr()関数は引数が整数型だと結果は整数型で返り、
引数が実数型だと結果は実数型で返ります。
これが仕様です。

なお、整数型を渡した場合、オーバーフローのチェックは行われません。そのため渡す値によっては

    sqr(x)+sqr(y)

の結果が負になる場合があり、これを実行時に検出することはできません。自乗した場合の結果が Integer の制限を越えることが予想できる場合には、引数を実数型にキャストして渡す必要があります。
参照: [Delphi-ML:7250] <バグ> <PASCAL>

0225  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/08/28 西坂良幸 rev 1.4
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/09/05 藤中理香 編集
数値の2進数への変換で効率的な方法


ML[Delphi-ML:32617][Delphi-ML:32720]のスレッドにいろいろな変換関数例が示されています。

ベンチワークしたわけではありませんが、効率ということでは、中川さんのコードがお勧めでは?
「10進から2進数への変換に標準関数はありませんが、デルファイではTBitsという
似て非なるクラスがあります。(定義はclassesの中)
そこを見てみますと、インラインアセンブラが使われています。ということで、題材的に
インラインアセンブラが向いているのではないかと思い作ってみました。」[Delphi-ML:32720]中川

以下、中川さんの例示コードをIntToHex型パラメータの関数にしたものです。(4桁ごとのスペースはありません)

function IntToBin(Int: LongInt; Digits: Integer = 32): string;
  // 変換の関数
  procedure ToBin(Int: LongInt; x: pointer);
  asm
    push ebx;         // ebxは使用不可なので保存
    mov edx,x;        // 文字列の先頭アドレス
    mov eax,int;      // 変換する整数
    mov ecx,32;       // 32回のループのセット
    @MAWASU:
       mov bl,'0';    // とりあえず文字「0」とする
       sal eax,1;     // 整数を左シフトして、1ビットを取り出す
       jnc @TOBU;     // ビットが0ならそのまま
       inc bx;        // ビットが1ならば文字を「1」にする
    @TOBU:
       mov [edx],bl;  // 文字を書き込む
       inc edx;       // 文字列のポインタを進める。
       loop @MAWASU;  // これを繰り返す
    mov bl,0;         // 文字列の最後は空文字
    mov [edx],bl;     // 最後の空文字の書き込み
    pop ebx;          // ebxを呼び戻す
  end;
var
  Buff: array[0..63] of Char;
begin
  if (Digits> 1) and (Digits <= 32) then
  begin
    ToBin(Int, @Buff);
    result := StrPas(Buff + 32 - Digits);
    exit;
  end;
  raise EConvertError.Create('第二パラメータが範囲外です。');
end;

// テスト
procedure TForm1.Button1Click(Sender: TObject);
begin
   Edit1.Text := IntToBin(16,8);
   Edit2.Text := IntToBin(-1);
   Edit3.text := IntToBin(Integer($F0F0F0F0));
end;

参照: [Delphi-ML:32720] <PASCAL>

0151  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/11 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/11 osamu 編集
和暦を西暦に直したい

こんなのはどうでしょう?

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key<>#13 then
    Exit;
  Key:= #0;
  with Edit1 do
  try
    Text:= FormatDateTime('yyyy/m/d',VarToDateTime(Text));
    SelectAll;
  except
    on EVariantError do begin
      SelectAll;
      raise Exception.Create('入力が不正です');
    end;
  end;
end;

98/8/21 h10/8/21 s24-10-2 m1/1/1 8/21 8/21/98 98/5 1996-8/21
等 ほとんど何でもありです。半角、全角関係なしです。
参照: [Delphi-ML:25313] [Delphi-ML:25315] <文字列> <日時> <PASCAL>

0088  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/08 osamu 編集
time_t を TDateTime に変換する

time_t は、 int (4バイト)で、GMT の 1970年1月1日0時0分0秒からの経過時間を秒単位で表したもので、どうやら2038年1月19日3時14分07秒で破綻するようです。

function time_tToDateTime(vtime_t : Integer) : TDateTime;
const
  cDeltaDate  : Integer = 25569; // 1970/01/01 の TDateTime値
  cSecPerMint : Integer = 60;
  cSecPerHour : Integer = (60*60);
  cSecPerDay  : Integer = (60*60*24);
var
  hh, mm, ss : Word;
  n : Integer;
begin
  vtime_t := vtime_t - cSecPerHour * 9;  // JST=+9
  n  := vtime_t mod cSecPerDay;
  hh := n div cSecPerHour;
  n  := n mod cSecPerHour;
  mm := n div cSecPerMint;
  ss := n mod cSecPerMint;
  Result := (vtime_t div cSecPerDay) +
        cDeltaDate + EncodeTime(hh, mm, ss, 0);
end;
参照: [Delphi-ML:18520] <日時> <PASCAL>

[新規作成] [最新の情報に更新]

How To
Lounge
KeyWords

Tips
Delphi
Home
Osamu Takeuchi osamu@big.or.jp