How do I split any number into its parts? - delphi

I have used this code to split into parts
How to find the numbers in the thousands, hundreds, tens, and ones place in DELPHI for an input number? For example: 155 has 5 ones, 5 tens, etc
however I now need to handle floats.
eg. 101.01
type TSplitNumber = record
Hundreds : integer
Tens : integer
Ones : integer
DecimalPl1 : integer //this will contain 0
DecimalPl2 : integer //this will contain 1
DecimalPl3 : integer
end;
Heres is implementation so far but it only handles non-floats.
type TDivisions = record
Hundreds : integer;
Tens : integer;
Ones : integer;
end;
function SplitNumberIntoDivisions(number : integer) : TDivisions;
var
p : integer;
Ones : integer;
Tens : integer;
Hundreds : integer;
MyDivision : TDivisions;
begin
p := 1;
while number <> 0 do
begin
if p = 1 then
begin
MyDivision.Ones := (number mod 10);
number := number div 10;
Inc(p);
end;
if p = 2 then
begin
MyDivision.Tens := (number mod 10);
number := number div 10;
Inc(p);
end;
if p = 3 then
begin
MyDivision.Hundreds := (number mod 10);
number := number div 10;
Inc(p);
end;
Result := MyDivision;
end;
end;
Anyone got any idea on how to do this?
Ben

First of all, recognise what your float is. Depending on the architecture you will have a certain number of significant digits. Upto 15 is typical but certain architectures may (at some point) give you more, and BCD as implemented in the RTL will give you up to 64.
You then have a 'power' indicating where the decimal point is. Typically you refer to the parts of the float as the mantissa and exponent.
So your answer is going to comprise a set of dgits, each digit being a power of 10, where the powers of 10 are all consecutive and may be all >0 or all <0 or they could straddle 0.
So you will need a structure to hold your powers of 10 which could be something like:
type TDecimalFactor = class(TObject)
Digit: Integer;
Power: Integer;
end;
You can find out what your largest power of 10 is by taking the base 10 log of the number. (So log(100) is 2 and log(1000) is 3 and log(0.1) is -1).
I suggest it would probably be fairly straightforward to 'normalise' your number by dividing it by the highest power - so you have a number which is between 1 and 9.999999999999999 and you know the power it represents. Then work through the number for a many digits as you want (bearing in mind the resolution of the platform) multiplying the float by 10 each time and decrementing your power by 1.
Sample program for you to play with:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Math, System.Generics.Collections;
type
TDecimalFactor = class(TObject)
protected
_nDigit: Integer;
_nPower: Integer;
function _GetValue(): Double;
public
constructor Create(nDigit, nPower: Integer);
procedure ShowFactor();
property Digit: Integer read _nDigit;
property Power: Integer read _nPower;
property Value: Double read _GetValue;
end;
TDecimalFactors = class(TObjectList<TDecimalFactor>)
protected
function _GetValue(): Double;
public
property Value: Double read _GetValue;
end;
constructor TDecimalFactor.Create(nDigit, nPower: Integer);
begin
Self._nDigit:=nDigit;
Self._nPower:=nPower;
inherited Create();
end;
function TDecimalFactor._GetValue(): Double;
begin
Result:=Self._nDigit*System.Math.Power(10, Self._nPower);
end;
procedure TDecimalFactor.ShowFactor();
begin
writeln('Factor: ', IntToStr(Self._nDigit), ' x ', FloatToStr(System.Math.Power( 10, Self._nPower)));
end;
function TDecimalFactors._GetValue(): Double;
var
pFactor: TDecimalFactor;
begin
Result:=0;
for pFactor in Self do
Result:=Result+pFactor.Value;
end;
var
fValue: Double;
fLog: Double;
nPower: Integer;
fPower: Double;
nDigits: Integer;
nLoop: Integer;
pDigits: TDecimalFactors;
pFactor: TDecimalFactor;
begin
try
pDigits:=TDecimalFactors.Create(true);
fValue:=6.5788902E-5; // try different values here to test operation
writeln('Input Value: '+FloatToStr(fValue));
nDigits:=15;
fLog:=log10(fValue);
nPower:=floor(fLog);
fPower:=Power(10,nPower);
fValue:=fValue/fPower;
nLoop:=0;
while(nLoop<nDigits) do
begin
pFactor:=TDecimalFactor.Create(floor(fValue), nPower);
pDigits.Add(pFactor);
pFactor.ShowFactor();
fValue:=(fValue-pFactor.Digit)*10;
inc(nLoop);
dec(nPower);
// stop the loop when we have got far enough, recognising limited precision
if(SameValue(fValue, 0, 0.00000001)) then
break;
end;
writeln('Factorised Value: '+FloatToStr(pDigits.Value));
FreeAndNil(pDigits);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

