Fracaso IF del archivo por lotes de Windows: ¿cómo puede 30000000000000 igualar 40000000000?

IF da la respuesta incorrecta cuando trato de comparar 2 números grandes.

Por ejemplo, este simple archivo por lotes

@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% 

produce

 30000000000000 is equal to 40000000000 

¿Qué está pasando y cómo soluciono esto?

Si ambos lados de una comparación IF están compuestos estrictamente de dígitos decimales, entonces IF interpretará ambos lados como números. Esto es lo que permite a IF determinar correctamente que 10 es mayor que 9. Si tiene caracteres que no son dígitos, IF hace una comparación de cadenas. Por ejemplo, “10” es menor que “9” porque las comillas no son dígitos y 1 es menor que 9.

La razón por la que falla la comparación en la pregunta es porque CMD.EXE no puede procesar números mayores que 2147483647. Una peculiaridad de diseño impar en IF trata cualquier número mayor que 2147483647 como si fuera igual a 2147483647.

Si desea hacer una comparación de cadenas de números grandes, entonces la solución es fácil. Solo necesita agregar 1 o más caracteres que no sean dígitos a ambos lados de la condición. El siguiente 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%" 

produce el resultado correcto de comparación de cuerdas

 "30000000000000" is less than "40000000000" 

Pero en la mayoría de los casos, esto no es lo que se quiere.

Si desea hacer una comparación numérica, entonces el proceso es un poco más complicado. Necesita convertir el número en una cadena que se ordenará correctamente como un número. Esto se logra prefijando la cadena numérica con ceros de manera que ambas cadenas numéricas tengan el mismo ancho. La solución más simple es determinar la cantidad máxima de dígitos que necesita para admitir, digamos 15 para este ejemplo. Por lo tanto, prefija cada valor con 15 ceros y luego solo conserva los 15 caracteres más a la derecha mediante una operación de subcadena. También debe agregar un no dígito a ambos lados como antes; las comillas de nuevo funcionan bien.

Este guion –

 @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 

produce –

 030000000000000 is greater than 000040000000000 

Tenga en cuenta que el prefijo a la izquierda con espacios funciona tan bien como ceros.

Luego puede eliminar los ceros a la izquierda siempre que lo desee utilizando lo siguiente (o adaptarse para eliminar espacios iniciales)

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

Normalmente no tratamos con números grandes en archivos por lotes. Pero pueden surgir fácilmente si miramos el espacio libre en un disco duro. Las unidades de disco Terabyte ahora son relativamente económicas. Así es como me topé por primera vez con la comparación de números grandes en https://stackoverflow.com/a/9099542/1012053

Elegí admitir 15 dígitos en mi ejemplo porque eso equivale a casi 999 terabytes. Me imagino que pasará un tiempo antes de que tengamos que lidiar con unidades de disco más grandes que eso. (¡Pero quién sabe!)

EDITAR – Mi descripción de cómo IF analiza números es intencionalmente demasiado simplista. SI realmente admite números negativos, así como la notación hexadecimal y octal. Consulte las Reglas para ver cómo CMD.EXE analiza los números para obtener una explicación mucho más completa.