Лично меня достало ради MulDiv вечно подключать Winapi.Windows. Знакомая ситуация? Пишешь код с прицелом на кроссплатформенность, всё красиво, а тут — бац! — нужна MulDiv. И вот уже тянешь за собой всю Windows-зависимость, как якорь.
Рано или поздно надо уходить в кроссплатформенную разработку, почему бы и не избавиться от этой вредной зависимости прямо сейчас?
Что не так с Windows.MulDiv?
Во-первых, она привязана только к Windows. Хотите собрать проект под Linux или Android? Прощай, MulDiv. Во-вторых, Windows API выполняет кучу проверок, обрабатывает edge-кейсы, делает округление — всё это стоит производительности.
Но главное — философия. Зачем тащить за собой системную зависимость ради простой математической операции? Это как использовать атомный реактор, чтобы вскипятить чайник.
При использовании Windows.MulDiv в Delphi замечена одна интересная особенность. Если на вход подавать переменные, определённые как Byte, MulDiv работает в 1.5-3 раза быстрее, чем при всех остальных типах. В примере, который можно скачать в конце статьи, это можно проверить по кнопке MulDiv Test.

Вряд ли это связано непосредственно с WinAPI функцией. Скорее всего, разница связана с:
- Неявным преобразованием типов (компиляторная оптимизация)
- Эффективностью кэша (меньше данных — больше помещается)
- Особенностями передачи параметров в stdcall
Хотя, после ряда экспериментов, подозрения с MulDiv не сняты. Такое ощущение, что проверяет Integer’ы на диапазон, и если они могут быть байтами — то взлетает. Проверю потом. Жаль, нет исходников от Винды.
Однако, несмотря на все кажущиеся потери производительности, это очень быстрая функция. Поэтому и столь живуча. Проще в очередной раз использовать функцию, чем пытаться угнаться за ней. Но мы попробуем.
Вариант 1: Просто и понятно
|
1 2 3 4 5 6 7 |
function MulDivSimple(A, B, C: Integer): Integer; inline; begin if C = 0 then Result := -1 // Совместимость с Windows API else Result := Int64(A) * Int64(B) div C; end; |
Что здесь важно:
- Int64(A) * Int64(B) — не просто прихоть. Умножение двух 32-битных чисел даёт 64-битный результат. Если не привести к Int64, произойдёт переполнение.
- inline — директива не для красоты. Для такой маленькой функции накладные расходы на вызов могут быть сравнимые с самими вычислениями. inline позволяет компилятору встроить код прямо в место вызова.
- Проверка C = 0 — это философия. Можно было бы положиться на исключение от деления на ноль, но:
- Исключения дорогие
- Windows API возвращает -1 в этом случае
- Предотвращение проблемы лучше, чем её обработка
Минусы:
- Может быть медленнее виндусовой версии (но ненамного)
- Результат может не совпадать из-за отсутствия округления по правилам округления
Когда использовать: Когда нужно просто «умножить и разделить», а совпадение с Windows не критично (иногда бывает разница в единицу, ну это ж такой пустяк).
Вариант 2: Точная совместимость
Функция ниже оставлена для понимания, что тут происходит. Вместо неё работает другая, расширенная версия, она в конце этого раздела.
|
1 2 3 4 5 6 7 |
function MulDiv(A, B, C: Integer): Integer; inline; begin if C = 0 then Result := -1 else Result := (Int64(A) * Int64(B) + C div 2) div C; end; |
Магия округления: + C div 2
Windows MulDiv округляет к ближайшему целому. Формула простая:
- Если остаток от деления ≥ половине делителя — округляем вверх
- Иначе — вниз
Добавление C div 2 перед делением — это математический трюк для такого округления. Пример:
|
1 2 3 4 5 6 7 8 |
49 * 104 = 5096 5096 / 246 = 20.723… Остаток: 5096 mod 246 = 164 164 * 2 = 328 > 246 => округляем до 21 Наша формула: 5096 + 123 (246 div 2) = 5219 5219 / 246 = 21.21… => 21 |
Плюсы:
- Полная совместимость с Windows API
- Один в один результаты
Минусы:
- Медленнее предыдущей из-за дополнительного сложения. А вот функция ниже ещё медленней, но зато результат один в один. Расхождение по времени с оригиналом очень небольшое.
Когда использовать: При портировании кода с Windows, где важна точность совпадения. Для бухгалтера разница в единицу это не пустяк, а срок. Хотя… бухгалтеры ведь работают с currency, им не нужен MulDiv.
Переходим к правильной реализации. В чём проблема: такая понятная и милая функция не округляет правильно на отрицательных числах. Поэтому, вместо неё будем использовать следующую функцию.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function MulDiv(A, B, C: Integer): Integer; inline; var Temp: Int64; Sign: Integer; begin if C = 0 then Exit(-1); // Излишняя оптимизация: крадёт такты // if (A OR B OR C) AND $80000000 = 0 then // if (Integer(Cardinal(A) or Cardinal(B) or Cardinal(C)) >= 0) then // Exit((Int64(A) * Int64(B) + C div 2) div C); Temp := Int64(A) * Int64(B); if Temp = 0 then Exit(0) else if Temp < 0 then begin if C < 0 then begin Sign := 1; C := -C; end else Sign := -1; Temp := -Temp; end else begin if C < 0 then begin Sign := -1; C := -C; end else Sign := 1; end; Temp := Temp + (C shr 1); // C div 2; Result := Sign * Temp div C; end; |
Здесь почти всё то же самое. Мы дополнительно анализируем знак будущего результата и приводим аргументы в положительный вид. Далее, вычисляем всё, как для положительных значений, и назначаем ранее найденный знак.
Специализированная версия для x32
После оптимизации функции выше, получаем более быстрый вариант:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
function MulDiv32(A, B, C: Integer): Integer; inline; var Temp: Int64; Negative: Boolean; begin // Проверка деления на ноль if C = 0 then // Поведение как у Windows MulDiv Exit(-1); // Вычисляем произведение в Int64 // для избежания переполнения Temp := Int64(A) * Int64(B); // Определяем знак результата Negative := (Temp < 0) xor (C < 0); // Работаем с абсолютными значениями // для правильного округления if Temp < 0 then Temp := -Temp; if C < 0 then C := -C; // Округление к ближайшему целому: // добавляем половину делителя Temp := Temp + (C shr 1); Result := Temp div C; // Применяем знак if Negative then Result := -Result; end; |

