Delphi: using BigInts from a database - sql-server

I´m using Delphi 7 with devart dbExpress to connect to SQLServer.
The problem is that when I add a bigInt field to a ClientQuery it comes as TFMTBCDField.
And the TFMTBCDField don´t have a method to get the 64 bit value.
I can use the Field.AsVariant or the StrToInt64(Field.AsString) to pick this 64 bits value.
Is there a better way to pick/use this value?

Maybe add a TLargeIntField manualy to dataset, set it's FieldName to appropriate name and use such code:
SomeInt64Value := (qryMyQuery.FieldByName('blahblah') as TLargeIntField).AsLargeInt;
Do not remember exactly types, but it worked this way in Delphi6.

You can convert the BCD to Variant and than to int64 with VarFMTBcdCreate from unit FMTBcd.
Try this:
var value64 : int64;
...
value64 := VarFMTBcdCreate(Field.Value);

The data format for TFMTBCDField is the TBcd record from the FMTBcd unit. You can get that raw value by reading the field's Value or AsBCD properties.
Depending on what you need the value for, TBcd might be sufficient. That is, you might not need to convert it to an Int64. The FMTBcd unit provides functions to add, subtract, multiply, and divide TBcd values.
The unit provides no conversions to Int64. There are conversions to Variant, string, Currency, Double, and Integer. If we were going to write an Int64 conversion, the Integer conversion is probably a good place to start, so let's take a look at how it's implemented:
function BcdToInteger(const Bcd: TBcd; Truncate: Boolean = False): Integer;
var
ABcd: TBcd;
begin
if Truncate and (BcdScale(Bcd) > 0) then
NormalizeBcd(Bcd, ABcd, Bcd.Precision, 0)
else
ABcd := Bcd;
Result := StrToInt(BcdToStr(ABcd));
end;
So, the VCL itself doesn't provide any more direct way to convert a TBcd to an Integer than to go through string. Therefore, it looks like your idea to call StrToInt64 on the string version of the field is fine.

I dont have Delphi 7 installed here anymore, but looking in the help, I see you can get as Float (Double), like this:
function GetFieldAsInt64(Field: TField): Int64;
begin
Result:= Int64(Round(Field.GetAsFloat));
end;
And then, call the function:
var
Value: Int64;
begin
Value:= GetFieldAsInt64(MyFMTBCDField);
end;

Related

Sort issues with TArray<Myrectype> for large numbers

What is the reason why TArray.Sort<Mytype> does not work when I have large numbers in the comparison?
My code is as follows (Delphiy Tokyo):
Interface
Type
RCInd = record
Num : Integer;
Ger : Integer;
Confirmed : Boolean;
Total : Real;
End;
TArrInd = TArray<RCInd>;
Procedure SortInd (Var PArrayInd : TArrInd);
Implementation
Procedure SortInd (Var PArrayInd : TArrInd);
begin
TArray.Sort<RCInd>( PArrayInd,TComparer<RCInd>.Construct
( function (Const Rec1, Rec2 : RCInd) : Integer
begin
Result := - ( Trunc(Rec1.Total) - Trunc(Rec2.Total) );
end )
);
end;
......
When the values of Rec1.Total and Rec2.Total are within a Integer limits, this Sort works fine, BUT when values exceed Integer limits Sort procedure does not work! It generates a non sorted set of data in PArrayInd .
What is going on here?
The problem is one of overflow. The real values overflow the integer type.
The compare function is meant to return negative to indicate less than, positive to. Indicate greater than and zero to indicate equal. Your using arithmetic is the cause of your problem, leading as it does to overflow. Instead use comparison operators.
function(const Rec1, Rec2: RCInd): Integer
begin
if Rec1.Total < Rec2.Total then
Result := 1
else if Rec1.Total > Rec2.Total then
Result := -1
else
Result := 0;
end;
The problem in this question is trying to fit a real value into an integer, but even if you have integer data then arithmetic should not be used for compare functions. Consider the expression
Low(Integer) - 1
This results in overflow. As a general principle, always use comparison operators to implement compare functions.

Warning: function result variable of a managed type does not seem to be initialized

