Reputation: 766
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?
Upvotes: 8
Views: 2596
Reputation: 1
// bankers round omzeilen
function RoundN(X: double): double;
const
cFuncName = 'RoundN';
begin
Result := Trunc(X + Frac(X));
end;
Upvotes: 0
Reputation: 612964
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.
Upvotes: 14
Reputation: 163277
You're concerned about the time and effort required to manually change all your existing Round
calls to call something else. So don't change them manually. Use a tool to automate it. For example, you could use sed.
sed -i -e "s/\bRound\b/BiasedRoundAwayFromZero/g" *.pas
With that change, your code is now explicit about what rounding it uses. It doesn't require everyone reading your code to know that a patch was applied elsewhere in the code to affect the global behavior of standard functions. It also doesn't affect code you link to from other libraries, which might rely on the standard behavior of Round
and be broken by a global change.
Upvotes: 1
Reputation: 8243
UNIT MathRound;
INTERFACE
FUNCTION ROUND(X : Extended) : Int64;
IMPLEMENTATION
FUNCTION ROUND(X : Extended) : Int64;
BEGIN
Result:=TRUNC(X+0.5)
END;
END.
If you save the above in MathRound.PAS i your project's directory, then include this unit in your source files, you will have a mathematical ROUND function instead of the banker's rounding that is implemented by default.
It'll round off -12.5 to -12 (ie. always rounding towards zero for .5 values) and -12.1 to -11. If you want a more "Logical" rounding, you should use this line instead:
IF X<0.0 THEN Result:=-TRUNC(ABS(X)+0.5) ELSE Result:=TRUNC(X+0.5)
as the function body.
This will result in
ROUND(12.5) = 13
ROUND(12.1) = 12
ROUND(-12.5)= -13
ROUND(-12.1)= -12
Upvotes: 7