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

1. Синтаксис и типы данных: что вы получите от чёткого понимания операторов
Выражения сравнения в Delphi строятся на операторах =, <>, <, >, <=, >=. Результат всегда имеет тип Boolean (True или False). Вы избежите классической ошибки — путаницы между оператором присваивания := и оператором равенства =. Компилятор Delphi строго различает эти контексты: внутри условия if или while использование := вызовет ошибку компиляции, что защищает вас от C-подобных багов.
Вы получите гарантию, что для всех порядковых типов (Integer, Char, Enum) сравнение работает по значению их порядковых номеров. Для типа Boolean действует соглашение: False (0) < True (1). Это позволяет писать лаконичные проверки — например, if SomeBoolean <> False then эквивалентно if SomeBoolean then. Вы сократите код без потери читаемости.
- Оператор
=(равно) — только для сравнения, не для присваивания; компилятор выдаст ошибку, если вы напишетеif x := 5 then. - Оператор
<>(не равно) — работает для всех типов, включая записи (record) при условии поддержки компилятором, начиная с Delphi 2009. - Операторы
<и>— для вещественных чисел сравнивают математические значения, но с учётом ошибок округления (см. раздел 3). - Для строковых типов (
string,AnsiString,WideString) сравнение по умолчанию зависит от настройкиSystem.SysUtils.AnsiCompareStrв зависимости от текущей локали системы. - Для вариантного типа
Variantсравнение выполняется с приведением типов, что может привести к исключению, если типы несовместимы.
2. Сравнение строк: преимущества контроля над локалью и регистром
Вы получите предсказуемое поведение при сравнении строк, используя специфические функции вместо прямого оператора. Прямое использование if Str1 = Str2 then в Delphi по умолчанию использует посимвольное сравнение с учётом регистра (case-sensitive) на основе кодов символов. Это быстро, но игнорирует правила сортировки языка пользователя. Если вам нужно сравнение без учёта регистра (case-insensitive), вы должны явно вызывать SameText(Str1, Str2) — это даёт производительность на 15-20% выше, чем двойное приведение к UpperCase.
Для сортировки строк по правилам локали используйте AnsiCompareStr (с учётом локали, регистрозависимое) или AnsiCompareText (с учётом локали, регистронезависимое). Вы получите корректную сортировку для символов с диакритикой (ä, ö, ü, é), что критично для приложений с мультиязычным интерфейсом. Без этих функций строка 'Ä' может быть отсортирована после 'Z' в стандартном порядке ASCII, что является ошибкой для немецкого или шведского языка.
- SameText — оптимально для проверки равенства строк без учёта регистра в английском и большинстве западноевропейских языков.
- AnsiCompareStr — возвращает -1, 0 или 1, что позволяет использовать результат напрямую в алгоритмах сортировки (например, для TStringList.CustomSort).
- AnsiCompareText — аналог AnsiCompareStr, но игнорирует регистр. Используйте для поиска без учёта регистра в локализованных данных.
- CompareStr и CompareText — старые функции, работающие по кодам символов. Не рекомендуются для новых проектов, где требуется локализация.
- WideCompareStr и WideCompareText — для строк типа
WideString(UTF-16). Необходимы при работе с символами из старших плоскостей Unicode (суррогатные пары).
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. Это избавит вас от логических ошибок в финансовых или научных расчётах.
- SameValue(A, B) — возвращает True, если разница между A и B меньше, чем Max(Min(A,B)*1E-12, 1E-12). Подходит для большинства инженерных задач.
- IsZero(A, Epsilon) — проверяет, что число близко к нулю. Избегайте сравнения вида
if A = 0 thenдля дробных чисел. - CompareValue — альтернатива if-else цепочкам, улучшает читаемость кода в длинных условиях.
- Для типа
Currency(фиксированная точка, 4 знака после запятой) прямое сравнение=безопасно, так как он не подвержен ошибкам округления IEEE 754. - При сериализации в JSON или XML всегда округляйте до нужной точности перед сравнением, чтобы избежать ошибок из-за разной длины мантиссы.
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 элементов и выше.
- Располагайте самое быстрое и наиболее вероятное условие слева от
and/or— это максимизирует эффект короткого замыкания. - Для проверки принадлежности диапазону используйте
if (X in [Low..High]) thenвместо двойного сравнения сand— компилятор генерирует более эффективный код для перечисляемых типов. - Избегайте сравнения строк через
if Str = '' then; используйтеif Length(Str) = 0 thenилиif Str = '' then(компилятор оптимизирует обе конструкции одинаково, но вторая читаемее). - Для проверки пустого указателя на объект не используйте
if Obj = nil thenв 64-битных приложениях — это корректно, но быстрее черезAssigned(Obj), так как функция убирает накладные расходы на приведение типа. - При сравнении больших массивов записей (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+, при этом все поля должны быть простых типов или также поддерживать сравнение.
- VarIsNull(V) — проверка, что Variant содержит Null (например, из базы данных). Сравнение Null с чем-либо всегда даёт False по стандарту SQL, но в Delphi может вызвать исключение EVariantInvalidOpError.
- VarIsEmpty(V) — проверка на Unassigned. Никогда не сравнивайте Variant с числом или строкой, не проверив его предварительно на Empty/Null.
- Supports(Unknown, IComparable) — для объектов, полученных через интерфейсы. Если интерфейс не поддерживает сравнение, вызов метода CompareTo приведёт к AV.
- При сравнении строк разной кодировки (AnsiString vs UTF8String vs WideString) Delphi выполняет автоматическое преобразование, но это может повлечь потерю данных, если кодировка не конвертируема. Используйте
UTF8ToStringиStringToUTF8для явного контроля. - Для записей с полями типа
stringили динамическими массивами стандартное сравнение записей через=запрещено компилятором. Используйте собственный метод сравнения с ручной проверкой каждого поля.
6. Технические рекомендации по настройкам компилятора для сравнений
Вы получите дополнительный уровень безопасности при компиляции, если включите директивы проверки диапазонов и переполнения. Директива {$R+} включает проверку выхода за границы массива — при сравнении индексов массива вы будете получать исключение ERangeError, а не молчаливое чтение мусора. Директива {$Q+} включает проверку переполнения целочисленной арифметики (для типов Integer, Cardinal). При сравнении чисел, полученных в результате сложения, вы защищены от неожиданного перехода через ноль.
Для проектов с критическими вычислениями (финансы, управление оборудованием) отключайте оптимизацию сравнений ({$O-}) на критических участках. Оптимизатор может переставлять порядок вычисления операндов, что в редких случаях нарушает логику короткого замыкания. Вы получите предсказуемое выполнение кода ценой 5-10% производительности на конкретном участке. В стандартных бизнес-приложениях дефолтная оптимизация ({$O+}) безопасна.
- Используйте
{$B+}только если вам необходимо вычисление всех частей логического выражения (например, для триггеров в БД-логике). Это замедляет выполнение и может маскировать ошибки AV. - Для модулей, работающих с внешними DLL (например, через
external), всегда явно указывайте соглашение о вызовах (cdecl,stdcall) и тип возвращаемого значения Boolean. Несоответствие размера типа (1 байт в C vs 4 байта в Delphi) приведёт к чтению мусора при сравнении результата. - При работе с записями, передаваемыми в WinAPI, используйте
packed recordи проверяйте выравнивание. Неверное выравнивание полей может привести к тому, что сравнение черезCompareMemдаст ложный отрицательный результат из-за padding-байтов. - Для многопоточных приложений всегда защищайте блоки сравнения и присваивания мьютексами (TCriticalSection) или используйте атомарные операции (
TInterlocked.CompareExchange). Прямое сравнение двух потоков без синхронизации может привести к гонке данных. - Используйте инструмент PGO (Profile-Guided Optimization) в Delphi Enterprise редакциях для оптимизации ветвлений сравнений. PGO собирает реальную статистику выполнения и переупорядочивает код, чтобы наиболее частые пути сравнения были предсказаны процессором (branch prediction).
Добавлено: 27.04.2026
