One somewhat unique, or at least quirky feature of ASB is the implicit or automatic conversion strings to numbers, and vice versa, according to the statement context. For the most part this is intuitive and may even go without notice, but the fact that the + operator acts as addition for numeric arguments but concatenation for string arguments can easily lead to unexpected results if you're not conscious of the way it works. The basic principle is that every expression, whether a literal value, a variable, or a more complex expression involving operators and functions, is either considered to be a number or a string. As the compiler parses statements, it is always in one state or other, i.e. numeric or string, based on the types of the arguments and operators it has seen so far, and when it runs into an operand that doesn't match the current expression state, it inserts a conversion operation into the expression so that each operand matches the current expression state. An example will make this more clear. Consider the following code:
op1 = 12.3
op2 = 4.56
op3 = 7
result = (op1 + op2 - op3) using "###.#"
If none of these variables were mapped and the /M switch was not specified, the compiler would auto-map them as numeric F variables and process the final statement as follows:
• | The l-value, result, is numeric, so that sets the initial expression mode. |
• | The next operands, op1, is numeric, matching the current mode, and the mode remains numeric. |
• | Since the mode is numeric, the + operator is compiled as addition, and the mode remains numeric. |
• | The next operand, op2, is also numeric; the result is the numeric 16.86, and the mode remains numeric. |
• | The - operator is always numeric. |
• | The next operand, op3, matches the mode so the subtraction proceeds, resulting in the numeric value 9.86. Mode remains numeric. |
• | The using operator expects to start in numeric mode (the value to be formatted) but then expects the next argument to be a string (the mask), and the result is string. So now the expression mode is string. 9.86 using "###.#" results in "9.9". |
• | Finally the assignment operator = is processed. It's a mixed mode operator with the rule being that the r-value type must be converted to match the l-value type. In this case, the l-value, result is numeric, and the r-value ("9.9") is a string, so the compiler inserts a VAL() operator into the expression to convert the string "9.9" to a number that can be assigned to and stored in a numeric variable. |
Now let's make it more interesting by mapping the variables as follows:
map1 result,s,10
map1 op1,f,6
map1 op2,f,6
map1 op3,s,8
The introduction of strings into the mix causes the expression evaluation logic to change:
• | The first token encountered, result, is now a string variable, causing the expression mode to be set to string. |
• | The first operand, op1, is numeric. Since that doesn't match the current expression mode, the compiler effectively converts it to STR(op1), and the mode remains string. |
• | Since the expression mode is string, the + operator is compiled as concatenation and expects the next argument to be string (i.e. the expression mode remains string). |
• | The same goes for the next operand, op2, i.e. it gets converted to STR(op2) to match the current expression mode of string. The result of the concatenation is "12.34.56". |
• | The - operator is always numeric; since that does not match the current expression mode, the compiler inserts a VAL() function into the expression to convert the string "12.34.56" to a number, resulting in the value 12.34. String to numeric conversion stops at the first character that isn't legal for numbers, which in this case is the second decimal point. The expression mode changes to numeric. |
• | The next operand, op3, is a string, which doesn't match the expression mode, so the compiler converts it to VAL(op3). The subtraction 12.34 - 7 = 5.34 is carried out and the expression mode remains numeric. |
• | The next operator, using, expects to start in numeric mode (with the current expression numeric value to format), but it expects the next argument to be a string (the mask), so it changes the expression mode to string. |
• | The next argument "###.#" is a string as expected; the formatting operation is carried out resulting in the string "5.3" (due to the mask rounding), and the expression mode remains string. |
• | Finally the assignment operator = is processed. As in the previous example, the compiler inserts a VAL() operator into the expression to convert the string "5.3" to a number that can be assigned to and stored in a numeric variable. |
As the above example illustrates, the implicit string/numeric conversion, combined with the dual addition/concatenation treatment of the + operator can cause unexpected results if you're not careful. Which leads to the question of how to avoid such confusion. There several possibilities:
• | Avoid using string variables in arithmetic expressions, or numeric variables in string expressions. |
• | Insert your own explicit STR() and VAL() functions to make your intentions explicit. Note that although it may be redundant, there is nothing illegal about VAL(x) where x is already a numeric expression or variable. This is because numbers can always be represented as strings and it is both understood and expected the value of a string like "ABC" is going to be zero. The reverse, however, is not true. STR("ABC") evaluates to "0", not "ABC". This is because the STR() function expects a numeric argument; if it gets a string argument instead, it first converts it to a numeric argument, in this case effectively become STR(VAL("ABC")). The same goes for the VAL() function, i.e. VAL(27) actually gets compiled as VAL(STR(27)), but VAL("27") is still 27. |
• | Avoid the addition vs. concatenation confusion of the + operator by using Explicit Plus Operators instead, or perhaps the NUMEXPR() and STREXPR$() functions. |
Also See