Page 1 of 1

Type Coercion and Math

Posted: Fri Feb 28, 2025 3:12 am
by russellm72
I'm trying to get back up to speed with PB and I'm running into something that is make me feel rather dumb.

I was trying to create a simple calculator. I just placed two StringGadgets and one ComboBox and one TextGadget in a window with the Forms Designer, along with a button that says "Calculate".

When the user enters 2 numbers in the StringGadgets and then selects and operator (+,-.*,/) and then clicks calculate, then I want to grab the values from the StringGadgets and perform the math and then write the result to the TextGadget like: "3 + 5 = 8".

Easy, right?

However, I also want the program to be able to do floating point math if the user doesn't enter simple integers into the StringGadgets and this is where I'm starring to pull what little hair I have left out of my head. I've been trying to figure out the simplest way to convert the strings retrieved by GetGadgetText to either an int or a double and then to perform the intended math and get results that make sense. Like if both numbers are integers I want to do integer math and have an integer result (unless it's division that results in a double). Or, if one of the numbers is not an integer, I want to perform the intended math and get a double as a result.

In the beginning, I had something as simple as this:

Code: Select all

Procedure doCalculation(x)
  
  tResult$ =  "No Calculation Performed"
  tCalculated = #False
  
  ;MessageRequester("tCalculated", "tCalculated = " + Str(tCalculated))
  
  tOp1 = Val(GetGadgetText(String_1))
  tOp2 = Val(GetGadgetText(String_2))
  
  
  tOper$ = GetGadgetText(Combo_0)
  
  ;MessageRequester("Currently Selected Operator", "The currently selected operator is: " + tOper$)
  
  Select tOper$
    Case "+"
      tResult = tOp1 + tOp2
      tCalculated = #True
    Case "-"
      tResult = tOp1 - tOp2
      tCalculated = #True
    Case "*"
      tResult = tOp1 * tOp2
      tCalculated = #True
    Case "/"
      tResult = tOp1 / tOp2
      tCalculated = #True
    Default
      MessageRequester("Select an Operator", "You must first select an operator.")
  EndSelect
  
  ;MessageRequester("tCalculated", "tCalculated = " + Str(tCalculated))
  
  If tCalculated = #True
        tResult$ = Str(tOp1) + " " + tOper$ + " " + Str(tOp2) + " = " + Str(tResult)
  EndIf
  
  SetGadgetText(Text_0, tResult$)
EndProcedure
Which, of course only works for addition, subtraction, and multiplication of integers.

So, then I started coding myself in knots trying stuff like this:

Code: Select all

Procedure doCalculation(x)
  
  tResult$ =  "No Calculation Performed"
  tCalculated = #False
  
  ;MessageRequester("tCalculated", "tCalculated = " + Str(tCalculated))
  
  tOp1$ = GetGadgetText(String_1)
  If FindString(tOp1$, ".") > 0 
    ;Debug "tOp1$ contains ."
    tOp1.d = ValD(tOp1$)
    tAllInts = #False
  Else
    Debug "tOp1$ doest NOT contain ."
    tOp1 = Val(tOp1$)
  EndIf
  
  If TypeOf(tOp1) = #PB_Double
    Debug "PB_Double"
  Else
    Debug "PB_Integer"
  EndIf
  
  
  
  tOp2$ = GetGadgetText(String_2)
  If FindString(tOp2$, ".") > 0 
    tOp2.d = ValD(tOp2$)
    tAllInts = #False
  Else
    tOp2 = Val(tOp2$)
  EndIf
  
  tOper$ = GetGadgetText(Combo_0)
  
  ;MessageRequester("Currently Selected Operator", "The currently selected operator is: " + tOper$)
  
  Select tOper$
    Case "+"
      If tAllInts = #True
        tResult = tOp1 + tOp2
      Else
        tResult.d = tOp1 + tOp2
      EndIf
      
      tCalculated = #True
    Case "-"
      If tAllInts = #True
        tResult = tOp1 - tOp2
      Else
        tResult.d = tOp1 - tOp2
      EndIf
      
      tCalculated = #True
    Case "*"
      If tAllInts = #True
        tResult = tOp1 * tOp2
      Else
        tResult.d = tOp1 * tOp2
      EndIf
      
      tCalculated = #True
    Case "/"
      tResult.d = tOp1 / tOp2
      tCalculated = #True
    Default
      MessageRequester("Select an Operator", "You must first select an operator.")
  EndSelect
  
  ;MessageRequester("tCalculated", "tCalculated = " + Str(tCalculated))
  
  If tCalculated = #True
    If TypeOf(tResult) = #PB_Double
      tCalculation$ = StrD(tResult)
    Else
      tCalculation$ = Str(tResult)
    EndIf
    
    tResult$ = tOp1$ + " " + tOper$ + " " + tOp2$ + " = " + tCalculation$
  EndIf
  
  SetGadgetText(Text_0, tResult$)