The task I have requires me to create two routines one of which reads in data from a terminal and the other outputs data to the terminal, and another two routines which utilize an array to loop through these two routines to perform them multiple times.
The issue I am having is that the terminal crashes after one run through of the ReadComputer function instead of looping multiple times. The compiler is also providing me the following warning:
"Warning: function result variable of a managed type does not seem to be initialized"
although after extensive research and due to the fact that no one uses pascal I cannot find a solution. Any help is much appreciated! :)
I have provided a copy of my code here for reference:
program CompupterProgram;
uses TerminalUserInput;
type
Computer = Record
id: integer;
manafacturer: String;
year: integer;
warranty: integer;
end;
type Computers = Array of Computer;
function ReadComputer(): Computer;
begin
ReadComputer.id := ReadInteger('PLease Enter Computer Id:');
ReadComputer.manafacturer := ReadString('PLease Enter Computer Manafacturer:');
ReadComputer.year := ReadInteger('PLease Enter Computer Year:');
ReadComputer.warranty := ReadInteger('PLease Enter Computer Warranty:');
result := ReadComputer;
end;
procedure WriteComputer(c: Computer);
begin
WriteLn('Computer ID: ', c.id);
WriteLn('Computer Manafacturer ', c.manafacturer);
WriteLn('Computer Year ', c.year);
WriteLn('Computer Warranty ', c.warranty);
ReadLn();
end;
function ReadAllComputers(count: Integer): Computers;
var i: Integer;
begin
for i := 0 to count do
begin
ReadAllComputers[i] := ReadComputer();
end;
result := ReadAllComputers;
end;
procedure WriteAllComputers(computerArray: Computers);
var i: Integer;
begin
for i:= 0 to (length(computerArray)) do
begin
WriteComputer(computerArray[i]);
end;
end;
procedure Main();
var computers: Array of Computer;
index: Integer;
begin
computers := ReadAllComputers(3);
WriteAllComputers(computers);
end;
begin
Main();
end.
Computers is a dynamic array, and you need to set its length before use in ReadAllComputers with SetLength().
All dynamic arrays are zero based, so you need to count from zero to Length(aDynArray)-1 in a couple of places. Or use the High(aDynArray) function to express the highest possible value of it's index.
Note: The Result use in ReadComputer is superfluous. Either use the function name or the Result variable to return the function result. The latter is to prefer, since code will be more clear.
In freepascal the Result variable is defined only in ObjFPC or Delphi mode.

Combobox value to code

I'm a Pascal newbie and I already read some stuff about it, but it is still pretty hard for me. I want to create a simple password generator, and adjust the number of characters.
I found a function that actually generates the random password for me, which is this:
function RandomPassword(PLen: Integer): string;
var
str: string;
begin
Randomize;
//string with all possible chars
str := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
Result := '';
repeat
Result := Result + str[Random(Length(str)) + 1];
until (Length(Result) = PLen)
end;
This is the code that prints the string to the Memo:
procedure TForm1.Button2Click(Sender: TObject);
begin
Memo1.Caption := RandomPassword(10);
end;
I also got a TComboBox, and I want to use the value from the combobox to chose the number of characters (6-32). The number of characters is in this case 10 but I want to use the value from the Combobox instead of a predetermined number. Who can help me? I'd appreciate it!
You can select the combobox value like this:
RandomPassword(StrToInt(ComboBox.Items[ComboBox.ItemIndex]))
ComboBox.ItemIndex returns the index of select combobox item
ComboBox.Items[] is used to select a item in the combobox.
StrToInt() is used cause the combobox value is a string and you have to change it to integer

Why do I lose precision when passing a c# double to a SQL procedure with varchar argument?

