How to prevent wrong inputs (only numbers) in Delphi? - delphi

I am trying to build a function/check to prevent wrong inputs from keyboard and I am a little bit lost here.
function ReadInputs : integer;
var
number : integer;
begin
repeat
Write('Set random number (1-10): ');
Readln(number);
if NOT((number <= 10) AND (number >= 1)) then
begin
Writeln('Error! Type 1-10!');
end;
until (number >= 1) AND (number <= 10);
result := column;
end;
How to prevent from any other character to be input except numbers 1-10? Why only numbers define in my function is not enough even when I set integer? When I type for example "A" it crash, so what is the right way? Thank you.

As it stands your program will fail with an error if the user inputs something that cannot be converted to an integer. That's because the variable that you passed to Readln is typed as an Integer. That is effectively an assertion that the user enters a number. But you want to be more flexible than that and allow the user to recover from non-numeric input.
What you need to do is read a string. This will always succeed. Then you can decide how to handle that string. For example you would try to convert to integer, and if that succeeded, perform further validity checks.
Perhaps like this:
var
Input: string;
Num: Integer;
....
Readln(Input);
if TryStrToInt(Input, Num) then
// perform checks on Num, etc.
else
// handle error: the value input was not numeric

You've already had a good answerfrom David H, but a little more explanation might help.
The ReadLn() procedure dates from before applications had GUIs and doesn't really restrict what the user can type in; the user might just press [return] or type characters that aren't digits (or +/-). ReadLn(AnInteger) will succeed if what the user types happens to convert to an integer, otherwise it fails.
On the other hand, Readln(AString) will always succeed, and the problem then is just how to check that it represents an integer, and DH's answer shows you how to do that.
In case you're wondering, a GUI application, you can control what characters an edit control will accept, e.g. by using a TMaskEDit, which allows you specify what character patterns are acceptable (e.g 6 digits and nothing else) - if the user types something which doesn't match the mask, the edit control doesn't accept it. However, even if you use a TMaskEdit, it's best to check that what's been typed in actually converts to the number type you're wanting.

