Выражения сравнения

b

1. Синтаксис и типы данных: что вы получите от чёткого понимания операторов

Выражения сравнения в Delphi строятся на операторах =, <>, <, >, <=, >=. Результат всегда имеет тип Boolean (True или False). Вы избежите классической ошибки — путаницы между оператором присваивания := и оператором равенства =. Компилятор Delphi строго различает эти контексты: внутри условия if или while использование := вызовет ошибку компиляции, что защищает вас от C-подобных багов.

Вы получите гарантию, что для всех порядковых типов (Integer, Char, Enum) сравнение работает по значению их порядковых номеров. Для типа Boolean действует соглашение: False (0) < True (1). Это позволяет писать лаконичные проверки — например, if SomeBoolean <> False then эквивалентно if SomeBoolean then. Вы сократите код без потери читаемости.

2. Сравнение строк: преимущества контроля над локалью и регистром

Вы получите предсказуемое поведение при сравнении строк, используя специфические функции вместо прямого оператора. Прямое использование if Str1 = Str2 then в Delphi по умолчанию использует посимвольное сравнение с учётом регистра (case-sensitive) на основе кодов символов. Это быстро, но игнорирует правила сортировки языка пользователя. Если вам нужно сравнение без учёта регистра (case-insensitive), вы должны явно вызывать SameText(Str1, Str2) — это даёт производительность на 15-20% выше, чем двойное приведение к UpperCase.

Для сортировки строк по правилам локали используйте AnsiCompareStr (с учётом локали, регистрозависимое) или AnsiCompareText (с учётом локали, регистронезависимое). Вы получите корректную сортировку для символов с диакритикой (ä, ö, ü, é), что критично для приложений с мультиязычным интерфейсом. Без этих функций строка 'Ä' может быть отсортирована после 'Z' в стандартном порядке ASCII, что является ошибкой для немецкого или шведского языка.

3. Сравнение вещественных чисел: как избежать скрытых ошибок округления

Прямое сравнение чисел с плавающей точкой (типы Single, Double, Extended) через if A = B then ненадёжно из-за погрешностей арифметики IEEE 754. Вы получите неожиданный результат False для математически равных значений (например, 0.1 + 0.2 ≠ 0.3). Решение — использовать сравнение с эпсилоном (допустимой погрешностью). В Delphi определена константа Epsilon для каждого вещественного типа в модуле System.Math: SingleEpsilon, DoubleEpsilon, ExtendedEpsilon.

Вы получите надёжную проверку через функцию SameValue(A, B, Epsilon). Если третий параметр опущен, используется значение эпсилон по умолчанию, которое масштабируется относительно величин A и B. Для сравнения больше/меньше с учётом погрешности используйте CompareValue(A, B, Epsilon), которая возвращает -1, 0 или 1. Это избавит вас от логических ошибок в финансовых или научных расчётах.

4. Оптимизация условий сравнения: ускорение работы циклов и фильтров

Вы получите прирост производительности до 30% на больших наборах данных, если будете соблюдать порядок операндов в логических выражениях. Компилятор Delphi использует короткое замыкание (short-circuit evaluation) для операторов and и or, если включена директива {$B-} (по умолчанию). То есть, если левая часть условия if (A > 0) and (B / A > 5) then даёт False, правая часть не вычисляется, и деление на ноль не происходит. Вы автоматически защищены от AV (Access Violation) при проверке указателя и его поля в одном условии: if Assigned(Pointer) and (Pointer^ > 0) then.

