Кейсі Мюраторі, автор Handmade Hero та освітнього блогу Computer, Enhance!, ще в 2022 році опублікував текст під назвою «Turns are Better than Radians» — а влітку 2026-го цей допис несподівано вибрався в топ Hacker News і зібрав понад 100 балів, обійшовши в стрічці свіжі релізи моделей. За даними HN 100+, суперечка розгорілася навколо запитання, яке на перший погляд здається шкільним: якою одиницею вимірювати кут у коді — і чому це взагалі має значення для когось, хто не пише підручник з геометрії.
Уявіть дрон, що годинами утримує курс за допомогою кута повороту, збереженого в радіанах: щокадру до накопиченого значення додається невелика кутова швидкість, і раз у раз результат «нормалізують» — беруть залишок від ділення на 2π, щоб кут не зростав до нескінченності. Проблема в тому, що 2π — число з нескінченним двійковим розкладом, тож жоден float чи double не зберігає його точно. Кожна операція нормалізації лишає крихітну похибку, і за годину польоту чи тиждень безперервної роботи сервера ці крихти складаються у цілком відчутний дрейф курсу.
Це не абстрактна проблема з підручника. У 2026 році дедалі більше продуктів — від AI-агентів, що керують роботизованими маніпуляторами, до генеративних музичних синтезаторів і мультиплеєрних симуляцій із детермінованим lockstep-мережевим кодом — виконують тригонометричні обчислення мільйони разів на секунду, годинами поспіль. Саме в таких умовах крихітна похибка float-арифметики перестає бути теоретичною цікавинкою і починає ламати продукт.
Мюраторі пропонує рішення, яке насправді старше за нього самого: рахувати кут не в радіанах і не в градусах, а обертами (turns) — де повне коло дорівнює рівно 1.0, а не ірраціональному 2π. Ідея не нова — DSP-інженери й автори Doom користувалися нею десятиліттями, — але для покоління розробників, які виросли на готовому math.sin() з коробки, вона звучить як відкриття. AiiN розібрався, що насправді змінюється під капотом, кому це дає реальну перевагу, а кому — лише зайвий рефакторинг.
Звідки взялася ця суперечка про радіани?
Радіан визначається як відношення довжини дуги до радіуса кола — одиниця, зручна математично, бо похідна sin(x) дорівнює cos(x) лише тоді, коли x виміряно саме в радіанах. Через цю властивість радіани стали стандартом у математичному аналізі ще в XIX столітті, а звідти перекочували у бібліотеки libm, у CPU-інструкції на кшталт fsin/fcos, у GLSL і HLSL, у NumPy та PyTorch. Практично будь-яка функція sin() чи cos(), яку викликає розробник у 2026 році, за замовчуванням очікує аргумент саме в радіанах.
Періодично спільнота піднімає суміжну суперечку — «тау проти пі»: у 2010 році вийшов «Tau Manifesto», який стверджував, що природною константою кола має бути τ = 2π, а не π, бо саме 2π описує повне коло. Аргумент настільки прижився, що в Python 3.6 з'явився math.tau. Але це косметична правка: τ так само ірраціональне, як і π, і жодної проблеми з точністю float вона не вирішує. Мюраторі йде радикальніше — він відмовляється від ірраціональної константи як такої, а не просто міняє її множник.
Як оберти прибирають похибку під капотом?
У системі обертів повне коло — це рівно 1.0. Замість fmod(angle, 2π) для нормалізації кута достатньо взяти дробову частину: angle - floor(angle). Оскільки 1.0 — число, яке double чи float зберігає абсолютно точно (на відміну від 2π), ця операція не вносить систематичної похибки на кожному кроці. Дрейф, який роками був «нормальною» ціною роботи з кутами, у такій системі просто зникає з рівняння.
Ще радикальніший варіант — цілочисельне представлення кута, відоме в геймдеві як binary angular measurement (BAM). У класичному Doom (1993) тип angle_t — це 32-бітне ціле без знака, де повне коло дорівнює 2^32. Додавання кутової швидкості — звичайне ціле додавання, а нормалізація не потребує жодної окремої інструкції: переповнення unsigned-цілого саме по собі й є коректним wrap-around обертанням. Той самий трюк десятиліттями живе в цифровому синтезі звуку (Direct Digital Synthesis): фазовий акумулятор осцилятора зберігається як фіксована точка, переповнюється щоцикл — і саме так генерується періодичний сигнал без жодного modulo.
А що з sin() і cos()?
Тут ціна прихована: стандартні бібліотечні sin/cos очікують радіани, тож щоб рахувати в обертах, доводиться писати власну апроксимацію — поліноміальну (minimax-підгонку на чверті кола з використанням симетрії) або таблицю пошуку, проіндексовану старшими бітами BAM-значення. Для GPU-шейдерів компроміс простіший: кут конвертують у радіани одним множенням на межі виклику вбудованого sin(), а не на кожному кроці накопичення — тож ірраціональна константа лишається, але перестає накопичувати похибку.
Чим оберти відрізняються від радіанів, градусів і тау?
Три головні системи виміру кута мають різні компроміси:
- Градуси (360°) — спадок вавилонської шістдесяткової системи, зручні для людини, але довільні: 360 не має природного зв'язку ні з геометрією кола, ні зі степенями двійки, тож апаратне переповнення для них не працює як безкоштовна нормалізація.
- Радіани (2π) — математично природні для аналізу й похідних, стандарт у libm, GLSL/HLSL, NumPy, PyTorch; ціна — ірраціональна константа, яка не має точного двійкового представлення.
- Оберти (1.0 або 2^n) — повне коло дорівнює точному, представному числу; нормалізація або точна (frac), або взагалі безкоштовна (цілочисельне переповнення); ціна — потрібні власні тригонометричні апроксимації і рефакторинг межі з зовнішніми бібліотеками.
Важливе застереження: більшість сучасних ігрових рушіїв обертами НЕ користуються нативно. Unity показує кути в градусах у редакторі, але Mathf.Sin() під капотом однаково очікує радіани; Unreal Engine зберігає обертання в FRotator (градуси) або кватерніонах, знову ж конвертуючи в радіани для тригонометрії; Godot працює з радіанами напряму. Оберти сьогодні — це переважно практика нижчого рівня: embedded-прошивки, DSP-чипи, ретро-рушії на кшталт idTech 1, і саморобні Angle-типи, які пишуть інженери продуктивності на кшталт Мюраторі. Тобто HN-дискусія — це не опис усталеної практики, а заклик її змінити.
Що це дає тим, хто зараз будує ігри, звук чи embedded-системи?
Детерміноване мережеве lockstep-моделювання. У змагальних іграх на кшталт RTS чи файтингів усі клієнти мають порахувати однаковий результат симуляції з однакових вхідних даних — інакше настає desync. Класична причина desync — різні реалізації libm на різних CPU чи компіляторах дають на біт відмінний результат fmod() чи sin() у радіанах. Фіксована ціла арифметика в обертах прибирає цю залежність від конкретної бібліотеки: додавання і переповнення цілих чисел дають однаковий результат на будь-якій платформі, що історично й змушувало розробників RTS 1990–2000-х переходити на фіксовану точку саме для кутів і позицій.
Аудіосинтез і генеративна музика. Фазовий акумулятор осцилятора — це, по суті, кут в обертах: значення монотонно зростає й переповнюється рівно раз за період сигналу. AI-інструменти для генерації музики та софтверні синтезатори, що будують сигнал семпл за семплом годинами безперервної роботи, отримують від цього стабільну фазу без клацань чи дрейфу частоти, які виникали б при накопиченні похибки в радіанах.
Робототехніка та мотор-контролери. Інкрементальний енкодер на валу двигуна видає цілі імпульси на оберт — скажімо, 4096 на повний оберт. Це представлення вже, по суті, є фіксованою точкою в обертах: перетворення в радіани для PID-регулятора — зайвий крок, який на мікроконтролері без FPU (наприклад, Cortex-M0) ще й коштує реальних тактів. Рахуючи напряму в одиницях енкодера, прошивка економить і точність, і продуктивність одночасно.
Що може піти не так, якщо взяти оберти за замовчуванням?
Проблема нікуди не зникає — вона переїжджає на межу. Практично кожна зовнішня бібліотека, від Python-івського math до GLSL-шейдерів, усе одно очікує радіани. Якщо конвертація відбувається не раз на кадр, а всередині гарячого циклу, який щоразу викликає зовнішню тригонометрію, виграш від обертів з'їдається тим самим множенням на ірраціональну константу, від якої нібито тікали.
Змішування одиниць — окремий клас багів, і історія знає дорогі приклади: у 1999 році NASA втратила міжпланетний зонд Mars Climate Orbiter вартістю 327,6 млн доларів через те, що одна команда рахувала тягу у фунт-силах, а інша очікувала ньютони. Кут — не сила, але логіка та сама: якщо частина кодової бази очікує радіани, частина — градуси, а хтось додав ще й Angle-тип в обертах, один тихий баг на неправильній конвертації обійдеться дорожче, ніж уся економія на точності.
Похідні й оптимізація. Рівність d/dx sin(x) = cos(x) працює лише тоді, коли x — радіани; власна тригонометрія на обертах вимагає протягувати додатковий множник 2π крізь кожну похідну. Для фізичних рушіїв чи задач із градієнтним спуском — наприклад, підбір параметрів осцилятора нейромережею — це джерело тонких, важко вловимих помилок, тому в ML-коді радіани залишаться стандартом ще довго.
Чи варто переписувати наявний код під оберти?
Здебільшого — ні. Аргумент Мюраторі має вагу там, де кут накопичується мільйони разів за сесію: гарячі цикли фізики, DSP, embedded-контролери реального часу. Звичайний застосунок, що раз на клік повертає SVG-іконку через CSS-transform, не має проблеми, яку оберти вирішують, — і рефакторинг тут суто вартість без вигоди.
Де оберти вже реально використовують, а не просто обговорюють?
У прошивках DSP-чипів і аудіосинтезаторів, у ретро- та демосценних рушіях, що успадкували BAM-підхід від Doom-ери, та в бібліотеках на кшталт ARM CMSIS-DSP, де фіксована точка формату Q31 для тригонометрії — штатний режим роботи на мікроконтролерах без FPU. У мейнстрімних ігрових рушіях (Unity, Unreal, Godot) — поки що ні.
Висновок AiiN
Оберти — це не нова математика, а зміна одиниці виміру з практичними, вимірними наслідками для конкретного класу задач: там, де кут накопичується мільйони разів за сесію без зовнішнього втручання. AiiN вважає, що це нішевий, але справжній технічний аргумент, а не ретро-ностальгія за Doom: у міру того як embedded-AI, генеративний звук і детерміновані мультиплеєрні симуляції стають масовішими продуктовими категоріями 2026 року, кількість кодових баз, де ця похибка справді видима, тільки зростає.
Прогноз редакції на 6–12 місяців: мейнстрімні рушії (Unity, Unreal, Godot) не перепишуть тригонометрію під оберти — ціна міграції надто висока, а виграш відчутний не для більшості їхніх користувачів. Натомість варто очікувати появи окремих Angle- чи Turns-типів у нішевих Rust- та Zig-бібліотеках для геймдеву й DSP, кількох доповідей на конференціях з performance-aware programming, що прямо посилатимуться на оригінальний допис Мюраторі, і — можливо — перших випадків, коли AI-асистенти для кодогенерації почнуть самі пропонувати оберти в embedded- та audio-контексті просто тому, що ця дискусія тепер щільно осіла в їхніх тренувальних даних.
Кут — це просто число з одиницею виміру. Питання лише в тому, яка одиниця найдешевше конвертується в те, що насправді робить ваш CPU.