8

再実装する必要があるソフトウェアが System.Round() を広範囲に使用していることを発見しました。問題は、この関数が「Bankers rounding」を使用しており、Math.RoundTo() (rmDown,rmUp,rmNearest,rmTruncate) のように動作を変更できないことです。

動作を「通常の丸め」に変更する必要があります (12.5 -> 13 NOT 12.5 -> 12)...だから、System.Round() をグローバルにオーバーライドしたいと思います。Round() は何度も使用されており、すべてを手動で変更したくないため、これを行いたいと考えています。

これはどのように可能ですか?

4

4 に答える 4

14

警告: 以下の回答は、尋ねられた質問に対応していますが、誰も使用しないことをお勧めします。丸めを別の方法で実行する場合Roundは、専用の関数を記述して呼び出します。


ランタイム コード フックを使用して、 の実装を変更できますRound

Roundしわは、組み込み関数であるため、関数のアドレスを取得するのが少し難しいことです。また、使用される呼び出し規約に従うように注意する必要があります。入力値は x87 スタック レジスタで渡されST(0)、戻り値は 64 ビット整数ですEDX:EAX

方法は次のとおりです。

procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then 
  begin
    Move(NewCode, Address^, Size);
    FlushInstructionCache(GetCurrentProcess, Address, Size);
    VirtualProtect(Address, Size, OldProtect, @OldProtect);
  end;
end;

type
  PInstruction = ^TInstruction;
  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;

procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;//jump relative
  NewCode.Offset := 
    NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
  PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;

function System_Round: Pointer;
asm
  MOV     EAX, offset System.@Round
end;

procedure _ROUND;
asm
        { ->    FST(0)  Extended argument       }
        { <-    EDX:EAX Result                  }

        // your implementation goes here
end;

initialization
  RedirectProcedure(System_Round, @_ROUND);

自分のバージョンを asm よりも Pascal で実装したい場合は、非標準の呼び出し規約を_ROUNDDelphi の標準呼び出し規約に適合させる必要があります。このような:

function MyRound(x: Extended): Int64;
begin
  // your implementation goes here
end;

procedure _ROUND;
var
  x: Extended;
asm
        { ->    FST(0)  Extended argument       }
        { <-    EDX:EAX Result                  }

        FSTP    TBYTE PTR [x]
        CALL    MyRound
end;

ここでは、プログラムが 32 ビットをターゲットにしていると想定していることに注意してください。64 ビットをターゲットにする必要がある場合、原則はほとんど同じですが、詳細は明らかに異なります。

于 2013-06-07T08:38:31.123 に答える
7
UNIT MathRound;

INTERFACE

FUNCTION ROUND(X : Extended) : Int64;

IMPLEMENTATION

FUNCTION ROUND(X : Extended) : Int64;
  BEGIN
    Result:=TRUNC(X+0.5)
  END;

END.

上記をプロジェクトのディレクトリにある MathRound.PAS に保存し、このユニットをソース ファイルに含めると、デフォルトで実装されているバンカーの丸めではなく、数学的な ROUND 関数が得られます。

-12.5 を -12 に丸め (つまり、.5 値の場合は常にゼロに向かって丸めます)、-12.1 を -11 に丸めます。より「論理的な」丸めが必要な場合は、代わりに次の行を使用する必要があります。

  IF X<0.0 THEN Result:=-TRUNC(ABS(X)+0.5) ELSE Result:=TRUNC(X+0.5)

関数本体として。

これにより、

ROUND(12.5) = 13
ROUND(12.1) = 12
ROUND(-12.5)= -13
ROUND(-12.1)= -12
于 2013-06-07T11:40:30.503 に答える
1

あなたは、既存のすべてのRound呼び出しを手動で変更して別のものを呼び出すために必要な時間と労力について懸念しています。したがって、それらを手動で変更しないでください。ツールを使用して自動化します。たとえば、sed を使用できます。

sed -i -e "s/\bRound\b/BiasedRoundAwayFromZero/g" *.pas

この変更により、使用する丸めがコードで明示的に示されるようになりました。標準関数のグローバルな動作に影響を与えるパッチがコードの別の場所に適用されたことを、コードを読んでいる全員が知る必要はありません。また、他のライブラリからリンクするコードには影響しません。これは、の標準的な動作に依存しRound、グローバルな変更によって壊れる可能性があります。

于 2013-06-07T15:38:26.710 に答える