EndProcedure

Procedure doReset(x)

  SetGadgetText(String_1, "")
  SetGadgetText(String_2, "")
  SetGadgetText(Text_0, "")


EndProcedure
Which, will not even compile and gives and error that "Variable already declared with another type: tResult." in relation to this line:

tResult.d = tOp1 + tOp2

Help. There has to be an easy way to do this, doesn't there?

Re: Type Coercion and Math

Posted: Fri Feb 28, 2025 5:11 am
by PBJim
You can split the logic between the floating point and integer, using separate variables for doubles or integers, where appropriate. You cannot define a variable tresult and tresult.d.

As your example was not complete, I drew a basic form without the time consuming work involved in creating the combobox options, so you'll need to add your combobox functions again yourself. It would be better to change the variable names to be more meaningful. Hope it helps you to move forward with it.

Code: Select all

EnableExplicit

Global Window_0
Global String_0, String_1, Text_0
Global Button_0

Define event

Procedure doCalculation()
  
  Define tResult$
  Define tCalculated
  Define tOper$
  Define tOp1
  Define tOp2
  Define tResult
  Define tdouble.d
  Define fl1.d
  Define fl2.d
  
  tResult$ =  "No Calculation Performed"
  tCalculated = #False
  
  If FindString(GetGadgetText(String_0), ".") Or FindString(GetGadgetText(String_1), ".")
    fl1.d  = ValD(GetGadgetText(String_0))
    fl2.d  = ValD(GetGadgetText(String_1))
    
    tOper$ = "+"
    Select tOper$
      Case "+"
        tdouble.d = fl1.d + fl2.d
        tCalculated = #True
    EndSelect
    
    If tCalculated = #True
      tResult$ = StrD(fl1.d) + " " + tOper$ + " " + StrD(fl2.d) + " = " + StrD(tdouble.d) + " [fp]"
    EndIf
    
    SetGadgetText(Text_0, tResult$)
    
  Else
    
    tOp1 = Val(GetGadgetText(String_0))
    tOp2 = Val(GetGadgetText(String_1))
    
    tOper$ = "+"
    Select tOper$
      Case "+"
        tResult = tOp1 + tOp2
        tCalculated = #True
    EndSelect
    
    If tCalculated = #True
      tResult$ = Str(tOp1) + " " + tOper$ + " " + Str(tOp2) + " = " + Str(tResult)
    EndIf
    
    SetGadgetText(Text_0, tResult$)
  EndIf
  
EndProcedure