Or you could use this on the OnKeyPress event:
if NOT(key in['0'..'9', #8]) then
key := #0;

Related

Constant single using the hex memory value in Delphi

How can I make a single constant based on a hex value where that hex value is an unsigned integer and the raw memory for the single. I would like to do something like this but it doesn't compile and this will try and cast the hex value to a single and then store the result of that cast instead of storing hex value itself:
LARGEST_SINGLE_LESS_THAN_ZERO = Single($80800000);
I get a "Invalid Typecast" error.
For example:
The single value for 1 is stored as $3F800000 in memory. I would like to be able to create a const that lets me set the value using $3F800000 instead of 1.
I have also tried other variations such as this without luck:
LARGEST_SINGLE_LESS_THAN_ZERO = PSingle(#$80800000)^;
Background
I have a method that I use to get the next smallest single when provided with a single value:
type
PInt32 = ^Int32;
function NextBefore(const aValue: Single): Single;
var
int32Value: Int32;
begin
// this function ignores special values nan/inf
int32Value := PInt32(#aValue)^;
if (UInt32(int32Value) = $80000000) or (int32Value = 0) then
begin
// special handling needed for -0 and 0. We need to go to the smallest
// negative number.
int32Value := $80800000;
end
else
begin
if int32Value >= 0 then
Dec(int32Value)
else
Inc(int32Value);
end;
Result := PSingle(#int32Value)^;
end;
This is really useful because we use vector operations that can only do a > or < so we use it to do the equivalent of a >= and a <=. We often check against 0. So where we need get all of the data >= 0 we do something like this:
MyVector.ThresholdGT(NextBefore(0));
It would be nicer to provide the other developers with a constant for these types of operations. Trying to use the PSingle format below won't work because the number is not a variable.
In order to declare a single constant with a hex value in such a way that it cannot be altered by code, it can be done in two steps:
const
iLARGEST_SINGLE_LESS_THAN_ZERO : Int32 = $80800000;
var
LARGEST_SINGLE_LESS_THAN_ZERO : Single absolute iLARGEST_SINGLE_LESS_THAN_ZERO;
Trying to change the value of LARGEST_SINGLE_LESS_THAN_ZERO will give a compiler error: Left side cannot be assigned to.
It's hard to do this cleanly with the constraints of the language. Perhaps the best that you can do is to make a variant record type that has both integer and single fields overlapped.
type
TSingleIntegerVariantRec = record
case Integer of
0: (I: Integer);
1: (S: Single);
end;
Once you have that type available you can declare typed constants using the integer field, but then read the single field.
const
LARGEST_SINGLE_LESS_THAN_ZERO: TSingleIntegerVariantRec = (I: $80800000);
....
MyVector.ThresholdGT(LARGEST_SINGLE_LESS_THAN_ZERO.S);
If you want to add an extra nuance you could implement an implicit cast operator to Single which would allow you to omit the .S. If you made that operator inline then I suspect the emitted code would be very efficient.
This does what you ask, but I wouldn't claim that it was very elegant. We're I you I would move the code to use the next value down into the library function so that you can pass 0 and shield the consumer of the library from these implementation details.
In other words you would add a ThresholdGTequal method that was implemented like this:
procedure TMyVector.ThresholdGTequal(const Value: Single);
begin
ThresholdGT(NextBefore(Value));
end;
Then the consumers of this code simply write:
MyVector.ThresholdGTequal(0);
and remain oblivious to all of the gnarly implementation details.

Converting Integer number into hexadecimal number in delphi 7

Write a program to convert an integer number to its hexadecimal representation without using inbuilt functions.
Here is my code, but it is not working. Can anyone tell where is the mistake?
It is giving an error:
"Project raised exception class EAccessViolation with message 'Access violation at address 00453B7B in module 'Project.exe'.Write of address FFFFFFFF'.Process stopped.Use Step or Run to continue."
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,Forms,
Dialogs;
type
TForm1 = class(TForm)
end;
function hexvalue(num:Integer):Char;
var
Form1: TForm1;
implementation
{$R *.dfm}
function hexvalue(num:Integer):Char;
begin
case num of
10: Result:='A';
11: Result:='B';
12: Result:='C';
13: Result:='D';
14: Result:='E';
15: Result:='F';
else Result:=Chr(num);
end;
end;
var
intnumber,hexnumber,actualhex:String;
integernum:Integer;
i,j,k:Byte;
begin
InputQuery ('Integer Number','Enter the integer number', intnumber);
integernum:=StrToInt(intnumber);
i:=0;
while integernum >= 16 do
begin
hexnumber[i]:=hexvalue(integernum mod 16);
integernum:= integernum div 16;
Inc(i);
end;
hexnumber[i]:= hexvalue(integernum);
k:=i;
for j:=0 to k do
begin
actualhex[j]:= hexnumber[i];
Dec(i);
end;
ShowMessage(actualhex);
end.
Since this obviously is a homework assignment, I don't want to spoil it for you and write the solution, but rather attempt to guide you to the solution.
User input
In real code you would need to be prepared for any mistake from the user and check that the input really is integer numbers only and politely ask the user to correct the input if erroneous.
Conversion loop
You have got that OK, using mod 16 for each nibble of integernum and div 16 to move to the next nibble, going from units towards higher order values.
Conversion of nibble to hex character
Here you go wrong. If you would have written out also the cases for 0..9, you could have got the case statement right. As others have commented, Chr() takes an ASCII code. However, using a case statement for such a simple conversion is tedious to write and not very efficient.
What if you would have a lookup table (array) where the index (0..15) directly would give you the corresponding hex character. That would be much simpler. Something like
const
HexChars: array[_.._] of Char = ('0',_____'F')
I leave it to you to fill in the missing parts.
Forming the result (hex string)
Your second major mistake and the reason for the AV is that you did not set the length of the string hexnumber before attempting to acess the character positions. Another design flaw is that you fill in hexnumber backwards. As a result you then need an extra loop where you reverse the order to the correct one.
There are at least two solutions to solve both problems:
Since you take 32 bit integer type input, the hex representation is not more than 8 characters. Thus you can preset the length of the string to 8 and fill it in from the lower order position using 8 - i as index. As a final step you can trim the string if you like.
Don't preset the length and just concatenate as you go in the loop hexnumber := HexChars[integernum mod 16] + hexnumber;.
Negative values
You did not in any way consider the possibility of negative values in your code, so I assume it wasn't part of the task.
First mistake : String are 1 indexed. Meaning that the index of their first character is 1 and not 0. You initialize "i" to 0 and then try to set hexnumber[i].
Second mistake : Strings might be dynamic, but they don't grow automatically. If you try to access the first character of an empty string, it won't work. You need to call SetLength(HeXNumber, NumberOfDigits). You can calculate the number of digits this way :
NumberOfDigits := Trunc(Log16(integernum)) + 1;
Since Log16 isn't really something that exists, you can either use LogN(16,integernum) or (Log(IntegerNum) / Log(16)) depending on what is available in your version of Delphi.
Note that this might return an invalid value for very, very large value (high INT64 range) due to rounding errors.
If you don't want to go that road, you could replace the instruction by
hexnumber := hexvalue(integernum mod 16) + hexnumber;
which would also remove the need to invert the string at the end.
Third Mistake : Using unsigned integer for loop variable. While this is debatable, the instruction
for I := 0 to Count - 1 do
is common practice in Delphi without checking Count > 0. When count = 0 and using an unsigned loop counter, you'll either get an integer overflow (if you have them activated in your project options) or you'll loop High(I) times, which isn't what you want to be doing.
Fourth mistake : already mentionned : Result:=Chr(num) should be replaced by something like Result := InttoStr(Num)[1].
Personally, I'd implement the function using an array.
HexArr : Array[0..15] of char = ('0', '1',...,'D','E','F');
begin
if InRange(Num, 0, 15) then
Result := HexArr[Num]
else
//whatever you want
end;

Validating TEdit input, by comparing it with an integer value

I have developed an application that randomly creates two integers and then adds them. The sum of the two integers must be worked out by a user and input in TEdit.
When I use if statement to validate the answer(in TEdt) I get an error.
the code snipped:
answer := int1+int2;
UserAnswer:=StrToInt(edtAnswer.Text);
if(UserAnswer=answer)then
lblRemark.Caption:= 'Correct Answer';
When run the application I get an a convertError "Error...Not a valid integer".
When StrToInt meets a string that cannot be converted to an integer, it raises an exception. The documentation says:
If S does not represent a valid number, StrToInt raises an EConvertError exception.
You can catch that exception.
try
Value := StrToInt(Str);
except
on EConvertError do
// handle conversion error
end;
However, this is rather messy code. As a broad rule, exceptions should really be exceptional. They should not be part of the normal flow of your program. Another broad rule is that you should not handle exceptions. Let them propagate upwards to something that can handle them.
So, if we are breaking both of these rules, how can we do better? Well, by using TryStrToInt. This is a function that returns a Boolean to indicate whether or not the conversion succeeded. The code would then go like this:
if TryStrToInt(Str, Value) then
// handle conversion success by using Value
else
// handle conversion error
Assuming Int1 and Int2 are 'reasonable' for a user (eg. positive only) you can also use
Value := StrToIntDef( Str, -1 {or other illegal value});
Makes the code slightly simpler, maybe.

How to get the first element in a string?

I'm trying to figure out a way to check a string's first element if it's either a number or not.
if not(myString[0] in [0..9]) then //Do something
The problem is that I get an error "Element 0 inaccessible - use 'Length' or 'SetLength"
Another way came to my head from my C-like exprieince - convert the first element of the string to char and check the char,but there is no difference in the compile errors.
if not(char(myString[0]) in [0..9]) then //Do something
How do I accomplish it?
Strings are 1-based:
if not (myString[1] in ['0'..'9']) then // Do something
Pascal and Delphi indexes string from 1. This is a legacy from time where zero byte contained length, while next 255 (index 1 to 255) contained actual characters.
Joel Spolsky wrote quite good article on string issues:
http://www.joelonsoftware.com/articles/fog0000000319.html
Delphi strings use a 1-based index, so just rewrite to
if not(myString[1] in ['0'..'9']) then //Do something
Also take note of the quotes around the 0..9, otherwise you would be comparing characters to integers.
We should keep in mind some things:
String in Delphi is 0-based for mobile platforms and 1-based for Windows.
String in old versions of Delphi is AnsiString (1-byte per char) and WideString in new versions (2-bytes per char).
Delphi supports set of AnsiChar, but doesn't support set of WideChar.
So if we want to write a code compatible with all versions of Delphi, then it should be something like this:
if (myString[Low(myString)]>='0') and (myString[Low(myString)]<='9') then
// Do something
if not(myString[0] in [0..9]) then //Do something
If you're using Delphi 2009, the TCharacter class in Character.pas has functions like IsDigit to help simplify these kinds of operations.
Once you fix the indexing, of course. :)
With later updates to Delphi mobile code, the bottom string index changed from 0 to 1. When you compile older programmes, they compile and run correctly using 0 starting index. Programmes created with the later IDE produce an error. When you have mixtures, life gets complex!
It would be good to be able to take an older programme and tell the IDE that you want it brought up to date (maybe this would fix other things, like fonts getting scrambled when you answer a phone call!) but it would be good to get things consistent!
The simplest way to check to see if the first character of string is an integer, and then dispatch:
var
iResult : integer;
begin
if TryStrToInt( mySTring[1], iResult) then
begin
// handle number logic here iResult = number
end
else
begin
// handle non number logic here
end;
end;
I use a utility function to test the entire string:
function IsNumeric(const Value: string): Boolean;
var
i: Integer;
begin
Result := True;
for i := 1 to Length(Value) do
if not (Value[i] in ['0'..'9','.','+','-']) then
begin
Result := False;
Break;
end;
end;
The above code is for Delphi versions prior to 2007. In 2007 and 2009, you could change the integer variable i to a character c, and use for c in Value instead.
To test for integers only, remove the '.' from the set of characters to test against.
This is incorrect. ISO strings and older Pascal's also started at one. It is just a general convention, and afaik the s[0] thing is a result of that being vacant, and cheap to code in the UCSD bytecode interpreter. But that last bit is before my time, so only my guessing.
It results from the Pascal ability to have arbitrary upper and lower bounds, which provides for more typesafety accessing arrays.
Really old Pascal strings (till early eighties) strings were even worse than C ones btw. Multiple conventions were in used, but all were based on static arrays (like early C), but they were typically space padded, so you had scan back from the end till the spaces ended.
(removed the legacy tag, since being 1 based is not legacy. Accessing s[0] as length IS legacy, but that is not what the question is about)
Foreach element in strName
if not element in [0-9] then
do something
else
element is a digit
end if
Don't forget the quote between digits number.

Getting a Field List from a DBExpress TSQLQuery

I am having a problem getting a list of fields from a query defined at run time by the users of my program. I let my users enter a SQL query into a memo control and then I want to let them go through the fields that will return and do such things as format the output, sum column values and so forth. So, I have to get the column names so they have a place to enter the additional information.
I would do fine if there were no parameters, but I also have to let them define filter parameters for the query. So, if I want to set the parameters to null, I have to know what the parameter's datatype is.
I am using Delphi 2006. I connect to a Firebird 2.1 database using the DBExpress component TSQLConnection and TSQLQuery. Previously, I was successful using:
for i := 0 to Qry.Params.Count - 1 do Qry.Params[i].value := varNull;
I discovered I had a problem when I tried to use a date parameter. It was just a coincidence that all my parameters up until then had been integers (record IDs). It turns out that varNull is just an enumerated constant with a value of 1 so I was getting acceptable results (no records) was working okay.
I only need a list of the fields. Maybe I should just parse the SELECT clause of the SQL statement. I thought setting Qry.Prepared to True would get me a list of the fields but no such luck. It wants values for the parameters.
If you have an idea, I would sure like to hear it. Thanks for any help.
Replied again 'coz I'm interested. My methods works (with my queries) because they have been pre-defined with the params' datatypes preset to the correct type:)
I'm not sure how you are expecting the query to know or derive the datatype of the param given that you are not even selecting the field that it operates against.
So I think your query setup and user input method will need more attention. I've just looked up how I did this a while ago. I do not use a parameterised query - I just get the "parameter values" from the user and put them directly into the SQL. So your sql would then read:
SELECT s.hEmployee, e.sLastName
FROM PR_Paystub s
INNER JOIN PR_Employee e ON e.hKey = s.hEmployee
WHERE s.dtPaydate > '01/01/2008'
therefore no parameter type knowledge is necessary. Does not stop your users entering garbage but that goes back to input control :)
Although a slightly different dataset type this is what I use with TClientDataset simple and effective :)
for i := 0 to FilterDataSet.Params.Count -1 do
begin
Case FilterDataSet.Params.Items[i].Datatype of
ftString:
ftSmallint, ftInteger, ftWord:
ftFloat, ftCurrency, ftBCD:
ftDate:
ftTime:
ftDateTime:
.
.
.
end;
end;
can you not do something similar with the query?
You guys are making this way too hard:
for i := 0 to Qry.Params.Count - 1 do begin
Qry.Params[i].Clear;
Qry.Params[i].Bound := True;
end;
I'm not sure what version of Delphi you are using. In the Delphi 2006 help under Variant Types, it says:
Special conversion rules apply to the
Borland.Delphi.System.TDateTime type
declared in the System unit. When a
Borland.Delphi.System.TDateTime is
converted to any other type, it
treated as a normal Double. When an
integer, real, or Boolean is converted
to a Borland.Delphi.System.TDateTime,
it is first converted to a Double,
then read as a date-time value. When a
string is converted to a
Borland.Delphi.System.TDateTime, it is
interpreted as a date-time value using
the regional settings. When an
Unassigned value is converted to
Borland.Delphi.System.TDateTime, it is
treated like the real or integer value
0. Converting a Null value to Borland.Delphi.System.TDateTime raises
an exception.
The last sentence seems important to me. I would read that as varNull cannot be converted to a TDateTime to put into the field, and hence you get the exception that you're experiencing.
It also implies that this is the only special case.
Couldn't you do something like:
for i := 0 to Qry.Params.Count - 1 do
begin
if VarType(Qry.Params[i].value) and varTypeMask = varDate then
begin
Qry.Params[i].value := Now; //or whatever you choose as your default
end
else
begin
Qry.Params[i].value := varNull;
end;
end;
What I ended up doing was this:
sNull := 'NULL';
Qry.SQL.Add(sSQL);
for i := 0 to Qry.Params.Count - 1 do begin
sParamName := Qry.Params[i].Name;
sSQL := SearchAndReplace (sSQL, ':' + sParamName, sNull, DELIMITERS);
end;
I had to write SearchAndReplace but that was easy. Delimiters are just the characters that signal the end of a word.
TmpQuery.ParamByName('MyDateTimeParam').DataType := ftDate;
TmpQuery.ParamByName('MyDateTimeParam').Clear;
TmpQuery.ParamByName('MyDateTimeParam').Bound := True;

Resources