Related

I want to determine if the numbers in an array are factors of 50

I want to see if the numbers in an array are factors of 50 or not
I created an array of integers and tried to determine using mod , but can't seem to get it right
//Global
var
Form1: TForm1;
Num: array [1 .. 100] of integer;
ask: integer;
i, j, temp: integer;
procedure TForm1.btnDisplayCriteriaClick(Sender: TObject);
var
temp, fac :integer;
begin
fac:=num[ask];
if rbgCriteria.ItemIndex=0 then
begin
for I := 1 to ask do
begin
if fac mod 50=0 then
fac:=num[i];
Inc(fac);
end;
redDisplay.Lines.Add(IntToStr(fac)+' is a factor of 50')
end;
end;
I expect it to show whether number() is a factor of 50 but instead I get the position
It is correct for the procedure to return the position, judging by how you wrote the code. This is because fac stores the position of the array's element you work with (the array being Num). So, you're testing if the position you're working with divides 50, not the actual number.
procedure TForm1.btnDisplayCriteriaClick(Sender: TObject);
var
i :integer;
begin
if rbgCriteria.ItemIndex=0 then
for I := 1 to ask do
if (num[i] mod 50) = 0 then
redDisplay.Lines.Add(IntToStr(num[i])+' is a factor of 50')
end;

Find the Maximum in a List of calculated values using the Parallel Programming Library

I have a list of values. I'd like to find the maximum value. This is a common task. A simple version might be:
iBest := -1;
iMax := -1e20;
for i := 0 to List.Count - 1 do
begin
if List[i].Value > iMax then
begin
iBest := i;
iMax := List[i].Value;
end;
end;
In my case, the .Value getter is the performance bottleneck as it invokes a time consuming calculation (~100ms) which returns the final value.
How can I make this parallel using the Parallel Programming Library?
If the value is a calculated value and you can afford to cache, a simple solution might look something like this:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Threading, DateUtils, Math, Generics.Collections, StrUtils;
type
TFoo = class
private
FCachedValue : double;
function GetValue : double;
public
property CalculateValue : double read GetValue;
property CachedValue : double read FCachedValue;
end;
TFooList = class(TObjectList<TFoo>)
public
procedure CalculateValues;
function GetMaxValue(var BestIndex : integer) : double;
end;
function TFoo.GetValue : double;
begin
sleep(10); // simulate taking some time... make up a value
FCachedValue := DateUtils.MilliSecondOfTheSecond(now);
result := FCachedValue;
end;
procedure TFooList.CalculateValues;
begin
TParallel.For(0, Count - 1,
procedure (j:integer)
begin
self[j].CalculateValue;
end);
end;
function TFooList.GetMaxValue(var BestIndex : Integer) : double;
var
i, iBest : integer;
maxval : double;
begin
CalculateValues;
iBest := 0;
maxval := self[0].CachedValue;
for i := 0 to self.Count - 1 do
begin
if self[i].CachedValue > maxval then
begin
iBest := i;
maxval := self[i].CachedValue;
end;
end;
BestIndex := iBest;
result := maxval;
end;
var
LFooList : TFooList;
i, iBest : integer;
maxval : double;
begin
LFooList := TFooList.Create(true);
try
for i := 0 to 9999 do LFooList.Add(TFoo.Create);
maxval := LFooList.GetMaxValue(iBest);
WriteLn(Format('Max value index %d', [iBest]));
WriteLn(Format('Max value %.6f', [maxval]));
finally
LFooList.Free;
end;
ReadLn;
end.
This way your object retains a cache of the last calculated value, which you can refresh at any time, but which you can also access quickly. It's somewhat easier to parallelize a full calculation of the list than it is to paralellize the min/max search, and if the bottleneck is the calculation then it makes sense to restrict the added complexity to that operation alone (where you know the overhead is worth it).