; **  
; **  Main programme  
; **  
Window_0 = OpenWindow(#PB_Any, 600, 500, 600, 300, "", #PB_Window_SystemMenu)
String_0 = StringGadget(#PB_Any, 60, 30, 100, 25, "")
String_1 = StringGadget(#PB_Any, 60, 70, 100, 25, "")
Text_0 = TextGadget(#PB_Any, 60, 120, 200, 25, "")
Button_0 = ButtonGadget(#PB_Any, 230, 30, 100, 25, "Calc")


Repeat
  event = WaitWindowEvent()
  
  Select event
    Case #PB_Event_Gadget
      Select EventGadget()
        Case Button_0
          doCalculation()
      EndSelect
      
    Case #PB_Event_CloseWindow
      Break
      
  EndSelect
  
ForEver

Re: Type Coercion and Math

Posted: Fri Feb 28, 2025 6:52 am
by russellm72
Ok. Thanks.

I think what I just learned is that the simple answer is to just use doubles. It gives the kind of output I was looking for without having to define two sets of variables and determine if I am doing integer or double math, I think.

Here's my complete project now that I saw what happens with ValD and StrD in your provided example.

EasyCalc.pbf

Code: Select all

;
; This code is automatically generated by the Form Designer.
; Manual modification is possible to adjust existing commands, but anything else will be dropped when the code is compiled.
; Event procedures need to be put in another source file.
;

Global Window_0

Global Button_0, String_1, String_2, Text_0, Combo_0, Button_1

Declare doReset(EventType)
Declare doCalculation(EventType)

Procedure OpenWindow_0(x = 0, y = 0, width = 550, height = 400)
  Window_0 = OpenWindow(#PB_Any, x, y, width, height, "", #PB_Window_SystemMenu)
  Button_0 = ButtonGadget(#PB_Any, 20, 345, 515, 25, "Calculate")
  String_1 = StringGadget(#PB_Any, 205, 25, 315, 20, "")
  String_2 = StringGadget(#PB_Any, 205, 75, 315, 20, "")
  Text_0 = TextGadget(#PB_Any, 200, 120, 320, 20, "")
  Combo_0 = ComboBoxGadget(#PB_Any, 205, 50, 315, 20)
  AddGadgetItem(Combo_0, -1, "+")
  AddGadgetItem(Combo_0, -1, "-")
  AddGadgetItem(Combo_0, -1, "*")
  AddGadgetItem(Combo_0, -1, "/")
  Button_1 = ButtonGadget(#PB_Any, 20, 300, 100, 25, "Reset")
EndProcedure

Procedure Window_0_Events(event)
  Select event
    Case #PB_Event_CloseWindow
      ProcedureReturn #False

    Case #PB_Event_Menu
      Select EventMenu()
      EndSelect

    Case #PB_Event_Gadget
      Select EventGadget()
        Case Button_0
          doCalculation(EventType())          
        Case Button_1
          doReset(EventType())          
      EndSelect
  EndSelect
  ProcedureReturn #True
EndProcedure

EasyCalc.pb

Code: Select all

XIncludeFile "EasyCalc.pbf"

OpenWindow_0()



Procedure doCalculation(x)
  
  tResult$ =  "No Calculation Performed"
  tCalculated = #False
  
  ;MessageRequester("tCalculated", "tCalculated = " + Str(tCalculated))
  
  tOp1.d = ValD(GetGadgetText(String_1))
  tOp2.d = ValD(GetGadgetText(String_2))
  
  
  tOper$ = GetGadgetText(Combo_0)
  
  ;MessageRequester("Currently Selected Operator", "The currently selected operator is: " + tOper$)
  
  Select tOper$
    Case "+"
      tResult.d = tOp1 + tOp2
      tCalculated = #True
    Case "-"
      tResult.d = tOp1 - tOp2
      tCalculated = #True
    Case "*"
      tResult.d = tOp1 * tOp2
      tCalculated = #True
    Case "/"
      tResult.d = tOp1 / tOp2
      tCalculated = #True
    Default
      MessageRequester("Select an Operator", "You must first select an operator.")
  EndSelect
  
  ;MessageRequester("tCalculated", "tCalculated = " + Str(tCalculated))
  
  If tCalculated = #True
        tResult$ = StrD(tOp1) + " " + tOper$ + " " + StrD(tOp2) + " = " + StrD(tResult)
  EndIf
  
  SetGadgetText(Text_0, tResult$)
EndProcedure

Procedure doReset(x)

  SetGadgetText(String_1, "")
  SetGadgetText(String_2, "")
  SetGadgetText(Text_0, "")


EndProcedure

Repeat
  event = WaitWindowEvent()
  ;If event = #PB_Event_Menu
    ;If EventMenu() = #PB_Menu_Quit
      ;MessageRequester("Message", "You chose Quit from the Application menu")
      ;Break
    ;EndIf
    
  ;Else
    Window_0_Events(event)
  ;EndIf
  
      
Until event = #PB_Event_CloseWindow


    
It now gives the exact kind of output I was looking for without the need to determine if the values entered are integers or contain decimals.

The only question I have at the moment, is how do I set the ComboBox to a default value when I am using the Forms Designer and also used the Forms Designer to Edit Items?

Re: Type Coercion and Math

Posted: Fri Feb 28, 2025 9:53 am
by PBJim
russellm72 wrote: Fri Feb 28, 2025 6:52 am I think what I just learned is that the simple answer is to just use doubles. It gives the kind of output I was looking for without having to define two sets of variables and determine if I am doing integer or double math, I think.
It depends what you need it for. There can be some value in using integers also. Floating point can lead to some limitations with regard to accuracy, depending on the requirement.
The only question I have at the moment, is how do I set the ComboBox to a default value when I am using the Forms Designer and also used the Forms Designer to Edit Items?
You can either set the text value with SetGadgetText() which must be one of the valid items, or use SetGadgetState() to set the position from 0 - 3. Both appear to work. Personally I tend to use SetGadgetText() as it's often easier than counting the items.

Code: Select all

  AddGadgetItem(Combo_0, -1, "+")
  AddGadgetItem(Combo_0, -1, "-")
  AddGadgetItem(Combo_0, -1, "*")
  AddGadgetItem(Combo_0, -1, "/")

  SetGadgetText(Combo_0, "-")

;  or

  SetGadgetState(Combo_0, 2);   * 

Re: Type Coercion and Math

Posted: Fri Feb 28, 2025 6:52 pm
by SMaag
Take look at this IsNumeric Module. It' still under development. But there are some functions to check the numeric content of a String.

https://github.com/Maagic7/PureBasicFra ... Numeric.pb