I'm just starting out with Object Pascal for my computer studies so this is probably an easy question for many of you here. I'm trying to build a verification system for a Sum and Average Calculator so that answers which are not integers cannot be accepted but also don't crash the software. I've been trying for hours to get a solution for this and whilst it's in its current state, if I input an integer it would interpret it as a noninteger, whilst if I input a noninteger the program just crashes. Is there anyway around this?
The coding currently looks like this:
Program SumAverageCalculator;
{$APPTYPE CONSOLE}
uses
SysUtils;
Const
NumberOfIntegers = 3;
Var
NumberOne, NumberTwo, NumberThree: integer;
Sum: integer;
Average: real;
Begin
Writeln ('=======================================');
Write ('What is your first number? '); readln(NumberOne);
If NumberOne-sqr(0) <> 1 then
Begin
Write ('Please write an integer only. What is your first number? '); readln(NumberOne);
End
Else
Begin
Write ('Great, that is an Integer! ');
End;
Write ('And the second number? '); readln(NumberTwo);
If NumberTwo-sqr(0) <> 1 then
Begin
Write ('Please write an integer only. What is your second number? '); readln(NumberOne);
End
Else
Begin
Write ('Great, that is an Integer! ');
End;
Write ('And the third number? '); readln(NumberThree);
If NumberThree-sqr(0) <> 1 then
Begin
Write ('Please write an integer only. What is your third number? '); readln(NumberOne);
End
Else
Begin
Write ('Great, that is an Integer! ');
End;
Sum := NumberOne + NumberTwo + NumberThree;
Average := Sum/NumberOfIntegers;
Writeln;
Writeln ('=======================================');
Writeln ('The number of given integers was ', NumberOfIntegers);
Writeln ('Your first number was ', NumberOne);
Writeln ('Your second number was ', NumberTwo);
Writeln ('Your third number was ', NumberThree);
Writeln ('=======================================');
Writeln ('The Sum of your numbers is ', Sum);
Writeln ('The Average of your numbers is ', Average: 1:2);
Writeln ('=======================================');
Readln;
End.
Thank you for any help given. :)
This is really because you passed an integer variable to the readln call, and it really wants to put an integer there. If it can't (the input is not an integer), it will crash. A solution is to first read the input in the most general form possible, that is, a string, check that it is an integer, and then convert it to one.
Of course, you don't have to do all that yourself. The sysutils unit has some helpful functions, and among them the TryStrToInt function, which does what it says: it will try to convert a string input to an integer, and will let you (the developer) know if it fails instead of crashing and burning.
uses
SysUtils;
Var
Input: String;
IsInteger: Boolean;
Value: Integer;
begin
Write('Enter an integer: ');
ReadLn(Input); // will work, user input can always be represented by a string
IsInteger := TryStrToInt(Input, Value);
if IsInteger then
begin
// Do stuff with "Value" which contains the input integer
end
else
begin
WriteLn('Sorry, that''s not an integer.');
end;
end.
Of course, if you're going to be doing this often, it may make sense to implement a helper function that acts like readln but does the checking itself and prints out an error without crashing (perhaps the program could keep asking for the integer until the user complies, or perhaps it should gracefully terminate). For instance, some of the code above could be wrapped up in a utility function readint.
Once you come across exceptions, you'll find a more general way to handle failures in your program and respond to them properly to avoid your program crashing on the slightest user error, however at this point this is probably what you are looking for.
If you are wondering what the out thing means in the TryStrToInt function, it's similar to var, but it basically means "I am going to be filling out this value, but I won't try to read it" (a write-only parameter) unlike var which means "I am going to fill out this value, but I might try to read it before" (a read-write parameter). So an out parameter need not be initialized before being used (so in a way, an out parameter is a "second return value", which makes sense in this case since the TryStrToInt function needs to return two things: whether the input was an integer, and what that integer was, but functions can only have one "standard" return value).
Related
I have an issue with my database.
Table structure is:
Table name: sales
sale_id (autoincrement)
date (datetime)
total (decimal)
etc.
I have 2 computers, one is "the server" and the other is "the client", when I Insert in "sales" sometimes the database saves more than 1 record, it's an issue kind of random because one day could be normal just save 1 record as is but other day could save 2 or more duplicates.
My code is:
qry1.SQL.Text := 'SELECT * FROM sales '
+ 'WHERE sale_id = 1';
qry1.Open;
qry1.Insert;
qry1.FieldByName('date').AsDateTime := Date;
qry1.FieldByName('total').AsFloat := total;
qry1.Post;
saleId := qry1.FieldByName('sale_id').AsInteger;
qry1.Close;
// Code to save sale details using saleId.
I'm using Delphi 10.3 + ZeosLib 7.2.6-stable + MySQL 8.0
I opened the ports in the server so I have a direct connection to MySQL, I don't know what could be happening
Hope you can help me
Update----
Thanks for your kind answers,
#nbk Yes, I did it already.
#A Lombardo I used "where" to get just 1 record and then I use the query to insert the new one similar to use TTable but instead of load the hole table I just get one record and I can insert (qry.Insert),
#TheSatinKnight not only I get two records, sometimes I get 3 or more, but makes sense probably the keayboard is not working well and could send "enter" key more than once.
#fpiette, I will do ti right now.
I will keep you posted.
There are better ways to accomplish an insert than to open a TZTable and inserting on that open table.
As another approach, drop 2 TZQuery (NOT TZTable) on your form (which I'll assume is TForm1 - change as appropriate).
Assuming the name is ZQuery1 and ZQuery2.
Set its connection property the same as your TZTable, so it uses the same connector.
Set ZQuery1.SQL property to 'Insert into sales (date, total) values (:pdate, :ptotal)' //(w/o quotes)
Set ZQuery2.SQL property to 'select last_insert_id() as iddb'
now add the Function below to your form's Private delcaration
TForm1 = class(TForm)
ZQuery1: TZQuery; //added when dropped on form
ZQuery2: TZQuery;
private
{ Private declarations }
function AddNewSale(SaleDate: TDateTime; Total: Double): Integer; //add this line
public
{ Public declarations }
end;
and then add the following code to your form's methods
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.AddNewSale(SaleDate: TDateTime; Total: Double): Integer;
begin
ZQuery1.ParamByName('pdate').AsDateTime := SaleDate;
ZQuery1.ParamByName('ptotal').AsFloat := Total;
ZQuery1.ExecSQL; //*Execute* the Insert - Only "open" SQL that returns a result set
//now the record has been added to your DB
if ZQuery1.RowsAffected = 1 then //check to ensure it was inserted
begin
ZQuery2.Open;
try
Result := ZQuery2.FieldByName('iddb').AsInteger;
finally
ZQuery2.Close;
end;
end
else
result := -1;//indicate error by returning negative value
end;
now in the place you want to insert the record, simply call this function:
var
ReturnValue: Integer;
begin
ReturnValue := AddNewSale(Date, total);
if ReturnValue < 0 then
//error happened
else
begin
//Everything worked
end;
end;
Thanks again for all your kind answers.
At the end the problem was keyboard, It had a problem with "Enter" key, so when you pressed it, it send more than one pulsation so #TheSatinKnigh your approach was correct
#fpiette I created the log file and I figured out as you said the request had been executed twice or more.
I know maybe it is a silly thing for a programmer because I was disabling the button to late, sorry for that
#A Lombardo thanks for you code I like it better than mine I will use it
I am a beginner and using pascal and i am trying this problem that i attach. What should i do next? I am trying to make this program work by showing the "ayam" Function. But i don't know how to code the rest.
function ayam(a, b:integer) :
integer;
var
m: integer;
begin
readln(a) ;
readln(b) ;
readln(m) ;
begin
if (b=0) then
ayam:= 1 else
ayam:= a * ayam(a, b-1) mod m;
end;
writeln(ayam);
end;
The first thing I should say is that when a question is to do with coursework, as is obviously the case here, there is an unwritten convention that we do not write an answer which simply does your programming task for you. So in the following, I have not commented on whether the code inside your ayam function actually correctly implements what is required - that is for you to do.
Secondly, you did not say which Pascal implementation you are using. I have written the code of this answer in Lazarus, the IDE for FreePascal (aka FPC), which is the best of the freeware Pascal implementations currently available.
The first thing to do with your code is to re-format it the way it should be formatted,
using indentation of statements to reflect the structure of the steps you are
trying to code. Basically, your eye should be able to follow the code structure
as easily as possible. This gives something like this:
function ayam(a, b:integer) : integer;
var
m: integer;
begin
readln(a) ;
readln(b) ;
readln(m) ;
begin
if (b=0) then
ayam:= 1
else
ayam:= a * ayam(a, b-1) mod m;
end;
writeln(ayam);
end;
With that change made, it is pretty obvious that there is something wrong with
the logic of your code, because a and b are declared as input variables to
the ayam function but your code actually reads in their values via the
readln statements inside the function. This is what made me think that you think
you have done all that's necessary to write your program. In fact, you have not. What you are missing
is some code which sets up the input parameters a and b (and m, see below) in some way and then
invokes your ayam function to calculate its value and then does something with
the result. At the moment, your code simply defines ayam but takes no steps
to use it. So, to get your ayam function to do anything, you need to write a complete
Pascal program that uses it. Something like this:
program ayamprogram;
{ the following declares some variables for the program to use }
var
varA,
varB,
varM,
varResult : Integer;
{ next, we define the ayam function }
function ayam(a, b, m:integer) : integer;
begin
begin
if (b=0) then
ayam:= 1
else
ayam:= a * ayam(a, b-1, m) mod m;
end;
end;
{ now we write the code of the program, which reads in the values to be
used by ayam, then invokes ("calls") ayam to calculate its value and
assign its value to the varResult variable and then outputs the calculated
result using the writeln statement
}
begin
{ this is the code of the program }
readln(varA);
readln(varB);
readln(varM);
varResult := ayam(varA, varB, varM);
writeln('function ayam evaluates to ', varResult);
readln; { this causes the program to pause so you can see the result of the writeln statement }
end. { end of program }
Note that I have used different variable names for the variables which are supplied to ayam, varA, varB, varM, from the variable names used inside ayam to avoid confusion between them.
Note also that as it is bad practice to read user input inside an executing function, the value to be used for M is read in before ayam is called and is supplied to ayam as a third parameter.
A point you need to consider regarding the expression
ayam:= a * ayam(a, b-1, m) mod m
is whether the mod m should operate on the value of ayam(a, b-1, m) or on the entire expression including the a * as well; if so, parentheses can enforce the evaluation order you need, by writing
ayam:= (a * ayam(a, b-1, m)) mod m
I've just started learning Ada and I cannot figure out how to keep the program running when the user input is beyond the declared range of a variable. I'd like to print info about bad range of input and then ask user for input again.
This is my simple code:
with Ada.Text_IO;
use Ada.Text_IO;
procedure Main is
type Score is range 0..100;
a : Score;
begin
Put_Line ("Enter value range 0-100: ");
a := Score'Value(Get_Line);
if a'Valid then
Put_Line ("You entered" & Score'Image (a));
else
Put_Line ("Bad range of input");
end if;
end Main;
Shouldn't I use the "range" in order to check the input, but rather some if's with >, < restrictions?
My other approach was to try this with exceptions, but it also doesn't work as I want it to:
with Ada.Text_IO;
with Ada.IO_Exceptions;
use Ada.Text_IO;
procedure Main is
type Score is range 0..100;
a : Score;
begin
loop
begin
Put_Line ("Enter value range 0-100: ");
a := Score'Value(Get_Line);
Put_Line ("You entered" & Score'Image (a));
exit;
exception
when Ada.IO_Exceptions.Data_Error =>
Put_Line("Bad range of input");
end;
end loop;
end Main;
I believe the problem is in my lack of understanding this language, but I hope there is some kind of easy solution for this, thanks for any help.
Now you know a magical incantation that works, but I doubt you understand why it works, or why your other incantations didn't work. I will go into painful pedagogical detail about that, in hopes that some of it might be useful or of interest.
In Ada, when you declare a type, the type is anonymous, and the name (Score) you give is the name of the first-named subtype. The first-named subtype may have constraints that don't apply to the anonymous base type. For some types, including integer types, it's possible to refer to the anonymous base type with 'Base.
Since you declared Score using range, it is a signed integer type and its base type is (roughly) symmetrical around zero. So your declaration is equivalent to something like
type Score'Base is range -128 .. 127;
subtype Score is Score'Base range 0 .. 100;
(this is not Ada and will not compile).
Score'Value returns a value of Score'Base (ARM 3.5 (53)), so if you input "101" or "-3", Score'Value will succeed and return the appropriate value. When you assign that value to your variable of subtype Score, a check is performed that the value is in the range of Score; when that fails, Constraint_Error is raised. If you input an invalid image, such as "200" or "xyz", Score'Value fails and raises Constraint_Error. So you have two kinds of incorrect input resulting in two different failures, both of which happen to raise the same exception.
Your first version failed because you never got to the if statement. Your second version failed because Ada.Text_IO.Get_Line never raises Data_Error.
When dealing with numeric input, I advise that a complete line be read into a String and you then parse out the value(s) from that String, as you have done. However, 'Value will reject some input that you might want to consider valid. For example, you might want to accept "23 skidoo" and get the value 23 from it. For that, you might want to instantiate Ada.Text_IO.Integer_IO for your numeric (sub)type and use the Get function that takes a String parameter:
package Score_IO is new Ada.Text_IO.Integer_IO (Num => Score);
...
Score_IO.Get (From => "23 skidoo", Item => A, Last => Last);
will set A to 23 and Last to the index of '3' in From (2).
HTH
I'm writing long digit arythmetics. This is a function for adding to longint long binary digits. I need to output the sum inside the function, to debug it. How could I do it, without creating new variables?
function add(var s1,s2:bindata;shift:longint):bindata;
var l,i:longint;
o:boolean;
begin
writeln(s1.len,' - ',s2.len);
o:=false;
l:=max(s1.len,s2.len);
add.len:=0;
for i:=1 to l do begin
if o then Begin
if s1.data[i+shift] then Begin
if (s2.data[i]) then add.data[i+shift]:=true
Else add.data[i+shift]:=false;
End
else if s2.data[i] then add.data[i+shift]:=false
else Begin
add.data[i+shift]:=true;
o:=false;
End;
End
Else Begin
if s1.data[i+shift] then Begin
if s2.data[i] then
Begin
add.data[i+shift]:=false;
o:=true;
End
Else add.data[i+shift]:=true;
End
else if s2.data[i] then add.data[i+shift]:=true
else add.data[i+shift]:=false;
End;
output(add); //Can I output a variable?
end;
add.len:=l;
if o then Begin
inc(add.len);
add.data[add.len]:=true;
End;
end;
You are accumulating the result of the function within the function result variable, which is generally fine, but uses an outdated style, and leads to exactly the problem you're facing here. You're trying to report an intermediate value of the function result, and to do that, you're trying to reference the name of the function, add. When you do that, though, the compiler interprets it as an attempt to report the function itself, rather than the expected return value of this particular invocation of the function. You'll get the address of the function, if output is defined to accept function addresses; otherwise, you'll get a compiler error.
If your compiler offers a certain common language extension, then you should use the implicit Result variable to refer to the intermediate return value instead of continuing to refer to it by the function name. Since Result is declared implicitly, you wouldn't have to create any other variables. The compiler automatically recognizes Result and uses it as an alias for the function's return value. Simply find every place you write add within the function and replace it with Result. For example:
if o then begin
Inc(Result.len);
Result.data[Result.len] := True;
end;
Turbo Pascal, Free Pascal, GNU Pascal, and Delphi all support the implicit Result variable, but if you've managed to get stuck with a compiler that doesn't offer that extension, then you have no choice but to declare another variable. You could name it Result, and then implement your function with one additional line at the end, like so:
function add(var s1, s2: bindata; shift: longint): bindata;
var
l, i: longint;
o: boolean;
Result: bindata;
begin
{
Previous function body goes here, but with
`add` replaced by `Result`
}
{ Finally, append this line to copy Result into the
function's return value immediately before returning. }
add := Result;
end;
I keep getting a error when i run this code, What wrong with the code?
create or replace function f_vars(line varchar2,delimit varchar2 default ',')
return line_type is type line_type is varray(1000) of varchar2(3000);
sline varchar2 (3000);
line_var line_type;
pos number;
begin
sline := line;
for i in 1 .. lenght(sline)
loop
pos := instr(sline,delimit,1,1);
if pos =0 then
line_var(i):=sline;
exit;
endif;
string:=substr(sline,1,pos-1);
line_var(i):=string;
sline := substr(sline,pos+1,length(sline));
end loop;
return line_var;
end;
LINE/COL ERROR
20/5 PLS-00103: Encountered the symbol "LOOP" when expecting one of
the following:
if
22/4 PLS-00103: Encountered the symbol "end-of-file" when expecting
one of the following:
end not pragma final instantiable order overriding static
member constructor map
Stack Overflow isn't really a de-bugging service.
However, I'm feeling generous.
You have spelt length incorrectly; correcting this should fix your first error. Your second is caused by endif;, no space, which means that the if statement has no terminator.
This will not correct all your errors. For instance, you're assigning something to the undefined (and unnecessary) variable string.
I do have more to say though...
I cannot over-emphasise the importance of code-style and whitespace. Your code is fairly unreadable. While this may not matter to you now it will matter to someone else coming to the code in 6 months time. It will probably matter to you in 6 months time when you're trying to work out what you wrote.
Secondly, I cannot over-emphasise the importance of comments. For exactly the same reasons as whitespace, comments are a very important part of understanding how something works.
Thirdly, always explicitly name your function when ending it. It makes things a lot clearer in packages so it's a good habit to have and in functions it'll help with matching up the end problem that caused your second error.
Lastly, if you want to return the user-defined type line_type you need to declare this _outside your function. Something like the following:
create or replace object t_line_type as object ( a varchar2(3000));
create or replace type line_type as varray(1000) of t_line_type;
Adding whitespace your function might look something like the following. This is my coding style and I'm definitely not suggesting that you should slavishly follow it but it helps to have some standardisation.
create or replace function f_vars ( PLine in varchar2
, PDelimiter in varchar2 default ','
) return line_type is
/* This function takes in a line and a delimiter, splits
it on the delimiter and returns it in a varray.
*/
-- local variables are l_
l_line varchar2 (3000) := PLine;
l_pos number;
-- user defined types are t_
-- This is a varray.
t_line line_type;
begin
for i in 1 .. length(l_line) loop
-- Get the position of the first delimiter.
l_pos := instr(l_line, PDelimiter, 1, 1);
-- Exit when we have run out of delimiters.
if l_pos = 0 then
t_line_var(i) := l_line;
exit;
end if;
-- Fill in the varray and take the part of a string
-- between our previous delimiter and the next.
t_line_var(i) := substr(l_line, 1, l_pos - 1);
l_line := substr(l_line, l_pos + 1, length(l_line));
end loop;
return t_line;
end f_vars;
/