There was some talk a while back about the fact that the Comp data type is a second-class citizen - there are no routines to go from Comp to string and back. I'm working on a remedy, and I have a unit that's in pretty good shape. It includes CompToStr, CompToHex, StrToComp, and the helper functions CMod and CDiv, which implement MOD and DIV for Comp.
I found out something interesting in implementing the CMod and CDiv functions. It appears that the division operation on Comp variables *ROUNDS* the result rather than truncating it as one might expect.
I've found some ODD things happening at the very ends of the Comp range. E.g. the FIRST time I attempt to use CompToStr on a string with the value $7FFF FFFF FFFF FFFD (broken up for clarity), I get a floating point exception without a specific location in my program. But if I try, try again I get no exception. WEIRD! Anyway, take a look at this unit and see if you find any cool uses for it.
You WILL realize from looking at this that the format of a Comp is simply
two double-words jammed together. In effect, the high Dword is a LongInt
and the low DWord is an unsigned double-word. I really don't know why Delphi
and Object Pascal treat Comp as floating-point??
unit Compfunc;
interface
TYPE
CompAsTwoLongs = RECORD
LoL, HiL : LongInt;
END;
CONST Two32TL: CompAsTwoLongs = (LoL:0; HiL:1);
VAR Two32: Comp ABSOLUTE Two32TL;
{Some operations fail erratically for values at the extreme ends
of the range of Comp}
CONST MaxCompTL: CompAsTwoLongs = (LoL:$FFFFFFF0; HiL:$7FFFFFFF);
VAR MaxComp: Comp ABSOLUTE MaxCompTL;
FUNCTION CMod(Divisor, Dividend: Comp): Comp;
FUNCTION CDiv(Divisor: Comp; Dividend: LongInt): Comp;
FUNCTION CompToStr(C: Comp): String;
FUNCTION CompToHex(C: Comp; Len: Integer): String;
FUNCTION StrToComp(const S : String): Comp;
implementation
USES SysUtils;
FUNCTION CMod(Divisor, Dividend: Comp): Comp;
VAR Temp : Comp;
BEGIN
{Note: / operator for Comps apparently ROUNDS
result rather than truncating}
Temp := Divisor / Dividend;
Temp := Temp * Dividend;
Result := Divisor - Temp;
IF Result < 0 THEN Result := Result + Dividend;
END;
FUNCTION CDiv(Divisor: Comp; Dividend: LongInt): Comp;
BEGIN
Result := Divisor / Dividend;
IF Result * Dividend > Divisor THEN
Result := Result - 1;
END;
FUNCTION CompToStr(C: Comp): String;
VAR Posn : Integer;
BEGIN
IF C > MaxComp THEN
Raise ERangeError.Create('Comp too large for conversion
to string');
IF C < 0 THEN Result := '-'+CompToStr(-C)
ELSE
BEGIN
Result := '';
Posn := 0;
WHILE TRUE DO
BEGIN
Result := Char(Round($30 + CMod(C,10)))+Result;
IF C < 10 THEN Break;
C := CDiv(C,10);
Inc(Posn);
IF Posn MOD 3 = 0 THEN Result := ','+Result;
END;
END;
END;
FUNCTION CompToHex(C: Comp; Len: Integer): String;
BEGIN
IF (CompAsTwoLongs(C).HiL = 0) AND (Len <= 8) THEN
Result := IntToHex(CompAsTwoLongs(C).LoL, Len)
ELSE
Result := IntToHex(CompAsTwoLongs(C).HiL, Len-8)
+
IntToHex(CompAsTwoLongs(C).LoL, 8)
END;
FUNCTION StrToComp(const S : String): Comp;
VAR Posn : Integer;
BEGIN
IF S[1] = '-' THEN
Result := -StrToComp(Copy(S,2,Length(S)-1))
ELSE
IF S[1] = '$' THEN {Hex string}
try
IF Length(S) > 9 THEN
BEGIN
{If string is invalid, exception raised by StrToInt}
Result := StrToInt('$'+Copy(S,Length(S)-7, 8));
IF Result < 0 THEN Result := Result + Two32;
{If string is invalid, exception raised by StrToInt}
CompAsTwoLongs(Result).HiL :=
StrToInt(Copy(S,1,Length(S)-8))
END
ELSE
BEGIN
{If string is invalid, exception raised by StrToInt}
Result := StrToInt(S);
IF Result < 0 THEN Result := Result + Two32;
END;
except
ON EConvertError DO Raise
EConvertError.Create(S+'
is not a valid Comp');
end
ELSE {Decimal string}
BEGIN
Posn := 1;
Result := 0;
WHILE Posn <= Length(S)
DO
CASE S[Posn]
OF
',': Inc(Posn);
'0'..'9': BEGIN
Result := Result * 10 + Ord(S[Posn])-$30;
Inc(Posn);
END;
ELSE Raise EConvertError.Create(S+
' is not a valid Comp');
END;
END;
END;
end.