Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows batch file IF failure - How can 30000000000000 equal 40000000000?

IF gives the wrong answer when I try to compare 2 large numbers.

For example, this simple batch file

@echo off
setlocal
set n1=30000000000000
set n2=40000000000
if %n1% gtr %n2% echo %n1% is greater than %n2%
if %n1% lss %n2% echo %n1% is less than %n2%
if %n1% equ %n2% echo %n1% is equal to %n2%

produces

30000000000000 is equal to 40000000000

What is going on, and how do I fix this?

like image 859
dbenham Avatar asked Feb 02 '12 16:02

dbenham


People also ask

What does == mean in batch file?

[ == ] (Double Equals) The "IF" command uses this to test if two strings are equal: IF "%1" == "" GOTO HELP. means that if the first parameter on the command line after the batch file name is equal to nothing, that is, if a first parameter is not given, the batch file is to go to the HELP label.

What does && do in a batch file?

&& runs the second command on the line when the first command comes back successfully (i.e. errorlevel == 0 ). The opposite of && is || , which runs the second command when the first command is unsuccessful (i.e. errorlevel != 0 ).

What does %1 mean in a batch file?

When used in a command line, script, or batch file, %1 is used to represent a variable or matched string. For example, in a Microsoft batch file, %1 can print what is entered after the batch file name.

What does %0 mean in batch file?

%0 references argument 0 – the name of the batch file – always exactly as specified on command line or in another batch file.


1 Answers

If both sides of an IF comparison are composed strictly of decimal digits, then IF will interpret both sides as numbers. This is what enables IF to correctly determine that 10 is greater than 9. If you have any non digit characters, then IF does a string comparison. For example, "10" is less than "9" because the quotes are not digits, and 1 sorts lower than 9.

The reason the comparison in the question fails is because CMD.EXE cannot process numbers larger than 2147483647. An odd design quirk in IF treats any number larger than 2147483647 as being equal to 2147483647.

If you want to do a string comparison of large numbers, then the solution is easy. You just need to add 1 or more non digit characters to both sides of the condition. The following script -

@echo off
setlocal
set n1=30000000000000
set n2=40000000000
if "%n1%" gtr "%n2%" echo "%n1%" is greater than "%n2%"
if "%n1%" lss "%n2%" echo "%n1%" is less than "%n2%"
if "%n1%" equ "%n2%" echo "%n1%" is equal to "%n2%"

produces the correct string comparison result

"30000000000000" is less than "40000000000"

But in most cases, this is not what is wanted.

If you want to do a numeric comparison, then the process is a bit more involved. You need to convert the number into a string that will sort properly as a number. This is accomplished by prefixing the numeric string with zeros in a way that makes both numeric strings the same width. The simplest solution is to determine the maximum number of digits you need to support - let's say 15 for this example. So you prefix each value with 15 zeros, and then preserve only the right-most 15 characters by using a substring operation. You also need to add a non-digit to both sides as before - again quotes work well.

This script -

@echo off
setlocal
set n1=30000000000000
set n2=40000000000
call :padNum n1
call :padNum n2
if "%n1%" gtr "%n2%" echo %n1% is greater than %n2%
if "%n1%" lss "%n2%" echo %n1% is less than %n2%
if "%n1%" equ "%n2%" echo %n1% is equal to %n2%
exit /b

:padNum
setlocal enableDelayedExpansion
set "n=000000000000000!%~1!"
set "n=!n:~-15!"
endlocal & set "%~1=%n%"
exit /b

produces -

030000000000000 is greater than 000040000000000

Note that left prefixing with spaces works just as well as zeros.

You can later remove the leading zeros whenever you want using the following (or adapt to remove leading spaces)

for /f "tokens=* delims=0" %%A in ("%n1%") do set "n1=%%A"
if not defined n1 set "n1=0"

Normally we don't deal with large numbers in batch files. But they can easily crop up if we look at free space on a hard disk. Terabyte disk drives are now relatively inexpensive. This is how I first ran into comparison of large numbers at https://stackoverflow.com/a/9099542/1012053

I chose to support 15 digits in my example because that equates to almost 999 terabytes. I imagine it will be a while before we have to deal with disk drives larger than that. (But who knows!)

EDIT - My description of how IF parses numbers is intentionally overly simplistic. IF actually supports negative numbers, as well as hex and octal notation. See Rules for how CMD.EXE parses numbers for a much more thorough explanation.

like image 123
dbenham Avatar answered Sep 22 '22 06:09

dbenham