Про тестирование чуть ниже. Но давайте сравним показатели MulDivRounded и MulDiv x32. Последняя явно быстрее.
Специализированная версия для x64
Благодаря комментарию от Peter протестировал вариант, который используется в Lazarus. И, о чудо, под x64 он оказался намного быстрее даже оригинала! Peter, спасибо!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function MathRound(AValue: Double): Int64; inline; begin if AValue >= 0 then Result := Trunc(AValue + 0.5) else Result := Trunc(AValue - 0.5); end; function MulDiv64(A, B, C: Integer): Integer; inline; begin if C = 0 then Result := -1 else if B = C then Result := A else Result := MathRound(int64(A) * int64(B) / C); end; |
Связано это с тем, что для этого случая используется нативная 64-битная арифметика. Деление через FPU/SSE в 3-5 раз быстрее целочисленного. Умножение int64(A) * int64(B) выполняется одной инструкцией:
|
1 2 3 |
movsxd rax, ecx ; расширение A до 64 бит movsxd rdx, edx ; расширение B до 64 бит imul rax, rdx ; 64-bit умножение за 3 такта |
SSE2-конвейер работает параллельно. Пока целочисленный конвейер занят другими операциями, SSE2-блок выполняет деление параллельно:
|
1 2 3 4 5 6 7 8 |
; Конвертация и деление cvtsi2sd xmm0, rax ; int64 -> double cvtsi2sd xmm1, r8 ; C -> double divsd xmm0, xmm1 ; деление (10-14 тактов, но параллельно!) ; Округление через MathRound инлайнится addsd xmm0, [0.5] ; или subsd для отрицательных cvttsd2si rax, xmm0 ; обратно в int64 |

