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

后端 未结 1 1190
盖世英雄少女心
盖世英雄少女心 2020-11-29 10:19

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=         


        
相关标签:
1条回答
  • 2020-11-29 11:00

    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.

    0 讨论(0)
提交回复
热议问题