Without working Delphi and AvrCo yet, I could just make this with Lazarus. It should work with Delphi without changes. Double was not enough, so extended type was used.
Here is the conversion unit:
Code
unit fix64conv;
{$mode delphi}{$H+}
interface
uses
Classes, SysUtils, StrUtils;
const
FIX_INT_DIGITS = 10 + 1; // number of digits used for representation of integer part plus 1 digit for sign
FIX_FRACT_DIGITS = 9; // number of digits used for representation of fractional part
FIX_MAX_STRING_LENGTH = FIX_INT_DIGITS + 1 {decimal point} + FIX_FRACT_DIGITS; // 21 is max length of all strings that contain fixed point numbers
FIX_ONE = $100000000; // 1.0 is neutral in multiplication and division of fixed point numbers
FIX_FRACT_MULTIPLIER: int64 = 1000000000; // fractional multiplier needed to get readable numbers from fractional part
FIX_MASK_FOR_FRACT_PART: int64 = $00000000FFFFFFFF; // needed to mask just bits of fractional part of fixed point numbers
type
fix64 = int64;
fix = fix64;
TFix64 = fix64;
TFix = fix64;
word64 = qword;
TFix64Overlay = packed record case integer of
// for fast extraction of integer and fractional parts
0: (fix: fix64);
1: (x: fix64);
2: (i64: int64);
3: (w64: word64);
4: (f: longword; i: longint);
5: (f32: longword; i32: longint);
6: (b: array[0..7] of byte);
7: (w: array[0..3] of word);
end;
TFixString = string[FIX_MAX_STRING_LENGTH]; // strings containing fixed point numbers
function ExtendedToFix64(const a: extended): fix64;
function Fix64ToExtended(const a: fix64): extended;
function Fix64ToString(const a: fix64): TFixString;
function Fix64GetIntPartAsString(const a: fix64): TFixString;
function Fix64GetFractPartAsString(const a: fix64): TFixString;
function Fix64GetIntPart(const a: fix64): longint;
function Fix64GetFractPart(const a: fix64): longword;
function Fix64ToLongInt(const a: fix64): longint;
implementation
function ExtendedToFix64(const a: extended): fix64;
begin
result := Round(a * FIX_ONE);
end;
function Fix64ToExtended(const a: fix64): extended;
begin
result := a;
result := result / FIX_ONE;
end;
function Fix64ToString(const a: fix64): TFixString;
var
TmpStr: TFixString;
begin
TmpStr := fix64GetIntPartAsString(a);
if (int64(a) < 0) and (TmpStr[1] <> '-') then // special case negative numbers between 0 and -1
Result := '-'
else
Result := '';
Result := Result + TmpStr + DecimalSeparator;
Result := Result + fix64GetFractPartAsString(a);
end;
function Fix64GetIntPartAsString(const a: fix64): TFixString;
begin
Result := IntToStr(fix64GetIntPart(a));
end;
function Fix64GetFractPartAsString(const a: fix64): TFixString;
begin
Result := AddChar('0', IntToStr(fix64GetFractPart(a)), FIX_FRACT_DIGITS); // AddChar is actually PadLeft
end;
function Fix64GetIntPart(const a: fix64): longint;
begin
Result := Fix64ToLongInt(a);
end;
function Fix64GetFractPart(const a: fix64): longword;
var
Tmp: TFix64Overlay;
begin
Tmp.fix := a;
if Tmp.fix < 0 then
Tmp.fix := (not Tmp.fix) + 1;
Tmp.fix := Tmp.fix and FIX_MASK_FOR_FRACT_PART;
Result := longword((word64(Tmp.f) * word64(FIX_FRACT_MULTIPLIER)) div word64(FIX_ONE));
end;
function Fix64ToLongInt(const a: fix64): longint;
var
Tmp: TFix64Overlay;
begin
Tmp.fix := a;
if (Tmp.i64 < 0) and (Tmp.f <> 0) then // (Tmp.f <> 0) is a correction for round negative numbers
Tmp.fix := Tmp.fix + FIX_ONE;
Result := Tmp.i;
end;
end.
To use the demo example below, make new application with single form and put Memo1 and Button1, and replace default code with the one here:
Code
unit demo;
{$mode delphi}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, fix64conv;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
var
ext: extended;
fix: fix64;
fixovr: TFix64Overlay;
begin
ext := 3.14;
fix := ExtendedToFix64(ext);
Memo1.Clear;
Memo1.Append(FloatToStr(ext));
Memo1.Append('$' + IntToHex(fix, 16) + ' ' + IntToStr(fix));
Memo1.Append('$' + IntToHex(TFix64Overlay(fix).i, 1) + ',' + IntToHex(TFix64Overlay(fix).f, 8));
ext := Fix64ToExtended(fix);
Memo1.Append(FloatToStr(ext));
Memo1.Append(FloatToStr(2147483647.333333333) + ' FloatToStr()'); // FloatToStr handles just double (only 15 significant digits)
Memo1.Append(FloatToStr(Fix64ToExtended($7FFFFFFFFFFAAAAA)) + ' FloatToStr()'); // FloatToStr does not handle full extended type
Memo1.Append(Fix64ToString($7FFFFFFFFFFAAAAA) + ' Fix64ToString()'); // my string conversion can handle full extended type
Memo1.Append(FloatToStr(Fix64ToExtended($7FFFFFFFFFFFFFFF)) + ' FloatToStr()'); // 2147483647.999999999 biggest fix64 number
Memo1.Append(Fix64ToString($7FFFFFFFFFFFFFFF) + ' Fix64ToString()'); // my string conversion can handle full extended type
Memo1.Append(FloatToStr(Fix64ToExtended($8000000000000000)) + ' FloatToStr()'); // -2147483648.000000000 smallest fix64 number
Memo1.Append(Fix64ToString($8000000000000000) + ' Fix64ToString()'); // my string conversion can handle full extended type
fixovr.fix := fix; // fixovr.fix = 3,14
inc(fixovr.i); // fixovr.fix = 4,14 (increment only integer part by one)
fixovr.f := $80000000; // fixovr.fix = 4,50 (replace fractional part with 0,5)
Memo1.Append(FloatToStr(Fix64ToExtended(fixovr.fix)) + ' FloatToStr()');
Memo1.Append(Fix64ToString(fixovr.fix) + ' Fix64ToString()');
end;
end.
This should give you a memo saying this:
Code
3,14
$0000000323D70A3D 13486197309
$3,23D70A3D
3,13999999989755
2147483647,33333 FloatToStr()
2147483647,99992 FloatToStr()
2147483647,999918619 Fix64ToString()
2147483648 FloatToStr()
2147483647,999999999 Fix64ToString()
-2147483648 FloatToStr()
-2147483648,000000000 Fix64ToString()
4,5 FloatToStr()
4,500000000 Fix64ToString()
For curious, you can replace 3.14 with 1.0 or 0.5 or -1.0, and try to guess what would it look like in hexadecimal representation of converted
fix64 number. I have also noticed that Lazarus FloatToStr() deals just with double, and although it accepts extended type it can not handle more then 15 significant digits (this is limitation of double). This means that if you have 19 significant digits (as in extended type) then some data is lost. I do not know yet if this unwanted feature is also valid for Delphi. So, FloatToStr(Fix64ToExtended($7FFFFFFFFFFAAAAA) gives you just 2147483647,99992 (less significant digits are lost because of just 15 significant digits as limitation of double type), and it is even more evident with 2147483647.999999999 (biggest
fix64 number) where FloatToStr(Fix64ToExtended($7FFFFFFFFFFFFFFF)) is rounded and gives just 2147483648. If someone wants to know, smallest
fix64 number is $8000000000000000 (-2147483648.000000000). Even if FloatToStr was perfect, there are some
fix64 numbers that can not be represented 100% accurate even in extended type.
Fix64 numbers have 10 digits for integer part, and 9 digits for fractional part. That matches 19 significant digits of extended type (double is a looser with just 15), but there is one hidden fractional digit never displayed. I do not remember if there were 2 or 3 additional bits that are used in all internal calculations, but they never show when
fix64 number is converted to string (since we do not have enough bits to show all 0..9 numbers this last fractional digit was hidden). Therefore, we can say that
fix64 has 19+1=20 digits so even extended type is small for our "mighty"
fix64 numbers. Joking put a side, I do not see that this will ever affect you calculations.
Report test results here.
EDIT: I have replaced simple TFix64Overlay from the first version with more advanced variant record, which is much closer to AvrCo implementation, and I have also expanded original demo a little. I have also added my Fix64ToString() since it is more accurate then built in functions. Please test it in Delphi.