Easiest way to find the mean of a dynamic array

I have created a dynamic array, and have passed values to it. Is there a shortcut for finding mean of dynamic array.
var
TheMin, TheMax: Integer;
x: array of Integer; //Dynamic array declaration
....
TheMin := MinIntValue(x);//I am able to retrieve the minium value of the dynamic array
TheMax := MaxIntValue(x);//I am able to retrieve the maximum value of the dynamic array
Is there a other way to get mean using Math library.
It is very easy to write such a function.
function Mean(const Data: array of Integer): Double; overload;
var
i: Integer;
begin
Result := 0.0;
for i := low(Data) to high(Data) do
Result := Result + Data[i];
Result := Result / Length(Data);
end;
I overloaded this so that it could sit alongside the same named functions in the Math unit.
If you wish to use built in library code you can use SumInt from the Math unit:
TheMean := SumInt(x) / Length(x);
SumInt performs the summation using an Integer accumulator. This is probably faster than the bespoke function that uses a floating point accumulator. However, an Integer accumulator is potentially subject to overflow which may be off-putting. On the other hand, an Integer accumulator is potentially more accurate than a floating point accumulator. Depending on your usage requirements these issues may be important to you.
In bother cases, if the input array is of length zero a runtime floating point divide by zero error will be raised.
If the array has additions or deletions, recalculating the average from scratch can get rather time consuming.
In that case it may be worthwhile to calculate a running average instead.
function RecalcAverage(OldAverage: double; const OldArray, Additions, Deletions: TIntArray): double; overload;
var
i: integer;
begin
i:= Length(OldArray) + Length(Additions) - Length(Deletions);
WeighingFactor := 1 / i;
Result:= OldAverage;
for i:= 0 to Length(Deletions) -1 do begin
Result:= Result - (Deletions[i] * WeighingFactor);
end;
for i:= 0 to Length(Additions) -1 do begin
Result:= Result + (Additions[i] * WeighingFactor);
end;
end;
If you have a running sum handy, you can avoid the rounding errors and calculate an exact average.
function RecalcAverage(var RunningTotal: Int64; const OldArray, Additions, Deletions: TIntArray): double; overload;
var
i: integer;
begin
for i:= 0 to Length(Deletions) -1 do begin
RunningTotal:= RunningTotal - Deletions[i];
end;
for i:= 0 to Length(Additions) -1 do begin
RunningTotal:= RunningTotal + Additions[i];
end;
Result:= RunningTotal / (Length(OldArray) + Length(Additions) - Length(Deletions));
end;
If performance is an issue, it would make much more sense to calculate all the needed values in a single loop.
type
TStats = record
MaxVal: integer;
MinVal: integer;
Average: double;
end;
function CalcStats(const input: TIntArray): TStats;
var
MinVal, MaxVal: integer;
Total: Int64;
i: integer;
begin
Assert(Length(input) > 0);
MinVal:= input[0];
MaxVal:= MinVal;
Total:= MinVal;
for i:= 1 to Length(input) -1 do begin
MinVal:= Min(MinVal, input[i]);
MaxVal:= Max(MinVal, input[i]);
Total:= Total + input[i];
end;
Result.MinVal:= MinVal;
Result.MaxVal:= MaxVal;
Result.Average:= Total / Length(input);
end;

How to save/load Set of Types?

