Search code examples
windowsbatch-filewindows-7

Batch File "Unbalanced Parenthesis"


So I have this program I'm trying to make:

//@echo off
@title Calculate Caloric Needs
set /p name=What is the name of the individual in question? 
CLS

:START
set /p sex=Is %name% a M(ale) or F(emale)? (M/F) 
CLS
if NOT %sex%==M if NOT %sex%==F ( 
    echo Invalid Selection...Try Again....
    goto START
)

set /p w=What is the target weight of %name%? 
CLS

set /p h=What is the height of %name% IN INCHES? 
CLS

set /p a=What is the age of %name%? 
CLS

if %sex%==M set /a result=66 + (6.23 * %w%) + (12.7 * %h%) - (6.8 * %a%)
if %sex%==F set /a result=655+(4.35*%w%)+(4.7*%h%)-(4.7*%a%)

echo %result% is the caloric intake for %name%.
pause
exit

@echo off has been disabled for the sake of troubleshooting.

It's a simple program meant to do some caloric intake calculations that are a bit arduous to do one by one.

Every time I reach the point where I need to do arithmetic calculations one of two things happens:

  1. if I wrap the set /a statement in an if %sex%==M ( CODE HERE ) block, the program will exit unexpectedly without showing an error message.
  2. If I don't and have it all on one line (as it is in the example code posted above) then I get the message Unbalanced Parenthesis without any further explanation (thanks Windows...).

As far as I know, mathematically speaking the parenthesis are completely balanced.

I've tried:

  • wrapping the set /a statement in both "" and [], set /a "result=66 + (6.23 * %w%) + (12.7 * %h%) - (6.8 * %a%)" or set /a [result=66 + (6.23 * %w%) + (12.7 * %h%) - (6.8 * %a%)]
  • escaping some or all of the parenthesis used in the arithmetic set /a result=66 + ^(6.23 * %w%^) + ^(12.7 * %h%^) - ^(6.8 * %a%^)
  • reducing the amount of parenthesis for the sake of testing set /a result=66 + (6.23 * %w%). If I use set /a result=66 + (6.23 * %w%) exactly, I can actually wrap the statement in an if ( CODE HERE ) block and have it actually return an error value. Although the error is still Unbalanced Parenthesis...
  • lengthening or shortening the number of spaces in the arithmetic itself set /a result=66+(6.23*%w%)+(12.7*%h%)-(6.8*%a%) VS. set /a result=66 + (6.23 * %w%) + (12.7 * %h%) - (6.8 * %a%)
  • all combinations of the above

So I am at a loss as to what could be causing this bizarre behavior.


