2013-06-07 12 views
8

Ich habe gerade festgestellt, dass eine Software, die ich neu implementieren muss, extensive System.Round() verwendet. Das Problem ist, dass diese Funktion "Bankers runding" verwendet und das Verhalten nicht wie in Math.RoundTo() geändert werden kann (rmDown, rmUp, rmNearest, rmTruncate).Delphi-Funktion überschreiben System.Round

Ich muss das Verhalten auf "normale Runden" ändern (12.5 -> 13 NICHT 12.5 -> 12) ... Also ich möchte System.Round() global überschreiben. Ich möchte dies tun, weil Round() so oft verwendet wird und ich nicht alle manuell ändern möchte.

Wie ist das möglich?

+3

Nach http://docwiki.embarcadero.com/Libraries/XE2/en/System.Round das Verhalten von System.round tatsächlich eingestellt werden kann. – RobS

+9

Sie können nur neue Einheit mit published 'function Round (extended): extended 'erstellen, diese Einheit in den' uses'-Abschnitt jeder Ihrer Quelldateien aufnehmen und neu kompilieren. Wenn Sie nicht den vollständig qualifizierten Namen verwenden (und fast niemand tut es für die allgegenwärtige RTL-Funktion), dann wäre Ihre neue Funktion bei der e-Kompilierung besser sichtbar als 'System.Round'. Das Ändern des globalen Verhaltens kann Folgen für einige Drittanbieter-Code in Ihrem Programm haben, –

+0

Zustimmen - das ist der einfachste Ansatz. +1 –

Antwort

14

WARNUNG: Obwohl die folgende Antwort die gestellte Frage anspricht, würde ich empfehlen, dass niemand sie jemals verwendet. Wenn Sie eine andere Rundung als Round durchführen möchten, schreiben und rufen Sie eine dedizierte Funktion auf.


Sie können einen Laufzeitcode Haken verwenden, um die Implementierung von Round zu ändern.

Die Falte ist, dass es ein wenig schwierig ist, die Adresse der Round Funktion zu bekommen, obwohl es ein intrinsischer ist. Sie müssen auch darauf achten, die verwendete Aufrufkonvention zu befolgen. Der Eingabewert wird im x87-Stack-Register ST(0) übergeben und der Rückgabewert ist eine 64-Bit-Ganzzahl in EDX:EAX.

So geht's.

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 [email protected] 
end; 

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

     // your implementation goes here 
end; 

initialization 
    RedirectProcedure(System_Round, @_ROUND); 

Wenn Sie lieber Ihre Version in Pascal als asm implementieren, dann müssen Sie die Nicht-Standard-Aufrufkonvention von _ROUND zum Standard Delphi-Aufrufkonvention anzupassen. Gefällt mir:

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; 

Beachten Sie, dass ich hier angenommen habe, dass Ihr Programm 32 Bit zielt. Wenn Sie 64 Bit zielen müssen, dann sind die Prinzipien sehr ähnlich, aber die Details unterscheiden sich offensichtlich.

+2

+1 Gibt es etwas, was du nicht weißt? – jpfollenius

+1

@Smasher Ha, danke! Es gibt riesige Mengen solcher Dinge. Das Crawlen von Runtime-Code sieht immer cleverer aus, als es wirklich ist. Der Trick mit 'System_Round' ist etwas, was ich von Madshis madExcept gelernt habe. Nichts Originelles hier. –

+1

@DavidHeffernan - es ist immer noch enzyklopädisches Wissen, auch wenn Sie nicht das Original denken –

7
UNIT MathRound; 

INTERFACE 

FUNCTION ROUND(X : Extended) : Int64; 

IMPLEMENTATION 

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

END. 

Wenn Sie die oben in MathRound.PAS speichern i-Verzeichnis Ihres Projekts, dann dieses Gerät in die Quelldateien enthalten, werden Sie eine mathematische Funktion RUNDEN haben statt Rundung des Bankers, die standardmäßig implementiert ist.

Es wird abgerundet -12,5 bis -12 (dh. Immer für Null-Werte auf 0,5 gerundet) und -12,1 bis -11. Wenn Sie eine „logische“ Runden wollen, sollten Sie diese Zeile verwenden statt:

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

als Funktionskörper.

führt dies in

ROUND(12.5) = 13 
ROUND(12.1) = 12 
ROUND(-12.5)= -13 
ROUND(-12.1)= -12 
+0

IMHO ist dies die einfachste und sauberste Lösung – ComputerSaysNo

1

Sie über manuell erforderliche Zeit und Mühe besorgt sind, um alle vorhandenen Round Anrufe nennen sonst etwas zu ändern. Also ändere sie nicht manuell. Verwenden Sie ein Werkzeug, um es zu automatisieren. Zum Beispiel könnten Sie sed verwenden.

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

Mit dieser Änderung ist Ihr Code jetzt explizit darüber, was es rundet.Es erfordert nicht, dass jeder, der Ihren Code liest, weiß, dass ein Patch an anderer Stelle im Code angewendet wurde, um das globale Verhalten von Standardfunktionen zu beeinflussen. Es wirkt sich auch nicht auf Code aus, den Sie mit anderen Bibliotheken verknüpfen, die sich möglicherweise auf das Standardverhalten von Round verlassen und durch eine globale Änderung beschädigt werden.

+1

Das ist wahrscheinlich zu unüberlegt. Sicherlich wird das Wort "rund" im Code anders verwendet. –

+0

Ich frage mich, was wahrscheinlich größer ist, @David, wie oft das Standalone-Wort 'Round' im Code erscheint, wenn * not * sich auf eine Funktion bezieht, oder wie oft' System.Round' irgendwo in a aufgerufen wird Programm (nicht nur der Quellcode des Projekts) und für die Verwendung des Standard-Rundungsverhaltens erforderlich. Und was ist schwerer zu erkennen? Ich postuliere, dass es in beiden Fällen Letzteres ist. –

+0

Ich dachte mehr an die Kommentare. Sehr einfach, System.Round natürlich zu erkennen. Unwahrscheinlich, andere Funktionen genannt Runde sein. Achten Sie auch auf Methoden. –

0

// Banker omzeilen Runde

function RoundN(X: double): double; 
const 
    cFuncName = 'RoundN'; 
begin 
    Result := Trunc(X + Frac(X)); 
end; 
+1

Hallo und willkommen in SO. StackOverflow.com ist eine englische Seite. Bitte geben Sie Ihrem Code, Fragen und Antworten immer englische Beschreibungen an. Ihre Antwort muss so bearbeitet werden, dass sie nur englischen Text enthält. – Hexfire

+0

Das hat das OP nicht gefragt. Dies überschreibt nicht die Round-Methode – rvheddeg