Сравним MulDivRounded и MulDiv x64. Последняя не просто опережает общую реализацию, но и обгоняет оригинал Windows.MulDiv. Причём данные округляются правильно.
И кстати, если сравнить с рисунком выше, можно заметить, насколько 64-битная версия неповоротлива на 32 битах.
Итоговая MulDivX
С учётом новых специализированных версий, сделаем общую функцию, где объединим их плюсы:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
function MathRound(AValue: Double): Int64; inline; begin if AValue >= 0 then Result := Trunc(AValue + 0.5) else Result := Trunc(AValue - 0.5); end; function MulDivX(A, B, C: Integer): Integer; inline; {$IF Defined(CPUX64)} begin if C = 0 then Result := -1 else if B = C then Result := A else Result := MathRound(int64(A) * int64(B) / C); {$ELSE} var Temp: Int64; Negative: Boolean; begin if C = 0 then Exit(-1); Temp := Int64(A) * Int64(B); Negative := (Temp < 0) xor (C < 0); if Temp < 0 then Temp := -Temp; if C < 0 then C := -C; Temp := Temp + (C shr 1); Result := Temp div C; if Negative then Result := -Result; {$ENDIF} end; |
Вариант 3: Максимальная скорость
Ахтунг! Ассемблер!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
function MulDivFast(A, B, C: Integer): Integer; {$IF Defined(CPUX86)} asm // EAX = A, EDX = B, ECX = C test ecx, ecx jz @div_zero imul edx // EDX:EAX = A * B (64-bit) idiv ecx // EAX = result, EDX = remainder ret @div_zero: mov eax, -1 end; {$ELSEIF Defined(CPUX64)} asm // ECX = A, EDX = B, R8 = C .NOFRAME // Ускоряемся - не создаём стековый фрейм test r8d, r8d jz @div_zero mov eax, ecx // A imul edx // EDX:EAX = A * B mov ecx, r8d // C idiv ecx // EAX = result ret @div_zero: mov eax, -1 end; {$ELSE} begin if C = 0 then Result := -1 else Result := Int64(A) * Int64(B) div C; end; {$ENDIF} |
Разбираем ассемблер:
- .NOFRAME в x64 — говорим компилятору, что не нужно создавать стандартный стековый фрейм. Для такой простой функции это излишне.
- test r8d, r8d — быстрая проверка на ноль. test выполняет побитовое И, устанавливая флаги, но не сохраняя результат.
- imul edx — умное умножение. На x86 умножает EAX на EDX, результат в EDX:EAX (64-битный). На x64 — аналогично.
- idiv ecx — деление 64-битного числа на 32-битное.
Философия минимализма: Всего 4 инструкции для основного случая. Ничего лишнего. Бритва Оккама в действии.
Плюсы:
- В 3 раза быстрее Windows API
- Кроссплатформенность (разные реализации для x86/x64/остальное)
- Проверка деления на ноль
Минусы:
- Результат может отличаться на 1 из-за отсутствия округления
- Ассемблер, 🤬 … Люди его не любят. И я тоже.
Когда использовать: В коде, критичном по производительности (performance critical) — рендеринг, аудиообработка, игры.
Тестируем
Мы хотим проверить работоспособность, корректность, быстродействие и кроссплатформенность.
Работоспособность
Кнопка Test1. Выбираем радиокнопками нужный метод. Проверяем A * B div C выбранным методом.

Видим, что на 0 реагирует правильно. Для выбранного метода разницу в 1 прощаем.
Быстродействие и корректность
Кнопка Test2. Выбираем радиокнопками нужный метод. При нажатии кнопки запускается генерация данных. Длина массива определяется в поле Iterations. По умолчанию это миллион (!) итераций на случайных числах от -256 до 256 (то есть байтом тут не пахнет). Проверяем A * B div C на этом количестве итераций, сравниваем с результатом функции Windows.MulDiv.
В результате работы этого теста, последней строкой будет запись: Diff: 0, Diff>1: 0. Это показывается количество несовпадающих значений, и количество, где разница больше 1. В идеале количество несовпадений должно быть 0. Но мы прощаем это для вариантов 1 и 3. Главное, чтобы количество по разнице 1 было всегда 0.