Solution

  • The error message you are receiving is very confusing in your situation, because the parentheses in the set /A command lines are balanced and work like that in case the entire command line is not placed inside of a parenthesised block of code on its own. Anyway, it is always a good idea to put the entire set /A expression in between quotation marks, so neither parentheses nor other special characters can cause trouble.
    Square brackets do not have any particular meaning for set /A.

    Anyway, the root cause of the error is the fact that you are using fractional numbers although set /A only supports signed integer arithmetics in a 32-bit room (see the help of set /?). Type set /A (1.2*3) in a command prompt window and you will receive the same error message; but remove the . and everything is going to be fine.

    A possible work-around is to use something like fixed-point arithmetics, hence multiplying everything by a whole power of 10 during the calculations and dividing the result by the same power of 10 finally, or in other words, shifting the decimal point to the right before doing the calculations and shifting it back afterwards.

    This is how it could look like (I also fixed some other issues in your code, but see below for that):

    @echo off
    @title Calculate Caloric Needs
    
    cls
    set "name=them"
    set /P "name=What is the name of the individual in question? "
    
    :START
    set "sex="
    set /P "sex=Is %name% a M(ale) or F(emale)? (M/F) "
    if /I not "%sex%"=="M" if /I not "%sex%"=="F" (
        echo Invalid Selection... Try Again...
        goto :START
    )
    
    set "w=0"
    set /P "w=What is the target weight of %name%? "
    
    set "h=0"
    set /P "h=What is the height of %name% IN INCHES? "
    set "h=%h%."            & rem // append a decimal dot to entry in `h`
    set "mil=1%h:*.=%"      & rem // store everything behind first dot in `mil`, prepend `1`
    set /A "h+=0, mil+=0"   & rem // convert `h` to integer, dismiss fractional part
    set "mil=%mil%000"      & rem // pad trailing zeros to `mil`
    set "mil=%mil:~,4%"     & rem // extract first four numerals from `mil`
    set /A "mil+=5"         & rem // add `5` to `mil` for rounding
    if %mil:~,1% GTR 1 set /A "h+=1" & rem // regard carry of previous addition in `h`
    set "h=%h%%mil:~-3,-1%" & rem /* append second and third numeral of `mil` to `h`,
                              rem    hence dismissing previously prepended `1`;
                              rem    so `h` holds the height in 100ths of inches now */
    
    set "a=0"
    set /P "a=What is the age of %name%? "
    
    rem // quotation marks avoid trouble with parenthesis or other characters;
    rem /* all original constants are multiplied by `1000` to avoid fractional parts,
    rem    except the factor at `h` which is multiplied by `10` only due to above
    rem    implicit multiplication of `h` by 100, then `500` is added for rounding,
    rem    and finally, the result is divided by `1000` to remove the previous factors: */
    if /I "%sex%"=="M" (
        set /A "result=(66000+(6230*%w%)+(127*%h%)-(6800*%a%)+500)/1000"
    ) else if /I "%sex%"=="F" (
        set /A "result=(655000+(4350*%w%)+(47*%h%)-(4700*%a%)+500)/1000"
    )
    
    echo %result% is the caloric intake for %name%.
    pause
    exit /B
    

    This is what I fixed:

    • the set and set /P syntax is improved so that entire expression is in between quotation marks, hence you avoid trouble with special characters and you clearly can see if there are any trailing white-spaces;
    • any prompted values are initialised to avoid the previous value is taken in case the user just presses RETURN;
    • the if queries for gender entry are corrected so that they are now case-insensitive by adding switch /I, and that they do not cause trouble in case the tested variables are empty by enclosing the comparison expressions in quotation marks;
      by the way, you might be interested in the choice command for such single-key entries, because it does not even accept any other characters;
    • the value of the height entry h in inches is converted to 100ths of inches because of the said fixed-point arithmetics (but weight w and age a are still treated as integers); this is how it works:
      • set "h=%h%.": append a dot to the entry in h to ensure there is at least one;
      • set "mil=1%h:*.=%": store everything after the first dot in h into mil (fractional part), and prepend 1 to the result to not lose any leading zeros (which are significant for the fractional part) as soon as this is converted to a number;
      • set /A "h+=0, mil+=0": convert both h and mil to numeric values (integers); this converts everything up to the first non-numeric figure to a number, ignoring leading white-spaces but regarding signs +/- (although they are irrelevant as not needed here);
      • set "mil=%mil%000": append 3 trailing zeros to the fractional part (the prepended 1 is still there, so there are always at least 4 digits);
      • set "mil=%mil:~,4%": extract the first 4 characters (so the prepended 1 plus 3 more figures);
      • set /A "mil+=5": add 5, so if the last numeral is 5 or more, a carry appears, so rounding up of the next-to-last figure happens;
      • if %mil:~,1% GTR 1 set /A "h+=1": increment integer part in h in case the carry exceeds the fractional part;
      • set "h=%h%%mil:~-3,-1%": concatenate the integer part of h and the range from the second to the next-to-last figure of the fractional part in mil and store it in h;
    • all the constants in the formulas for result are multiplied by 1000 for the decimal dot to disappear, except the factor of h which is multiplied by 10 only, because h is already 100 times the original height value; afterwards the result is divided by 1000; the addition of 500 means nothing but adding 0.5 when regarding the division by 1000 and is intended do round up fractional parts from .5 up to .9* before they get lost due to the integer division;
    • the exit command is replaced by exit /B to terminate batch file but not the owning cmd instance;

    Note that comments like // and /*, */ have no special meanings in batch-files. Comments or remarks are given by the rem statement. I used the slash-style comments within rem here only for some cool syntax highlighting here on this site...


    Update

    As per a comment by the original poster, the result itself should be a fractional number, so I modified the script a bit (a description follows below):

    @echo off
    @title Calculate Caloric Needs
    
    cls
    set "name=them"
    set /P "name=What is the name of the individual in question? "
    
    set "male="
    choice /C MF /M "Is %name% a M(ale) of F(emale)? "
    if not ErrorLevel 2 set "male=Flag"
    
    set "w=0"
    set /P "w=What is the target weight of %name% IN POUNDS? "
    
    set "h=0"
    set /P "h=What is the height of %name% IN INCHES? "
    set "h=%h%."            & rem // append a decimal dot to entry in `h`
    set "mil=1%h:*.=%"      & rem // store everything behind first dot in `mil`, prepend `1`
    set /A "h+=0, mil+=0"   & rem // convert `h` to integer, dismiss fractional part
    set "mil=%mil%000"      & rem // pad trailing zeros to `mil`
    set "mil=%mil:~,4%"     & rem // extract first four numerals from `mil`
    set /A "mil+=5"         & rem // add `5` to `mil` for rounding
    if %mil:~,1% GTR 1 set /A "h+=1" & rem // regard carry of previous addition in `h`
    set "h=%h%%mil:~-3,-1%" & rem /* append second and third numeral of `mil` to `h`,
                              rem    hence dismissing previously prepended `1`;
                              rem    so `h` holds the height in 100ths of inches now */
    
    set "a=0"
    set /P "a=What is the age of %name% IN YEARS? "
    
    rem // quotation marks avoid trouble with parenthesis or other characters;
    rem /* all original constants are multiplied by `1000` to avoid fractional parts,
    rem    except the factor at `h` which is multiplied by `10` only due to above
    rem    implicit multiplication of `h` by 100, then `500` is added for rounding,
    rem    and finally, the result is divided by `1000` to remove the previous factors: */
    if defined male (
        set /A "result=66000+(6230*%w%)+(127*%h%)-(6800*%a%)+5"
    ) else (
        set /A "result=655000+(4350*%w%)+(47*%h%)-(4700*%a%)+5"
    )
    
    echo %result:~,-3%.%result:~-3,-1% is the caloric intake for %name%.
    pause
    exit /B
    

    This is what I did:

    • the basic calculation formulas are still the same, except that the division by 1000 has been omitted this time, and that 5 is added to the result for rounding instead of 500; for displaying the result though, the very last figure is dismissed and a decimal dot . is inserted before the remaining last two numerals, so there are two fractional digits for the result;
    • the gender entry is now accomplished by the choice command as suggested in the original version of this answer (see above), so there is no more need to capture invalid results as choice simply only accepts predefined keys or characters;