Странные результаты с IF

Вдохновленный этим вопросом и его ответами, я провел некоторое тестирование. Один ответ предполагает, что числа слишком велики (из 32-битного целого), и они усекаются, но это не объясняет результаты. Также очевидно, что он не сравнивает обе стороны как строки (как я и ожидал). Кажется, что if запутывается и думает: "Ну, я не знаю - дай ИСТИНУ". (С помощью neq, gtr, lss вместо equ, geq, leq всегда дают ЛОЖЬ).

Код работает как положено, если любой из a и / или b находятся в пределах 32-битного целого числа или содержат любой символ из [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 -)

Есть ли какое-то логическое объяснение этому, или это просто то, с чем мы должны жить, не задумываясь?

2 ответа

Решение

Причину этого результата можно найти в документации по функции strtol, которая используется в первую очередь при использовании операторов сравнения. EQU, NEQ, LSS, LEQ, GTR, GEQ как объяснено в моем ответе на Symbol, эквивалентном NEQ, LSS, GTR и т. д. в пакетных файлах Windows.

Возвращаемое значение
В случае успеха функция возвращает преобразованное целое число как длинное значение типа int.
Если действительное преобразование не может быть выполнено, возвращается нулевое значение (0L).
Если считываемое значение выходит за пределы диапазона представимых значений с помощью длинного целого, функция возвращает LONG_MAX или LONG_MIN (определенный в ;), а для errno задано значение ERANGE.

Последнее предложение является наиболее важным здесь.

Похоже, если в cmd.exe закодирован как этот код C:

#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;
}

Компиляция этого кода C как консольного приложения и запуск исполняемого файла с параметрами 333333333333 444444444444 например результаты в выводе:

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)

И запуск исполняемого файла с параметрами 333333333333 222222222222 например результаты в выводе:

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)

Примечание. Номер ошибки и соответствующая строка ошибки могут отличаться в зависимости от используемого компилятора C или стандартной библиотеки.

В обоих тестовых примерах оба аргумента приводили к 32-разрядному целочисленному переполнению со знаком при преобразовании из строки в long int. Следовательно strtol возвращается для всех четырех значений LONG_MAX и установить errno в ERANGE, Но условие переполнения не оценивается кодом IF в cmd.exe, Просто проверяется результат преобразования и на какой символ указывает указатель конца для обоих аргументов, как в коде C выше.

Другими словами, IF обрабатывает использование операторов сравнения EQU, NEQ, LSS, LEQ, GTR, GEQ всегда целочисленное сравнение, пока преобразование из строки в целое число не завершится неудачно ни для одного из двух аргументов из-за недопустимого символа в строках аргумента. Условие вне диапазона не является причиной, если IF не выполняет целочисленное сравнение.

Сравнение строк выполняется только в том случае, если одна из двух строк аргумента содержит недопустимый символ для целого числа.

Это предел.

C:>set /A a=333333333333
Invalid number.  Numbers are limited to 32-bits of precision.
Другие вопросы по тегам