И вот в результате тестирования, видим, что метод, который позиционируется, как абсолютно правильный и совпадающий с Windows.MulDiv — неправильный! Выяснилось, что такая казалось бы уютная функция не распространяется на отрицательные числа. В исходниках выше представлена полная рабочая версия этой функции.

Вот так должна выглядеть функция с правильным округлением. Да, время выполнения увеличилось, но тут уж надо выбирать, что важнее, скорость или абсолютное совпадение. Да и разница по времени, если честно, не особо, по сравнению с оригиналом (ниже есть сравнительные таблички).
Статистический анализ быстродействия
Кнопка Test3. Что тут происходит. Создаётся набор данных в количестве требуемых итераций, и на нём прогоняется каждый метод, включая Windows.MulDiv. И весь этот процесс повторяется сто раз, с целью накопить устойчивую статистику. В конце выводится таблица с временными характеристиками.

Кроссплатформенность
Создаём Multi-Device Application. Пишем небольшой код, где наш MulDiv используется на всю катушку:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
implementation uses System.Math, IP76MulDiv; {$R *.fmx} function GetRValue(rgba: DWORD): Byte; begin Result := Byte(rgba); end; function GetGValue(rgba: DWORD): Byte; begin Result := Byte(rgba shr 8); end; function GetBValue(rgba: DWORD): Byte; begin Result := Byte(rgba shr 16); end; function RGB(r, g, b: Byte): DWORD; begin Result := (r or (g shl 8) or (b shl 16)); end; function muldiv(A, B, C: Integer): Integer; begin // Специально берём быстрый ассемблер, // но с возможным отклонением на 1 Result := MulDivFast(A, B, C); end; function Darker(Color:TColor; Percent:Byte): TColor; var r, g, b: Byte; begin r := GetRValue(Color); g := GetGValue(Color); b := GetBValue(Color); r := r-muldiv(r, Percent, 100); g := g-muldiv(g, Percent, 100); b := b-muldiv(b, Percent, 100); Result := RGB(r, g, b); end; function Lighter(Color:TColor; Percent:Byte):TColor; var r, g, b: Byte; begin r := GetRValue(Color); g := GetGValue(Color); b := GetBValue(Color); r := r+muldiv(255-r, Percent, 100); g := g+muldiv(255-g, Percent, 100); b := b+muldiv(255-b, Percent, 100); Result := RGB(r, g, b); end; procedure THeaderFooterForm.FormCreate(Sender: TObject); begin HeaderLabel.Text := TOSVersion.Name + ': MulDiv Test'; Button1Click(Button1); end; procedure THeaderFooterForm.Button1Click(Sender: TObject); var bmp: TBitmap; W, H: Integer; X, Y, DX, DY: Single; R: TRectF; begin W := Trunc(ImageControl1.Width); H := Trunc(ImageControl1.Height); bmp := TBitmap.Create(W, H); try bmp.Canvas.BeginScene; DX := Max(W/100, 3); DY := Max(H/32, DX); Y := 0; R.Width := DX; R.Height := DY; var Inverted := False; while Y < bmp.Height do begin X := 0; H := 0; while X<W do begin R.SetLocation(X, Y); var C := ComboColorBox1.Color; if Inverted then C := Darker(C, H) else C := Lighter(C, H); var B := TStrokeBrush.Create(TBrushKind.Solid, $FF000000 OR C); try bmp.Canvas.FillRect(R, 0.5, B); finally B.Free; end; Inc(H); X := X + DX; end; Inverted := not Inverted; Y := Y + DY; end; finally bmp.Canvas.EndScene; ImageControl1.Bitmap := bmp; bmp.Free; end; end; |

