I just discovered that a software I have to reimplement uses extensivelly System.Round(). The problem is that this function uses "Bankers rounding" and the behaviour can not be changed like in Math.RoundTo() (rmDown,rmUp,rmNearest,rmTruncate).
I have to change the behaviour to "normal rounding" (12.5 -> 13 NOT 12.5 -> 12)... So I would like to override System.Round() globally. I want to do this, because Round() is used so many times and I do not want to change them all manually.
How is this possible?
WARNING: Although the answer below addresses the question that was asked, I would recommend that nobody ever uses it. If you want to perform rounding differently from Round
then write and call a dedicated function.
You can use a runtime code hook to change the implementation of Round
.
The wrinkle is that it's a little tricky to get hold of the address of the Round
function though because it is an intrinsic. You also have to be careful to follow the calling convention used. The input value is passed in the x87 stack register ST(0)
and the return value is a 64 bit integer in EDX:EAX
.
Here's how to do it.
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);
If you'd rather implement your version in Pascal than asm then you need to adapt the non-standard calling convention of _ROUND
to the standard Delphi calling convention. Like this:
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;
Note that I have assumed here that your program is targeting 32 bit. If you need to target 64 bit then the principles are much the same, but the details obviously differ.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With