I have this code
type
TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType6, xsTyp7, xsType8); // up to FXSample30;
..
private
FXSample = Set of TXSample;
..
published
property Sample: TXSample read FXSample write FXSample;
..
//if Sample has a value of
Sample := [xsType2, xsType4, xsType5, xsType6, xsTyp7];
how can i save/load the property of Sample?
i would like to save it in the database.
is it possible?
Provided your set will never exceed 32 possibilities (Ord(High(TXSample)) <= 31), then it is perfectly fine to typecast the set into an Integer and back:
type
TXSamples = set of TXSample;
var
XSamples: TXSamples;
begin
ValueToStoreInDB := Integer(XSamples);
Integer(XSamples) := ValueReadFromDB;
end;
To be more specific: SizeOf(TXSamples) has to be precisely equal to SizeOf(StorageTypeForDB). Thus the following ranges apply for Ord(High(TXSample)) when typecasting TXSamples to:
Byte: Ord(High(TXSample)) < 8
Word: 8 <= Ord(High(TXSample)) < 16
Longword: 16 <= Ord(High(TXSample)) < 32
UInt64: 32 <= Ord(High(TXSample)) < 64
Directly typecasting a set variable is not possible in Delphi, but internally Delphi stores the set as a byte-value. By using an untyped move, it is easy copied into an integer. Note that these functions only go up to a size of 32 (bounds of an integer). To increase the bounds, use Int64 instead.
function SetToInt(const aSet;const Size:integer):integer;
begin
Result := 0;
Move(aSet, Result, Size);
end;
procedure IntToSet(const Value:integer;var aSet;const Size:integer);
begin
Move(Value, aSet, Size);
end;
Demo
type
TMySet = set of (mssOne, mssTwo, mssThree, mssTwelve=12);
var
mSet: TMySet;
aValue:integer;
begin
IntToSet(7,mSet,SizeOf(mSet));
Include(mSet,mssTwelve);
aValue := SetToInt(mSet, SizeOf(mSet));
end;
Personally, I would convert the set to an integer and store it in the database as an INT field, like others suggested. #teran suggested using the TIntegerSet type, and here is my approach working on native integers using bit operations.
Note that you can use SampleInInteger() to determine whether a certain element from the enumeration is present in the integer mask generated by SampleSetToInteger().
Here's the code:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
{ .: TXSample :. }
TXSample = (xsType1 = 0, xsType2, xsType3, xsType4, xsType5,
xsType6, xsType7, xsType8); // up to FXSample30;
TXSampleSet = set of TXSample;
// Converts a TXSampleSet to an integer.
function SampleSetToInteger(const S: TXSampleSet): Integer;
var
Sample: TXSample;
begin
Result := 0;
for Sample := Low(TXSample) to High(TXSample) do
if (Sample in S) then
Result := Result or (1 shl Ord(Sample));
end;
// Converts an integer to TXSampleSet.
function IntegerToSampleSet(const Int: Integer): TXSampleSet;
var
I: Integer;
begin
Result := [];
for I := 0 to Ord(High(TXSample)) do
if Int and (1 shl I) <> 0 then
Result := Result + [TXSample(I)];
end;
// Checks if a TXSample is present in the integer.
function SampleInInteger(const S: TXSample; const Int: Integer): Boolean;
begin
Result := Int and (1 shl Ord(S)) <> 0;
end;
var
XSample, XSample1: TXSampleSet;
Tmp: Integer;
begin
XSample := [xsType2, xsType4, xsType5, xsType6, xsType7];
XSample1 := [xsType1];
Tmp := SampleSetToInteger(XSample);
Writeln(Tmp);
XSample1 := IntegerToSampleSet(Tmp);
if (xsType5 in XSample1) then
Writeln('Exists');
if (SampleInInteger(xsType1, Tmp)) then
Writeln('Exists in int');
Readln;
end.
A Delphi set is simply a collection of (possibly) related boolean flags. Each boolean flag corresponds to whether or not the matching ordinal value is in the set.
You could certainly pack a set into an integer value by representing the set as a bitset. Or you could create a textual representation of the set.
However, both of these options leave you with no tractable ability to query the database at the SQL level. For this reason I would advise you to represent each value in the set, i.e. each boolean flag, as a separate field (i.e. column) of the database table. This gives you the most powerful representation of the data.
the easiest way to store set in database (as #DavidHeffernan mentioned in comment) is to convert your set to bit-mask.
in int32 (integer) value you have 32 bits and can save set up to 32 fields;
Delphi has TIntegerSet (see http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.TIntegerSet) type defined in SysUtils. it is declared as:
TIntegerSet = set of 0..SizeOf(Integer) * 8 - 1;
so using it, it is simple to convert set to integer and back (just casting TIngeterSet to integer or vice versa);
bit-mask is also good option because it is only one INT field in your database table.
also you can create separate table in your DB to store set content (main table (id, ...), and setValuesTable (main_id, setElementValue)) (this option is good for using in db queries)
here is an example of using TIntegerSet:
program Project1;
{$APPTYPE CONSOLE}
uses System.SysUtils;
type
TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType7, xsType8);
TSampleSet = set of TXSample;
function SampleSetToInteger(ss : TSampleSet) : integer;
var intset : TIntegerSet;
s : TXSample;
begin
intSet := [];
for s in ss do
include(intSet, ord(s));
result := integer(intSet);
end;
function IntegerToSampleSet(mask : integer) : TSampleSet;
var intSet : TIntegerSet;
b : byte;
begin
intSet := TIntegerSet(mask);
result := [];
for b in intSet do
include(result, TXSample(b));
end;
var xs : TSampleSet;
mask : integer;
begin
xs := [xsType2, xsType6 .. xsType8];
mask := SampleSetToInteger(xs); //integer mask
xs := IntegerToSampleSet(mask);
end.
Set variables can be saved successfully to a TStream descendant. Here's an example.
Just create a new vcl forms application, add two TButton components to it and fill in the OnClick events for each button as illustrated in the example below.
This was created in XE4 so the uses clause might be different for other versions of Delphi but that should be trivial to change by removing the namespace indicators before each unit in the uses clause. Saving a set type variable with articulated values is possible to a binary file and easily with Delphi. In other words,
Also suggested is taking a look at the TypInfo unit if you have the source or just using the functions provided which make dissecting Set types down to their text representation fairly simple though no example is provided here. That is suggested if you want to include saving to a config or ini file or in a persistence format that is text editable.
The one below is the simplest one that I know of. Looking at the binary output of a set type saved to a stream like the one below implies that it is saved in the smallest possible bitmapped representation for the set based on its size. The one below maps to one byte on disk (the value is 5) which means that each value must be mapped to a power of 2 (seThis = 1, seThat = 2, seTheOther = 4) just like manually created constant bitmasked values. The compiler likely enforces that it follows rules that forces set to retain their ordinality. This example was tested an works in Delphi XE4.
Hope that helps.
Brian Joseph Johns
unit Unit1;
interface
uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls;
type
TSomeEnum = (seThis, seThat, seTheOther);
TSomeEnumSet = set of TSomeEnum;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
SomeSetVar: TSomeEnumSet;
SomeBoolean: Boolean;
SomeInt: Integer;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
SomeSetVar := [seThis, seTheOther];
SomeBoolean := True;
SomeInt := 31415;
with TFileStream.Create('SetSave.bin',fmCreate or fmOpenWrite or fmShareCompat) do
try
Write(SomeSetVar,SizeOf(SomeSetVar));
Write(SomeBoolean,SizeOf(Boolean));
Write(SomeInt,SizeOf(Integer));
finally
Free;
end;
SomeSetVar := [];
SomeInt := 0;
SomeBoolean := False;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
ResponseStr: string;
begin
with TFileStream.Create('SetSave.bin',fmOpenRead or fmShareCompat) do
try
Read(SomeSetVar,SizeOf(SomeSetVar));
Read(SomeBoolean,SizeOf(Boolean));
Read(SomeInt,SizeOf(Integer));
finally
Free;
end;
ResponseStr := 'SomeSetVar = ';
if (seThis in SomeSetVar) then
ResponseStr := ResponseStr + 'seThis ';
if (seThat in SomeSetVar) then
ResponseStr := ResponseStr + 'seThat ';
if (seTheOther in SomeSetVar) then
ResponseStr := ResponseStr + 'seTheOther ';
ResponseStr := ResponseStr + ' SomeBoolean = ' + BoolToStr(SomeBoolean);
ResponseStr := ResponseStr + ' SomeInt = ' + IntToStr(SomeInt);
ShowMessage(ResponseStr);
end;
end.
With a little help from RTTI it can be achieved in a generic way:
program SetConverter;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.RTTI, System.SysUtils;
type
SetConverter<T> = class abstract
strict private
class var FRttiContext: TRttiContext;
public
class function ToInteger(aSet: T): Integer;
class function FromInteger(aValue: Integer): T;
end;
{ SetConverter<T> }
class function SetConverter<T>.FromInteger(aValue: Integer): T;
var
ResultValues: TIntegerSet;
ReturnType: TRttiType;
SetValues: TIntegerSet;
EnumSet: T absolute SetValues;
begin
ReturnType := FRttiContext.GetType(Self).GetMethod('FromInteger').ReturnType;
if not((ReturnType is TRttiSetType) and (TRttiSetType(ReturnType).ElementType is TRttiEnumerationType)) then
Exit;
SetValues := TIntegerSet(aValue);
Result := EnumSet;
end;
class function SetConverter<T>.ToInteger(aSet: T): Integer;
var
RttiParameter: TRttiParameter;
ResultValues: TIntegerSet;
SetValues: TIntegerSet;
EnumSet: T absolute SetValues;
EnumType: TRttiEnumerationType;
SetType: TRttiSetType;
i: Integer;
begin
Result := 0;
RttiParameter := FRttiContext.GetType(Self).GetMethod('ToInteger').GetParameters[0];
if not(RttiParameter.ParamType is TRttiSetType) then
Exit;
SetType := RttiParameter.ParamType as TRttiSetType;
if not(SetType.ElementType is TRttiEnumerationType) then
Exit;
EnumType := SetType.ElementType as TRttiEnumerationType;
EnumSet := aSet;
ResultValues := [];
for i := EnumType.MinValue to EnumType.MaxValue do
if i in SetValues then
Include(ResultValues, i);
Result := Integer(ResultValues);
end;
type
TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType7, xsType8);
TSampleSet = set of TXSample;
var
Before, After: TSampleSet;
i: Integer;
begin
Before := [xsType2, xsType6 .. xsType8];
i := SetConverter<TSampleSet>.ToInteger(Before);
After := SetConverter<TSampleSet>.FromInteger(i);
WriteLN('Before = After: ' + (Before = After).ToString(TUseBoolStrs.True));
Readln;
end.
You can use this unit to convert set to int. if you need more settoint functions you can add yours by looking code below.
Set may take only 1 byte memory space.
So you can obtain yourSet size and get result as modula of this result.
example: your set size: 1 byte you can get result -->
Result := pINT^ mod maxVal
You should obtain maxval by calculating maxvalue of variable type.
maxVal = Power( 2, (8*sizeof(MySet)-1) )
unit u_tool;
interface
uses Graphics;
type
TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType6, xsTyp7, xsType8); // up to FXSample30;
FXSample = Set of TXSample;
function FXSampleToInt(FXSample: FXSample ): Integer;
function IntToFXSample(Value: Integer): FXSample;
function FontStyleToInt(FontStyle: TFontStyles ): Integer;
function IntToFontStyle(Value: Integer): TFontStyles;
implementation
function FXSampleToInt(FXSample: FXSample ): Integer;
var
pInt: PInteger;
begin
pInt := #FXSample;
Result := pInt^;
end;
function IntToFXSample(Value: Integer): FXSample;
var
PFXSample: ^FXSample;
begin
PFXSample := #Value;
Result := PFXSample^;
end;
function FontStyleToInt(FontStyle: TFontStyles ): Integer;
var
pInt: PInteger;
begin
pInt := #FontStyle;
Result := pInt^;
end;
function IntToFontStyle(Value: Integer): TFontStyles;
var
PFontStyles: ^TFontStyles;
begin
PFontStyles := #Value;
Result := PFontStyles^;
end;
end.
Or we can make compiler forget about the types completly and then define what it should see (in case we know in compile-time what it sould see). This solution is so awful as it can be written on just one line.
type
// Controls.TCMMouseWheel relies on TShiftState not exceeding 2 bytes in size
TShiftState = set of (ssShift, ssAlt, ssCtrl,
ssLeft, ssRight, ssMiddle,
ssDouble, ssTouch, ssPen,
ssCommand, ssHorizontal);
var
Shifts : TShiftState;
Value : Integer;
begin
Shifts := TShiftState((Pointer(#Value))^):
Value := (PInteger(#Shifts))^;
if ssShift in TShiftState((Pointer(#Value))^) then
Exit;
end;
It happens that unused (top) bits are set (or not) but it has no influence on set operations (in, =, +, -, * .. ).
This line in Delphi:
Shifts := TShiftState((Pointer(#Value))^);
is like this in Assembler (Delphi XE6):
lea eax,[ebp-$0c]
mov ax,[eax]
mov [ebp-$06],ax
On Delphi 2007 (where is TShiftState is smaller so Byte can be used) this Assembler:
movzx eax,[esi]
mov [ebp-$01],al
Simplest solution - proceeding the set directly as numeric variable. The "absolute" is a keyword:
procedure Foo(FXSample: TFXSample);
var
NumericFxSample: Byte absolute FXSample;
begin
WriteLn(YourTextFile, NumericFxSample);//numeric value from a set
end;
If your type is wider than 8 bits you need to use wider numeric type like word (up to 16 items in a set) or dword.

How to simulate bit-fields in Delphi records?

I would like to declare a record in Delphi that contains the same layout as it has in C.
For those interested : This record is part of a union in the Windows OS's LDT_ENTRY record. (I need to use this record in Delphi because I'm working on an Xbox emulator in Delphi - see project Dxbx on sourceforge).
Anyway, the record in question is defined as:
struct
{
DWORD BaseMid : 8;
DWORD Type : 5;
DWORD Dpl : 2;
DWORD Pres : 1;
DWORD LimitHi : 4;
DWORD Sys : 1;
DWORD Reserved_0 : 1;
DWORD Default_Big : 1;
DWORD Granularity : 1;
DWORD BaseHi : 8;
}
Bits;
As far as I know, there are no bit-fields possible in Delphi. I did try this:
Bits = record
BaseMid: Byte; // 8 bits
_Type: 0..31; // 5 bits
Dpl: 0..3; // 2 bits
Pres: Boolean; // 1 bit
LimitHi: 0..15; // 4 bits
Sys: Boolean; // 1 bit
Reserved_0: Boolean; // 1 bit
Default_Big: Boolean; // 1 bit
Granularity: Boolean; // 1 bit
BaseHi: Byte; // 8 bits
end;
But alas: it's size becomes 10 bytes, instead of the expected 4.
I would like to know how I should declare the record, so that I get a record with the same layout, the same size, and the same members. Preferrably without loads of getter/setters.
TIA.
Thanks everyone!
Based on this information, I reduced this to :
RBits = record
public
BaseMid: BYTE;
private
Flags: WORD;
function GetBits(const aIndex: Integer): Integer;
procedure SetBits(const aIndex: Integer; const aValue: Integer);
public
BaseHi: BYTE;
property _Type: Integer index $0005 read GetBits write SetBits; // 5 bits at offset 0
property Dpl: Integer index $0502 read GetBits write SetBits; // 2 bits at offset 5
property Pres: Integer index $0701 read GetBits write SetBits; // 1 bit at offset 7
property LimitHi: Integer index $0804 read GetBits write SetBits; // 4 bits at offset 8
property Sys: Integer index $0C01 read GetBits write SetBits; // 1 bit at offset 12
property Reserved_0: Integer index $0D01 read GetBits write SetBits; // 1 bit at offset 13
property Default_Big: Integer index $0E01 read GetBits write SetBits; // 1 bit at offset 14
property Granularity: Integer index $0F01 read GetBits write SetBits; // 1 bit at offset 15
end;
The index is encoded as follows : (BitOffset shl 8) + NrBits. Where 1<=NrBits<=32 and 0<=BitOffset<=31
Now, I can get and set these bits as follows :
{$OPTIMIZATION ON}
{$OVERFLOWCHECKS OFF}
function RBits.GetBits(const aIndex: Integer): Integer;
var
Offset: Integer;
NrBits: Integer;
Mask: Integer;
begin
NrBits := aIndex and $FF;
Offset := aIndex shr 8;
Mask := ((1 shl NrBits) - 1);
Result := (Flags shr Offset) and Mask;
end;
procedure RBits.SetBits(const aIndex: Integer; const aValue: Integer);
var
Offset: Integer;
NrBits: Integer;
Mask: Integer;
begin
NrBits := aIndex and $FF;
Offset := aIndex shr 8;
Mask := ((1 shl NrBits) - 1);
Assert(aValue <= Mask);
Flags := (Flags and (not (Mask shl Offset))) or (aValue shl Offset);
end;
Pretty nifty, don't you think?!?!
PS: Rudy Velthuis now included a revised version of this in his excellent "Pitfalls of converting"-article.
Rudy's Delphi Corner is the best resource I know of regarding Delphi and C/C++ interoperability. His Pitfalls of conversion is pretty much a must read when using C/C++ APIs in Delphi. The chapter you'll be most interested in is Records and alignment -> Bitfields, but I urge you to read the entire thing top to bottom, twice. The other articles are definitely worth the time investment, too.
Ok, my bit manipulation is a bit rusty, so I could have reversed the bytes. But the code below gives the general idea:
type
TBits = record
private
FBaseMid : Byte;
FTypeDplPres : Byte;
FLimitHiSysEa: Byte;
FBaseHi : Byte;
function GetType: Byte;
procedure SetType(const AType: Byte);
function GetDpl: Byte;
procedure SetDbl(const ADpl: Byte);
function GetBit1(const AIndex: Integer): Boolean;
procedure SetBit1(const AIndex: Integer; const AValue: Boolean);
function GetLimitHi: Byte;
procedure SetLimitHi(const AValue: Byte);
function GetBit2(const AIndex: Integer): Boolean;
procedure SetBit2(const AIndex: Integer; const AValue: Boolean);
public
property BaseMid: Byte read FBaseMid write FBaseMid;
property &Type: Byte read GetType write SetType; // 0..31
property Dpl: Byte read GetDpl write SetDbl; // 0..3
property Pres: Boolean index 128 read GetBit1 write SetBit1;
property LimitHi: Byte read GetLimitHi write SetLimitHi; // 0..15
property Sys: Boolean index 16 read GetBit2 write SetBit2;
property Reserved0: Boolean index 32 read GetBit2 write SetBit2;
property DefaultBig: Boolean index 64 read GetBit2 write SetBit2;
property Granularity: Boolean index 128 read GetBit2 write SetBit2;
property BaseHi: Byte read FBaseHi write FBaseHi;
end;
function TBits.GetType: Byte;
begin
Result := (FTypeDplPres shr 3) and $1F;
end;
procedure TBits.SetType(const AType: Byte);
begin
FTypeDplPres := (FTypeDplPres and $07) + ((AType and $1F) shr 3);
end;
function TBits.GetDpl: Byte;
begin
Result := (FTypeDplPres and $06) shr 1;
end;
procedure TBits.SetDbl(const ADpl: Byte);
begin
FTypeDblPres := (FTypeDblPres and $F9) + ((ADpl and $3) shl 1);
end;
function TBits.GetBit1(const AIndex: Integer): Boolean;
begin
Result := FTypeDplPres and AIndex = AIndex;
end;
procedure TBits.SetBit1(const AIndex: Integer; const AValue: Boolean);
begin
if AValue then
FTypeDblPres := FTypeDblPres or AIndex
else
FTypeDblPres := FTypeDblPres and not AIndex;
end;
function TBits.GetLimitHi: Byte;
begin
Result := (FLimitHiSysEa shr 4) and $0F;
end;
procedure TBits.SetLimitHi(const AValue: Byte);
begin
FLimitHiSysEa := (FLimitHiSysEa and $0F) + ((AValue and $0F) shr 4);
end;
function TBits.GetBit2(const AIndex: Integer): Boolean;
begin
Result := FLimitHiSysEa and AIndex = AIndex;
end;
procedure TBits.SetBit2(const AIndex: Integer; const AValue: Boolean);
begin
if AValue then
FLimitHiSysEa := FLimitHiSysEa or AIndex
else
FLimitHiSysEa := FLimitHiSysEa and not AIndex;
end;
Well, you basically need to get down to the dirty with bit-manipulation.
Why, specifically, do you need to retain that structure?
If you only need to talk to a legacy program that either talks in this dialect (TCP/IP or similar), or stores data in this manner (files, etc.), then I would map a normal Delphi structure to a bit-version compatible. In other words, I would use a normally structured Delphi structure in memory, and write code to write and read that structure in a compatible manner.
If you need to save memory, I would make getters and setters that manipulate bits of internal integers or similar. This will have a performance impact, but not much more than what the original C program would have, the only difference is that the bit-manipulation would be added by compiler magic in the C version, whereas you will have to write it yourself.
If you don't have many records in memory, and don't need to talk to another program, I'd use a natural Delphi structure. Trade-off for higher performance will be more memory used.
But it all depends on your criteria.
In any case, you won't be able to talk the Delphi compiler into doing the same job for you as the C compiler.
PACKED RECORD, suggested by another here, doesn't do that, and was never meant to. It will only remove alignment padding to put integers on 32-bit boundaries and similar, but won't pack multiple fields into one byte.
Note that a common way to do this is through Delphi SETS, which are implementing internally using bit-fields. Again, you will have different code than the C variant.

Resources