Результат кроссплатформенного проекта одинаков. Наш MulDiv работает везде. Один и тот же код работает, как минимум, и на Windows, и на Android. На остальных ОСях успешно компилируется, но проверить на физическом устройстве пока возможности нет. Предполагаю, что поведение будет аналогичным.
Бенчмарки
Intel(R) Core(TM) i5-9400 CPU @ 2.90GHz, Delphi XE 7, 1 млн итераций:
Если применяем только переменные типа Byte:

| Вариант | 32-bit Время (мс) | Прирост скорости | 64-bit Время (мс) | Прирост скорости |
|---|---|---|---|---|
| Windows API | 5,23 | 1 | 11,03 | 1 |
| Вариант 1 (без округления) | 9,42 | 0,56 | 9,07 | 1,22 |
| Вариант 2 (с округлением / MulDivX) | 13,75 / 11,41 | 0,38 / 0,46 | 11,70 / 10,41 | 0,94 / 1,06 |
| Вариант 3 (ассемблер) | 4,00 | 1,31 | 4,213 | 2,61 |
Если применяем переменные любого целочисленного типа:
| Вариант | 32-bit Время (мс) | Прирост скорости | 64-bit Время (мс) | Прирост скорости |
|---|---|---|---|---|
| Windows API | 17,40 | 1 | 16,41 | 1 |
| Вариант 1 (без округления) | 9,49 | 1,83 | 9,09 | 1,80 |
| Вариант 2 (с округлением / MulDivX) | 23,68 / 19,52 | 0,74 / 0,89 | 17,23 / 14,13 | 0,95 / 1,16 |
| Вариант 3 (ассемблер) | 4,04 | 4,31 | 3,94 | 4,16 |

