Ive written a program that communicate with some hardware using a serial connection.
It sends a lot of hexadecimal values my way (sensor readings) and every once in a while it sends a negative value.
ex.
i receive a hexadecimal value : FFFFF5D6
and i have to convert it into : -2602
another problem i have is that i can't convert a float into hex and back.
Are there any simple ways of doing this?
You can "convert" from hex to float by using an integer large enough to cover the float value used, then using the ABSOLUTE keyword. All that is really doing is encoding the memory of the value as an integer. Be very careful to use types which are exactly the same size (you can use SIZEOF to find the memory size of a value). If you need an odd size, then absolute against an array of byte and loop through and convert to/from each byte (which would be two characters hex).
the ABSOLUTE keyword forces two variables to START at the same memory address, any value written from one is immediately available in the other.
var
fDecimal : Double; // size = 8 bytes
fInteger : Int64 absolute fDecimal; // size = 8 bytes
begin
fDecimal := 3.14;
ShowMessage(format('%x=%f',[fInteger,fDecimal]));
fInteger := StrToInt64('$1234123412341234');
ShowMessage(FloatToStr(fDecimal)+'='+Format('%x',[fInteger]));
end;
here is the routine for floats with odd sizes:
var
fDecimal : extended;
fInteger : array[1..10] of byte absolute fDecimal;
sHex : string;
iX : integer;
begin
ShowMessage(IntToStr(SizeOf(fDecimal))+':'+IntToStr(SizeOf(fInteger)));
fDecimal := 3.14;
sHex := '';
for iX := 1 to 10 do
sHex := sHex + IntToHex(fInteger[iX],2);
ShowMessage(sHex);
// clear the value
fDecimal := 0.0;
// Reload the value
for iX := 1 to (Length(sHex) DIV 2) do
fInteger[iX] := StrToInt('$'+Copy(sHex,(Ix*2)-1,2));
ShowMessage(FloatToStr(fDecimal));
end;
to convert a hex string into a integer, you can use the StrToInt Function , also you can check the TryStrToInt function (wich returns False if string does not represent a valid number).
uses
SysUtils;
var
ivalue : integer;
begin
ivalue:=StrToInt('$FFFFF5D6'); // Hexadecimal values start with a '$' in Delphi
..
end;
For the Hexadecimal representation of a float number, you can check theses articles.
http://www.merlyn.demon.co.uk/pas-type.htm#Str
http://www.merlyn.demon.co.uk/pas-real.htm
http://www.merlyn.demon.co.uk/programs/hexfloat.pas (source code example)
I've never seen a float represented in hex, so you'd have to figure out exactly what format the device is using for that. For the negative number case, you'll need to use HexToInt to covert it to an integer, and then determine if that integer is larger than whatever threshold value represents MaxInt for the hardware's integer data type. If it's bigger than that, it's a negative number, which means you'll need to, IIRC, get the number's 1s complement and convert it to negative.
If you want to separate the exponent and the significand, you can use a variant record:
TExtRec = packed record
case Boolean of
false:
(AValue: Extended);
true:
(ASignificand: uint64; ASignExp: uint16;)
end;
I think this helps to understand the structure of the floating point number.
Example usage:
var
r: TExtRec;
begin
r.AValue := 123.567;
ShowMessage(IntToHex(r.ASignExp) + IntToHex(r.ASignificand));
end;
Output:
4005F7224DD2F1A9FBE7
You can calculate it back:
v = (-1)s * 2(e-16383) * (i.f)
With
e = $4005 = 16389 and
i.f = $F7224DD2F1A9FBE7
i.f = 1.930734374999999999954029827886614611998084001243114471435546875
v=123.566999999999999997057908984743335167877376079559326171875
To convert i.f, i've used a binary converter.
Related
Please help:
I have two edit boxes on my form. The first one I use to type in an amount. The second one I use to divide the amount with. The problem is I try a number with a decimal like 5.5 and I keep on getting the error: "'5.5' is not a valid integer value".
Here is the code that I use:
var igroei,ipen, iper : integer;
rgroei, rper : real;
begin
ipen := strtoint(edtpen.Text); //the amount enter like 35060
iper := strtoint(edtper.Text); // The number use for the percentage like 5.5
iper := iper div 100;
rgroei := ipen + iper;
pnlpm.Caption := floattostrF(rgroei,ffcurrency,8,2);
end;
Thank you
5.5 is indeed not a valid integer. It is a floating point value. Use StrToFloat() instead of StrToInt(), and use Extended instead of Integer for the variable type.
var
ipen, iper, rgroei : Extended;
begin
ipen := StrToFloat(edtpen.Text); //the amount enter like 35060
iper := StrToFloat(edtper.Text); // The number use for the percentage like 5.5
iper := iper / 100.0;
rgroei := ipen + iper;
pnlpm.Caption := FloatToStrF(rgroei, ffcurrency, 8, 2);
end;
You should read the following to get started:
Integer and floating point numbers: The different number types in Delphi
I have an event in a component (VCLZip) that uses the comp type, but to display the result as a string I think I need to convert the comp value to int64, but I can not find a way to do so. Is there a way to convert a comp value to int64? or is there a different way to display a comp value as a string with commas ... maybe Format?
function FormatKBSize( Bytes: Cardinal ): string;
{ Converts a numeric value into a string that represents the number expressed as a size value in kilobytes. }
var
arrSize: array [ 0 .. 255 ] of char;
begin
{ explorer style }
Result := '';
{ same formating used in the Size column of Explorer in detailed mode }
Result := ShLwApi.StrFormatKBSizeW( Bytes, arrSize, Length( arrSize ) - 1 );
end;
procedure TFormMain.VCLZip1StartZipInfo( Sender: TObject; NumFiles: Integer; TotalBytes: Comp;
var EndCentralRecord: TEndCentral; var StopNow: Boolean );
var
Tb: int64;
begin
InfoWin.Lines.Add( '' );
InfoWin.Lines.Add( 'Number of files to be zipped: ' + IntToStr( NumFiles ) + '...' );
Tb := TotalBytes; // <= this will not compile
Tb := Int64(TotalBytes); // <= this will not compile
InfoWin.Lines.Add( 'Total bytes to process: ' + FormatKBSize( Tb ) + '...' );
end;
Edit - this seems to work but is there a better way?
InfoWin.Lines.Add( Format( '%n', [ TotalBytes ] ) );
The Comp type is an integer type but it is classified as a real. Thereby the compiler may not allow you to cast it directly to an Int64, nor assign it. You have to convert it. Try to use Trunc() to convert it to an Integer type.
You may also try to use the absolute directive to have an Int64 variable share the same address as the Comp variable:
procedure TFormMain.VCLZip1StartZipInfo( Sender: TObject; NumFiles: Integer; TotalBytes: Comp;
var EndCentralRecord: TEndCentral; var StopNow: Boolean );
var
Tb: Int64 absolute TotalBytes;
It should work although I usually don't like it too much because a cast/conversion is easy to spot in code, an absolute declaration may not be seen easily if the code is long enough.
A third solution is to declare a record:
CompRec = record
I64: Int64;
end;
and then the cast works:
Tb := CompRec(TotalBytes).I64;
Whoever wrote VCLZip to use Comp should get a slap on the wrist for that. Comp is an old Object Pascal 64-bit Integer type. The author should have used Int64 instead. Even (older) Delphi documentation says as much:
The Comp (computational) type is native to the Intel CPU and
represents a 64-bit integer. It is classified as a real, however,
because it does not behave like an ordinal type. (For example, you
cannot increment or decrement a Comp value.) Comp is maintained for
backwards compatibility only. Use the Int64 type for better
performance.
To convert a Comp to an Int64, you have to convert the Comp to a Double first (which the compiler does support), then convert the Double to Int64.
How can i rewrite this C++ code in Delphi?
int *intim = new int[imsize];
unsigned char *grays = new unsigned char[imsize];
int *intim2 = intim;
How can I increment pointer like this:
*(intim++) = x;
In Delphi you can use pointers (like in C/C++) but usually you try to avoid it.
the code looks most like
uses
Types;
procedure Test();
var
intim: TIntegerDynArray;
grays: TByteDynArray;
P: PInteger;
i, s: Integer;
begin
// 'allocate' the array
SetLength(intim, imsize);
SetLength(grays, imsize);
// if you want to walk through the array (Delphi style)
for i := Low(intim) to High(intim) do intim[i] := GetValueFromFunction();
// or (read only access, sum the array)
s := 0;
for i in intim do Inc( s, i );
// or with pointers:
P := #intim[ 0 ];
for i := 0 to High(intim) do begin
P^ := x;
Inc( P ); // like P++;
end;
end;
The people who point out that you should use array types rather than a direct pointer manipulation done as you see it done above in C, are right, it is not idiomatic to Delphi to use dangerous pointer types when safe arrays are easier, can be verified faster, and are safer at runtime. However for the pedantic out there who want to avoid using the nice built in array types, it is possible, albeit stupid, to do so:
var
intim,intim2:PInteger;
x:Integer;
begin
x := 0;
intim := AllocMem(SizeOf(Integer)*imsize);
intim2 := intim;
// dereference pointer intim2, store something, then increment pointer
intim2^ := x;
Inc(intim2);
FreeMem(intim);
The best way would be to use arrays. If imsize is a constant, you want a static array, otherwise, you'll use a dynamic array. Here's the syntax for both:
Static:
var
intim: array[0..imsize - 1] of integer;
Dynamic:
var
intim: array of integer;
begin
setLength(intim, imsize);
//do something with intim
end;
As for grays, how you'll declare it depends on if you're using your array of "unsigned chars" as characters (a string) or as single-byte integers. If they're integers, you can declare an unsigned single-byte integer as byte, and declare an array (either static or dynamic) of them using the syntax described above. If they're characters, just use the string type.
And the pointer math is possible, but not recommended because it makes buffer overruns too easy. Instead, try declaring your other variable as an integer and use it as an index into the array. If you have bounds checking turned on, this will prevent you from going beyond the end of the array and corrupting memory.
type B02 = array [01..02] of byte ;
...
var b : B02;
...
//here i read from tcp socket
socket.ReadBuffer(b, 2);
The question is: how to convert B02 to an integer?
You could declare a Word/Smallint at the same memory location, like this:
var
b : B02;
myInt: smallint absolute B02;
Then again, is there any particular reason why you don't just create the smallint and pass it to ReadBuffer instead of an array? I don't know exactly what class you're using, but that looks a lot like the way you read from a TStream, and it'll accept variables of any type, along with a size in bytes. Why not just declare your buffer as the integer type you're looking for and cut out the middleman?
You can just cast it:
var
a: array[01..02] of Byte;
i: Integer;
begin
i := PWORD(#a)^;
end;
or if you need to change the byte order:
i := Swap(PWORD(#a)^);
If the data is being transmitted in "network" order (highest byte first) and not in "Intel" order (lowest byte first), you can do some byte shufling yourself.
uses
SysUtils;
var
b: B02;
w: word; //two bytes represent a word, not an integer
socket.ReadBuffer(b, 2);
WordRec(w).Hi := b[1];
WordRec(w).Lo := b[2];
Mghie suggested following approach in comments (and I agree with him):
uses Winsock;
var
w: word;
socket.ReadBuffer(w, 2);
w := ntohs(w);
First, apologies for my English, I hope it makes sense what I`ve written here. Now to my problem.
How can I get the string representation of the content type of a Variant using TypInfo.GetEnumName(). I have tried the following, without luck, I get a numeric representation.
myString := GetEnumName( TypeInfo(TVarType), TVarData(myVar).VType );
Thank you.
Just use the build-in Delphi function for getting the string representation of a Variant type.
var
MyVariantType: string;
MyVariant: Variant;
begin
MyVariant := 'Hello World';
MyVariantType := VarTypeAsText(VarType(MyVariant));
ShowMessage(MyVariantType); //displays: String
MyVariant := 2;
MyVariantType := VarTypeAsText(VarType(MyVariant));
ShowMessage(MyVariantType); //displays: Byte
end;
Quoting from the Delphi 2007 help:
Use GetEnumName to convert a Delphi enumerated value into the symbolic name that represents it in code.
That means that you can't use it for that purpose, as TVarData.VType is not an enumerated value, but an integer which is set to one of the constants in System.pas that are taken from the Windows SDK wtypes.h file. Look at the source of GetEnumName(), it does immediately return a string containing the value of the integer.
Edit:
is there any other way to get the string representation of TVarData.VType
You can determine the string representation manually. First you need to be aware of that there are several bits of information encoded in that integer, so a simple case statement or array lookup will not work. The lower 12 bits are the type mask, and the upper bits encode information about whether it is a vector or array type and whether it is given by reference or not. The important parts are:
const
varTypeMask = $0FFF;
varArray = $2000;
varByRef = $4000;
So you could do something like:
function VariantTypeName(const AValue: TVarData): string;
begin
case AValue.VType and varTypeMask of
vtInteger: Result := 'integer';
// ...
end;
if AValue.VType and varArray <> 0 then
Result := 'array of ' + Result;
if AValue.VType and varByRef <> 0 then
Result := Result + ' by ref';
end;
Since it's not an enum, you'll have to do it manually. Write something like this:
function VariantTypeName(const value: TVarData): string;
begin
case value.VType of
vtInteger: result := 'integer';
//and so on
end;
Or, since the values in System.pas are listed in order, you could try declaring a const array of strings and have your VariantTypeName function return the appropriate member of the array.
Here's a thought for Delphi versions that don't support VarTypeAsText: You could define a enumerate type yourself that follows the VType values:
type
{$TYPEINFO ON}
TMyVarType = (
varEmpty = System.varEmpty,
varNull = System.varNull,
// etc...
);
(Fill the unused enum slots too - see Why do I get "type has no typeinfo" error with an enum type for the reasoning behind this).
Next, use these functions to read the Variants' type as your own enumerate type :
function MyVarType(VType: TVarType): TMyVarType; overload;
begin
Result := TMyVarType(VType);
end;
function MyVarType(V: Variant): TMyVarType; overload;
begin
Result := TMyVarType(TVarData(V).VType);
end;
And then you can convert it to a string like this :
function VarTypeToString(aValue: TMyVarType): string;
begin
Result := GetEnumName(TypeInfo(TMyVarType), Ord(aValue));
end;