weird results with IF
Inspired by this question and it's answers, I did some testing.
One answer suggests, the numbers are too big (out of 32bit integer) and they get truncated, but this doesn't explain the results. Also obviously it doesn't compare both sides as strings (as I would have expected). It seems that if
gets confused and thinks "well, I don't know - give it a TRUE". (Using neq
, gtr
, lss
instead of equ
, geq
, leq
always give FALSE).
The code works as expected, if any of a
and/or b
are within the borders of 32bit integer or contain any char out of [0-9].
@echo off
set a=333333333333
set b=444444444444
call :compare
set b=222222222222
call :compare
goto :eof
:compare
echo comparing %a% with %b%
if %a% geq %b% (echo a ^>= b) else (echo -)
if %b% geq %a% (echo b ^>= a) else (echo -)
if %a% leq %b% (echo a ^<= b) else (echo -)
if %b% leq %a% (echo b ^<= a) else (echo -)
if %a% equ %b% (echo a = b) else (echo -)
if %a% == %b% (echo a == b) else (echo -)
Is there any logical explanation for this, or is it just something we have to live with without thinking?
The reason for this result can be found in documentation of function strtol which is used first on using the comparison operators EQU
, NEQ
, LSS
, LEQ
, GTR
, GEQ
as explained in my answer on Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files.
Return Value
On success, the function returns the converted integral number as a long int value.
If no valid conversion could be performed, a zero value is returned (0L).
If the value read is out of the range of representable values by a long int, the function returns LONG_MAX or LONG_MIN (defined in <climits>), and errno is set to ERANGE.
The last sentence is most important here.
It looks like IF in cmd.exe
is coded similar to this C code:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char* argv[])
{
const char csNo[] = "no";
const char csYes[] = "yes";
char* pcEndValue1;
char* pcEndValue2;
int iExitCode = 2;
int iErrorNumber1;
int iErrorNumber2;
int iStringResult1;
int iStringResult2;
long lIntegerValue1;
long lIntegerValue2;
if(argc > 2)
{
/* Convert the two arguments to 32-bit signed integers. */
lIntegerValue1 = strtol(argv[1],&pcEndValue1,0);
iErrorNumber1 = errno;
lIntegerValue2 = strtol(argv[2],&pcEndValue2,0);
iErrorNumber2 = errno;
/* Failed the conversion for any of the two arguments? */
if(((lIntegerValue1 == 0) && (*pcEndValue1 != '\0')) ||
((lIntegerValue2 == 0) && (*pcEndValue2 != '\0')))
{
/* Compare case-sensitive the two arguments as strings. */
iStringResult1 = strcmp(argv[1],argv[2]);
iStringResult2 = strcmp(argv[2],argv[1]);
printf("String comparing %s (a) with %s (b):\n\n",argv[1],argv[2]);
printf("a GEQ b: %s\n",(iStringResult1 >= 0) ? csYes : csNo);
printf("b GEQ a: %s\n",(iStringResult2 >= 0) ? csYes : csNo);
printf("a LEQ b: %s\n",(iStringResult1 <= 0) ? csYes : csNo);
printf("b LEQ a: %s\n",(iStringResult2 <= 0) ? csYes : csNo);
printf("a EQU b: %s\n",(iStringResult2 == 0) ? csYes : csNo);
iExitCode = 1;
}
else
{
/* Compare the values. */
printf("Value comparing %s/%ld (a) with %s/%ld (b):\n\n",argv[1],lIntegerValue1,argv[2],lIntegerValue2);
printf("a GEQ b: %s\n",(lIntegerValue1 >= lIntegerValue2) ? csYes : csNo);
printf("b GEQ a: %s\n",(lIntegerValue2 >= lIntegerValue1) ? csYes : csNo);
printf("a LEQ b: %s\n",(lIntegerValue1 <= lIntegerValue2) ? csYes : csNo);
printf("b LEQ a: %s\n",(lIntegerValue2 <= lIntegerValue1) ? csYes : csNo);
printf("a EQU b: %s\n",(lIntegerValue2 == lIntegerValue1) ? csYes : csNo);
iExitCode = 0;
}
printf("\nError number a: %d ... %s\n",iErrorNumber1,strerror(iErrorNumber1));
printf("Error number b: %d ... %s\n",iErrorNumber2,strerror(iErrorNumber2));
}
return iExitCode;
}
Compiling this C code as console application and running the executable with the parameters 333333333333 444444444444
results for example in output:
Value comparing 333333333333/2147483647 (a) with 444444444444/2147483647 (b):
a GEQ b: yes
b GEQ a: yes
a LEQ b: yes
b LEQ a: yes
a EQU b: yes
Error number a: 2 ... Output of function out of range (ERANGE)
Error number b: 2 ... Output of function out of range (ERANGE)
And running the executable with the parameters 333333333333 222222222222
results for example in output:
Value comparing 333333333333/2147483647 (a) with 222222222222/2147483647 (b):
a GEQ b: yes
b GEQ a: yes
a LEQ b: yes
b LEQ a: yes
a EQU b: yes
Error number a: 2 ... Output of function out of range (ERANGE)
Error number b: 2 ... Output of function out of range (ERANGE)
Note: The error number and the corresponding error string can differ depending on used C compiler respectively standard library.
In both test cases both arguments resulted in a 32-bit signed integer overflow on conversion from string to long int. Therefore strtol
returned for all four values LONG_MAX
and set errno
to ERANGE
. But the overflow condition is not evaluated by code of IF in cmd.exe
. It is just checked the conversion result and on which character the end pointer points to for both arguments like by the C code above.
In other words IF processes on usage of comparison operators EQU
, NEQ
, LSS
, LEQ
, GTR
, GEQ
always an integer comparison as long as conversion from string to integer does not fail for any of the two arguments because of an invalid character in argument strings. An out of range condition is no reason for IF not doing an integer comparison.
A string comparison is done only if one of the two argument strings contains an invalid character for an integer.
It is a limit.
C:>set /A a=333333333333
Invalid number. Numbers are limited to 32-bits of precision.