Значения получены, как среднее на 100 циклах по миллиону итераций на каждый вариант. Указана битность тестового приложения. Тесты проводились на Windows 10 Pro x64. На всех платформах побеждает Вариант 3. А «правильный» Вариант 2, как видим, не особо и отстаёт от базы: на 64 битах 0.9-0.95, что вполне неплохо. А улучшенный вариант 2, MulDivX, даже опережает оригинал.
Листинг для копипаста
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
//****************************************************************************** // Project: IP76.RU // Created: 2025-12-16 // Article: https://ip76.ru/muldiv-fast // Описание: Своя кроссплатформенная функция MulDiv для Delphi. //****************************************************************************** unit IP76MulDiv; interface function MulDivSimple(A, B, C: Integer): Integer; inline; function MulDiv(A, B, C: Integer): Integer; inline; function MulDiv32(A, B, C: Integer): Integer; inline; function MulDiv64(A, B, C: Integer): Integer; inline; function MulDivX(A, B, C: Integer): Integer; inline; function MulDivFast(A, B, C: Integer): Integer; implementation function MulDivSimple(A, B, C: Integer): Integer; inline; begin if C = 0 then Result := -1 // Совместимость с Windows API else Result := Int64(A) * Int64(B) div C; end; // Боевая function MulDiv(A, B, C: Integer): Integer; inline; var Temp: Int64; Sign: Integer; begin if C = 0 then Exit(-1); // Излишняя оптимизация: крадёт такты // if (Integer(Cardinal(A) or Cardinal(B) or Cardinal(C)) >= 0) then // Exit((Int64(A) * Int64(B) + C div 2) div C); Temp := Int64(A) * Int64(B); if Temp = 0 then Exit(0) else if Temp < 0 then begin if C < 0 then begin Sign := 1; C := -C; end else Sign := -1; Temp := -Temp; end else begin if C < 0 then begin Sign := -1; C := -C; end else Sign := 1; end; Temp := Temp + (C shr 1); // C div 2; Result := Sign * Temp div C; end; // Хороша при x32 function MulDiv32(A, B, C: Integer): Integer; inline; var Temp: Int64; Negative: Boolean; begin // Проверка деления на ноль if C = 0 then // Поведение как у Windows MulDiv Exit(-1); // Вычисляем произведение в Int64 // для избежания переполнения Temp := Int64(A) * Int64(B); // Определяем знак результата Negative := (Temp < 0) xor (C < 0); // Работаем с абсолютными значениями // для правильного округления if Temp < 0 then Temp := -Temp; if C < 0 then C := -C; // Округление к ближайшему целому: // добавляем половину делителя Temp := Temp + (C shr 1); Result := Temp div C; // Применяем знак if Negative then Result := -Result; end; // Хороша при x64 function MathRound(AValue: Double): Int64; inline; begin if AValue >= 0 then Result := Trunc(AValue + 0.5) else Result := Trunc(AValue - 0.5); end; function MulDiv64(A, B, C: Integer): Integer; inline; begin if C = 0 then Result := -1 else if B = C then Result := A else Result := MathRound(int64(A) * int64(B) / C); end; // Боевая function MulDivX(A, B, C: Integer): Integer; inline; {$IF Defined(CPUX64)} begin if C = 0 then Result := -1 else if B = C then Result := A else Result := MathRound(int64(A) * int64(B) / C); {$ELSE} var Temp: Int64; Negative: Boolean; begin if C = 0 then Exit(-1); Temp := Int64(A) * Int64(B); Negative := (Temp < 0) xor (C < 0); if Temp < 0 then Temp := -Temp; if C < 0 then C := -C; Temp := Temp + (C shr 1); Result := Temp div C; if Negative then Result := -Result; {$ENDIF} end; function MulDivFast(A, B, C: Integer): Integer; {$IF Defined(CPUX86)} asm // EAX = A, EDX = B, ECX = C test ecx, ecx jz @div_zero imul edx // EDX:EAX = A * B (64-bit) idiv ecx // EAX = result, EDX = remainder ret @div_zero: mov eax, -1 end; {$ELSEIF Defined(CPUX64)} asm // ECX = A, EDX = B, R8 = C // Стековй фрейм не нужен, дополнительных вызовов нет .NOFRAME // Ускоряемся test r8d, r8d jz @div_zero mov eax, ecx // A imul edx // EDX:EAX = A * B mov ecx, r8d // C idiv ecx // EAX = result ret @div_zero: mov eax, -1 end; {$ELSE} begin if C = 0 then Result := -1 else Result := Int64(A) * Int64(B) div C; end; {$ENDIF} end. |
Заключение (философское)
Мы часто цепляемся за старые привычки, как за спасательный круг. Winapi.Windows кажется чем-то надёжным, проверенным. Но каждая системная зависимость — это якорь, мешающий плыть в будущее, чёрный ящик, работу которого мы не контролируем.
Написание своей MulDiv — это не просто техническое упражнение. Это шаг к независимости. Шаг к пониманию, что мы контролируем свой код, а не код контролирует нас. Это акт цифрового суверенитета.
В итоге мы получили быстрый кроссплатформенный аналог функции Windows.MulDiv, не обременённый никакими привязанностями и зависимостями. Наша функция MulDiv выполняет ровно то же, что и оригинал. Таким образом, можно использовать старый код без дополнительных правок, надо только убрать Winapi.Windows из предложения uses. А для новых проектов можно сразу использовать MulDivFast, если хотим выигрыш в скорости.
Можно перевести нашу MulDiv на ассемблер. Это будет много строк кода, но работать станет быстрее. Но стоит ли оно того? Получим трудно поддерживаемый код, ненависть коллег и иллюзию оптимизации там, где она не нужна. Мы пишем код для людей, а не для машин.
Все листинги из статьи — готовы к использованию. Копируйте, адаптируйте, улучшайте. Грамотный копипаст — это наше всё. Пишите комментарии.
Скачать
Друзья, спасибо за внимание!
Исходник (zip) 10.1 Кб. Delphi XE 7 (Проверен в XE 13)
Исходник FMX XE 13 (Test4) (zip) 12.8 Кб. Delphi XE 13
Исполняемый файл 32-bit (zip) 820 Kб (Скомпилирован в XE 7 32-bit)
Исполняемый файл 64-bit (zip) 1.03 Мб (Скомпилирован в XE 7 64-bit)