I am working with a SQL Server stored procedure that takes, among other things, a string as a parameter. It is expected that the string will represent a number. Normally, I would use a numeric type for this, but this procedure does something that requires it to be a number.
I am experiencing problems with round-off. In order to simplify the problem, I wrote this procedure:
CREATE PROCEDURE dbo.TestProc #s
VARCHAR(20)
AS
SELECT #s;
I call this procedure from C# code as follows.
var cmd = new SqlCommand()
{
CommandText = "TestProc",
CommandType = CommandType.StoredProcedure,
Connection = myConnection
};
cmd.Parameters.Add(new SqlParameter("#s", -157.7181));
var reader = cmd.ExecuteReader();
reader.Read();
// Should get -157.7181, but get -157.718 instead
var x = reader[0];
The result is off by one ten-thousandth.
However, I get the correct result when I use the code below to add the parameter.
cmd.Parameters.Add(new SqlParameter("#s", "-157.7181"));
In other words, explicitly using a string for the number in c# yields the correct answer, while allowing it to be converted to a string implicitly creates a round-off error.
I can readily see that the solution to my problem is to explicitly cast my double value to a string before adding the parameter, but my question is: Why?
How can this be explained?
My Visual Studio version and SQL Server version are both 2008.
Looks like that's SQLs default behavior when converting from a float value to a string. Look at this:
declare #f float = 157.7181
select #f, convert(varchar(20), #f), STR(#f, 18, 10)
resulting in:
(No column name) (No column name) (No column name)
157.7181 157.718 157.7181000000
From the Transact-SQL manual (https://msdn.microsoft.com/en-us/library/ms173773.aspx):
Values of float are truncated when they are converted to any integer type.
When you want to convert from float or real to character data, using the STR string function is usually more useful than CAST( ). This is because STR enables more control over formatting. For more information, see STR (Transact-SQL) and Built-in Functions (Transact-SQL).
Conversion of float values that use scientific notation to decimal or numeric is restricted to values of precision 17 digits only. Any value with precision higher than 17 rounds to zero.

Ada: packing record with variable sized array

I am looking to create a packed record that can hold an array the varies in length from 5 - 50 elements. Is it possible to do this in such a way that the record can be packed with no wasted space? I will know how many elements will be in the array when I go to create the record.
-- the range of the array
type Array_Range_T is Integer range 5 .. 50;
-- the array type
type Array_Type_T is array range (Array_Range_T) of Integer;
-- the record
type My_Record_T (Array_Length : Integer := 5) is
record
-- OTHER DATA HERE
The_Array : Array_Type_T(Array_Length);
end record;
-- Pack the record
for My_Record_T use
record
-- OTHER DATA
The_Array at 10 range 0 .. Array_Length * 16;
end record;
for My_Record_T'Size use 80 + (Array_Length * 16);
This obviously won't compile, but shows the spirit of what I am trying to do. If possible I would like to keep the length of the array out of the record.
Thank you!
There really isn't a way in Ada to represent the record the way you're asking for. However, since your concern really isn't with how the record is represented in memory, but rather with how it's transmitted to a socket, you probably don't need to worry about record representation clauses.
Instead, you can define your own Write routine:
procedure Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : in My_Record_T);
for My_Record_T'Write use Write;
or, I believe this will work in Ada 2012:
type My_Record_T is record
...
end record
with Write => Write;
procedure Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : in My_Record_T);
and then the body will look like
procedure Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : in My_Record_T) is
begin
-- Write out the record components, EXCEPT Array_Length and The_Array.
T1'Write (Stream, Item.F1); -- F1 is a record field, T1 is its type
T2'Write (Stream, Item.F2); -- F2 is a record field, T2 is its type
...
-- Now write the desired data
declare
Data_To_Write : Array_Type_T (1 .. Item.Array_Length)
renames Item.The_Array (1 .. Item.Array_Length);
-- I'm assuming the lower bound is 1, but if not, adjust your code
-- accordingly
begin
Array_Type_T'Write (Stream, Data_To_Write);
-- Note: using 'Write will write just the data, without any bound
-- information, which is what you want.
end;
end Write;
This won't work if the other components need to be packed, though, e.g. if you want to write a byte to the socket that contains one 3-bit record component and one 5-bit record component. If that's necessary, I don't think the built-in 'Write attributes will do that for you; you may need to do your own bit-twiddling, or you could get tricky and define an array of Stream_Elements and use an Address clause or aspect to define an array that overlays the rest of the record. But I wouldn't use the overlay method unless I were 100% certain that the reader at the other end of the socket were an Ada program that uses the exact same type definition.
Note: I haven't tested this.
Not sure I completely understand what you're trying to achieve but can't you do something like this
-- the range of the array
type Array_Range_T is range 1 .. 50;
-- the array type
type Array_Type_T is array (Array_Range_T range <>) of Integer;
Array_Length : constant := 5; --5 elements in the array
-- the record
type My_Record_T is
record
-- OTHER DATA HERE
The_Array : Array_Type_T (1 .. Array_Length);
end record;
-- Pack the record
for My_Record_T use
record
-- OTHER DATA
The_Array at 0 range 0 .. (Array_Length * Integer'Size) - 1 ;
end record;
for My_Record_T'Size use (Array_Length * Integer'Size);

Resources