Для оптимизации сравнения строк в циклах — приводите строки к одному регистру один раз до цикла, а не внутри условия. Например, вычислите UpperStr := UpperCase(SearchStr); и затем используйте SameText или прямое сравнение с предварительно преобразованными данными. Это снижает нагрузку на аллокатор памяти, так как каждая операция UpperCase создаёт новую строку. Вы получите выигрыш во времени выполнения при обработке массивов от 10 000 элементов и выше.

  1. Располагайте самое быстрое и наиболее вероятное условие слева от and/or — это максимизирует эффект короткого замыкания.
  2. Для проверки принадлежности диапазону используйте if (X in [Low..High]) then вместо двойного сравнения с and — компилятор генерирует более эффективный код для перечисляемых типов.
  3. Избегайте сравнения строк через if Str = '' then; используйте if Length(Str) = 0 then или if Str = '' then (компилятор оптимизирует обе конструкции одинаково, но вторая читаемее).
  4. Для проверки пустого указателя на объект не используйте if Obj = nil then в 64-битных приложениях — это корректно, но быстрее через Assigned(Obj), так как функция убирает накладные расходы на приведение типа.
  5. При сравнении больших массивов записей (record) используйте CompareMem(Pointer1, Pointer2, SizeOf(TMyRecord)) для побайтового сравнения — это быстрее, чем поочередное сравнение каждого поля, если запись не содержит упакованных (packed) полей.

5. Обработка исключений при сравнении: защита от некорректных данных

Вы получите стабильную работу программы без неожиданных прерываний, если предусмотрите варианты, когда сравнение может выбросить исключение. Основные сценарии: сравнение вариантных (Variant) переменных, содержащих Null; сравнение объектов, не реализующих интерфейс IComparable; сравнение строк с некорректной кодировкой. Используйте конструкцию try...except или предварительные проверки через VarIsNull и VarIsEmpty для вариантов.

При сравнении пользовательских классов переопределяйте метод Equals (не оператор =) из TObject. Стандартный оператор = для объектных ссылок сравнивает указатели, а не содержимое. Вы получите логически корректное сравнение только после переопределения. Пример: if MyObject1.Equals(MyObject2) then — это исключает путаницу между ссылками и значениями. Для структур (record) оператор = работает как поэлементное сравнение только в Delphi 2009+, при этом все поля должны быть простых типов или также поддерживать сравнение.

6. Технические рекомендации по настройкам компилятора для сравнений

Вы получите дополнительный уровень безопасности при компиляции, если включите директивы проверки диапазонов и переполнения. Директива {$R+} включает проверку выхода за границы массива — при сравнении индексов массива вы будете получать исключение ERangeError, а не молчаливое чтение мусора. Директива {$Q+} включает проверку переполнения целочисленной арифметики (для типов Integer, Cardinal). При сравнении чисел, полученных в результате сложения, вы защищены от неожиданного перехода через ноль.

Для проектов с критическими вычислениями (финансы, управление оборудованием) отключайте оптимизацию сравнений ({$O-}) на критических участках. Оптимизатор может переставлять порядок вычисления операндов, что в редких случаях нарушает логику короткого замыкания. Вы получите предсказуемое выполнение кода ценой 5-10% производительности на конкретном участке. В стандартных бизнес-приложениях дефолтная оптимизация ({$O+}) безопасна.

  1. Используйте {$B+} только если вам необходимо вычисление всех частей логического выражения (например, для триггеров в БД-логике). Это замедляет выполнение и может маскировать ошибки AV.
  2. Для модулей, работающих с внешними DLL (например, через external), всегда явно указывайте соглашение о вызовах (cdecl, stdcall) и тип возвращаемого значения Boolean. Несоответствие размера типа (1 байт в C vs 4 байта в Delphi) приведёт к чтению мусора при сравнении результата.
  3. При работе с записями, передаваемыми в WinAPI, используйте packed record и проверяйте выравнивание. Неверное выравнивание полей может привести к тому, что сравнение через CompareMem даст ложный отрицательный результат из-за padding-байтов.
  4. Для многопоточных приложений всегда защищайте блоки сравнения и присваивания мьютексами (TCriticalSection) или используйте атомарные операции (TInterlocked.CompareExchange). Прямое сравнение двух потоков без синхронизации может привести к гонке данных.
  5. Используйте инструмент PGO (Profile-Guided Optimization) в Delphi Enterprise редакциях для оптимизации ветвлений сравнений. PGO собирает реальную статистику выполнения и переупорядочивает код, чтобы наиболее частые пути сравнения были предсказаны процессором (branch prediction).

Добавлено: 27.04.2026