Ошибка пакетного файла Windows. Как может 30000000000000 равняться 40000000000?

IF дает неправильный ответ, когда я пытаюсь сравнить 2 больших числа.

Например, этот простой командный файл

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

производит

30000000000000 is equal to 40000000000

Что происходит и как это исправить?

Ответ 1

Если обе стороны сравнения IF состоят строго из десятичных цифр, тогда IF интерпретирует обе стороны как числа. Это то, что позволяет IF правильно определить, что 10 больше 9. Если у вас есть символы без цифр, тогда IF выполняет сравнение строк. Например, "10" меньше "9", потому что кавычки не являются цифрами, а 1 сортируется ниже 9.

Причина, по которой сравнение в вопросе не удается, заключается в том, что CMD.EXE не может обрабатывать числа, большие, чем 2147483647. Нечетная проектная ошибка в IF рассматривает любое число, большее, чем 2147483647, как равное 2147483647.

Если вы хотите провести сравнение строк больших чисел, то это решение легко. Вам просто нужно добавить 1 или более несимвольных символов в обе стороны условия. Следующие 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%"

выводит правильный результат сравнения строк

"30000000000000" is less than "40000000000"

Но в большинстве случаев это не то, что нужно.

Если вы хотите сделать числовое сравнение, тогда процесс будет более активным. Вам нужно преобразовать число в строку, которая будет правильно сортироваться в виде числа. Это достигается путем префикса числовой строки нулями таким образом, чтобы обе числовые строки имели одинаковую ширину. Самое простое решение - определить максимальное количество цифр, которое вам нужно поддерживать - скажем, 15 для этого примера. Таким образом, вы префикс каждого значения с 15 нулями, а затем сохраняете только самые 15 символов, используя операцию подстроки. Вам также необходимо добавить несимволы в обе стороны, как и раньше, - снова кавычки работают хорошо.

Этот 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

производит -

030000000000000 is greater than 000040000000000

Обратите внимание, что левое префикс с пробелами работает так же хорошо, как и нули.

Затем вы можете удалить ведущие нули всякий раз, когда хотите использовать следующее (или адаптировать для удаления ведущих пространств)

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

Обычно мы не имеем дело с большими числами в пакетных файлах. Но они могут легко возникнуть, если мы посмотрим на свободное место на жестком диске. Терабайтовые дисковые накопители теперь относительно недороги. Вот как я впервые столкнулся с сопоставлением больших чисел на fooobar.com/info/527344/...

Я решил поддержать 15 цифр в моем примере, потому что это эквивалентно почти 999 терабайтам. Я предполагаю, что пройдет какое-то время, прежде чем нам придется иметь дело с дисками большего размера. (Но кто знает!)

EDIT. Мое описание того, как IF анализирует числа, преднамеренно чрезмерно упрощено. IF фактически поддерживает отрицательные числа, а также шестнадцатеричную и восьмеричную нотации. См. Правила того, как CMD.EXE анализирует номера для более подробного объяснения.