<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ivan Grishaev's blog</title>
    <description>Writing on programming, education, books and negotiations.
</description>
    <link>https://grishaev.me/</link>
    <atom:link href="https://grishaev.me/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 06 May 2026 06:51:47 +0000</pubDate>
    <lastBuildDate>Wed, 06 May 2026 06:51:47 +0000</lastBuildDate>
    <generator>Jekyll v4.2.0</generator>
    
      <item>
        <title>Числа №16. Знаковость</title>
        <description>&lt;p&gt;Почему в Джаве байт имеет знаковый тип? Скажем, если прочитать из файла байт
249, то при печати увидим -7. Откуда минус?&lt;/p&gt;

&lt;p&gt;Дело в том, что в Джаве тип byte и его старший брат Byte – знаковые.&lt;/p&gt;

&lt;p&gt;Знаковые числа – те, что тратят первый бит на признак того, есть ли впереди
минус. Например, число 3 в битовом виде записано так:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;00000011
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;А с минусом – так:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;10000011
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Первый бит означает, что впереди минус. Если прочитать этот набор битов как
беззнаковое число, то первый бит дает 128 (два в степени 8). Итоговое число
становится 128 + 2 + 1 = 131.&lt;/p&gt;

&lt;p&gt;Это как раз случай Джавы. Она смотрит на биты &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10000011&lt;/code&gt; как на знаковое число,
поэтому первый бит нарисует минус, а оставшиеся &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0000011&lt;/code&gt; – тройку.&lt;/p&gt;

&lt;p&gt;В интернете есть цитата Гослинга:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;For me as a language designer, which I don’t really count myself as these
days, what “simple” really ended up meaning was could I expect J. Random
Developer to hold the spec in his head. That definition says that, for
instance, Java isn’t – and in fact a lot of these languages end up with a lot
of corner cases, things that nobody really understands. Quiz any C developer
about unsigned, and pretty soon you discover that almost no C developers
actually understand what goes on with unsigned, what unsigned arithmetic
is. Things like that made C complex. The language part of Java is, I think,
pretty simple. The libraries you have to look up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Кому лень переводить: причина в том, чтобы сделать проще. В языках С и С++ есть
знаковые и беззнаковые типы, и когда они выступают вместе, никто не знает, что
произойдет. Да, в стандартах Си описаны правила, но легко ошибиться. Поэтому
проще всего исключить беззнаковые числа.&lt;/p&gt;

&lt;p&gt;Мое мнение таково: да, беззнаковые short, int, long лучше исключить. Пусть будут
только знаковые. А вот с байтом вышла ошибка.&lt;/p&gt;

&lt;p&gt;Беда в том, что в Джаве байт считается числом: тип Byte унаследован от Number. Я
не согласен. Байт – это не число. Он никогда не интересует нас в качестве
числа. Когда в последний раз вы прибавляли число к байту? Или умножали байт на
байт? Вычитали их?&lt;/p&gt;

&lt;p&gt;Байт – это группа битов, элемент алфавита из 256 символов. Да, ему можно
сопоставить число для удобства, но не более того. Байт – это сырые данные, на
которые можно смотреть по-разному в зависимости от контекста. Это может быть
знаковое число или беззнаковое; это может быть несколько битовых групп,
например, первые три – тип, а остальные пять – значение.&lt;/p&gt;

&lt;p&gt;В байте может умещаться крошечное число с плавающей запятой! Бит на знак, три на
экспоненту и четыре на мантису:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 101 1010
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Такие числа называются tiny float 8 и поддерживаются видеокартами.&lt;/p&gt;

&lt;p&gt;Факт, что отрицательные байты никем не используются, очевиден. Скажем, IP-адрес
– это четыре байта через точку, но никто не записывает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;192.168.0.1&lt;/code&gt; как
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-64.-88.0.1&lt;/code&gt;. Ни один hex-редактор не показывает отрицательные байты, не важно
в hex- или decimal режимах.&lt;/p&gt;

&lt;p&gt;Тот же Турбо Паскаль разделял байт и знаковое 8-битное число: типы Byte и
ShortInt. Первый фигурирует, когда читаешь сеть или файл. Второе – это
преобразование группы бит к конкретному типу.&lt;/p&gt;

&lt;p&gt;На примере Джавы можно вспомнить цитату Эйнштейна: вещи должны быть простыми, но
не проще этого. Идея избавиться от беззнаковых типов в целом хороша, но повлияла
на работу с байтами. Байты не должны трактоваться как числа, а если это
допустимо, то знака не должно быть.  Мы ведь не считаем буквы от -16? Так что с
байтами упрощение пошло во вред.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Это была последняя заметка о числах – других заметок на эту тему в черновиках у
меня нет. Надеюсь, вам понравилось, и кто-то что-то для себя извлек. Напомню,
что все заметки про числа легко найти по тегу &lt;a href=&quot;/tag/numbers/&quot;&gt;#numbers&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/numbers-16/</link>
        <guid isPermaLink="true">https://grishaev.me/numbers-16/</guid>
        
        <category>numbers</category>
        
        
      </item>
    
      <item>
        <title>Числа №15. Об играх</title>
        <description>&lt;p&gt;Пока бы закончить с числами; осталось две заметки.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/numbers-14/&quot;&gt;В прошлый раз&lt;/a&gt; я рассказывал о числах с фиксированной запятой и о
том, как они использовались в играх. В том числе упомянул первый Старкрафт
(BroodWar), о котором и хочу поговорить.&lt;/p&gt;

&lt;p&gt;Старкрафт – это стратегия реального времени фирмы Blizzard; вышла в 1998
году. Она стала прорывом во многих областях и надолго задала планку. Старкрафт
отличался запредельным числом игровых механик и их комбинаций. Расы были
кардинально разными: зерги делали ставку на массу, протоссы – на мощь, терраны –
на технологии. Юниты были наземные и летающие, транспортные, невидимые, с
обычным уроном и массовым. Были псионики – юниты, которые атаковали только
магией.&lt;/p&gt;

&lt;p&gt;Игра, хотя и была двумерной, поддерживала многоуровневый ландшафт, и это влияло
на исход боя: юниты ниже мазали по тем, что выше.&lt;/p&gt;

&lt;p&gt;Не все тонкости игры были очевидны. Скажем, юниты делились на классы (легкий,
тяжелый и другие), атака тоже была разных типов. Позже игроки составили таблицы,
кто по кому наносит какой урон.&lt;/p&gt;

&lt;p&gt;Добавьте к этому отличную кампанию, полную пафоса; крутые CGI-ролики; сетевую
игру в компьютерном классе; Battle.net для счастливчиков с интернетом в 98 году
– и станет ясно, что игра была прорывом. До сих пор она остается одной из
главных киберспортивных дисциплин, а в Южной Корее – национальным видом спорта.&lt;/p&gt;

&lt;p&gt;Чтобы обеспечить все это добро, движок считал много чисел. В 98 году не все
процессоры поддерживали плавающие числа, поэтому в Старкрафте использовались
числа с фиксированной запятой. Код игры никогда не открывали, однако ее
устройство ясно из публикаций разработчиков и специального SDK для
ботов. Последнее особенно интересно: Старкрафт был первой игрой, под которую
писали ИИ-ботов. Были даже шоу-матчи между известными игроками и ботами по
аналогии с шашками Go и AlphaStar. Ежегодно проходят турниры для разрабочиков
ботов.&lt;/p&gt;

&lt;p&gt;Интересен следующий факт: числа, что мы видим в игре, на самом деле являются
дробными. Скажем, у морпеха (марика) 40 единиц здоровья, атака 6, броня 0. У
зилота (zealot, воин протоссов) здоровье 100 единиц, защитное поле на 60
пунктов, атака 16. Все числа выглядят как целые, удобно складывать и вычитать
их.&lt;/p&gt;

&lt;p&gt;На уровне кода каждое число – это контейнер с фиксированной запятой. Тип,
который его описывает, называется &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fp8&lt;/code&gt;, что означает fixed point 8. Восьмерка
подразумевает число битов, отведенных под дробную часть. Таким образом, fp8
хранит числа с точностью до &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1/256&lt;/code&gt; или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.00390625&lt;/code&gt; в десятичной системе.&lt;/p&gt;

&lt;p&gt;Я не видел полного описания этого типа. Однако логично предположить: если он
занимал 16 бит, то на целую часть оставалось 8 бит. Таким образом целая часть
вмещала диапазон от -128 до 127. Это мало. Поэтому скорее всего тип занимал 32
бита (обычный int), и на целую часть отводилось 24 бита вместе со
знаком. Диапазон таком образом был от &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-(2^23)&lt;/code&gt; до &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(2^23)-1&lt;/code&gt; или от
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-8,388,608&lt;/code&gt; до &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8,388,607&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Восемь миллионов – достаточно, чтобы покрыть все случаи игры, включая
особенные. Например, юнитов-боссов в кампании с колоссальным запасом здоровья.&lt;/p&gt;

&lt;p&gt;И все-таки: зачем игре дробные числа? Мы ведь выяснили, что у зилота 160
здоровья, а у марика 6. Это значит, потребуется 27 атак, чтобы марик победил
зилота. Все удобно, но нет – за этим удобством кроется фиксированная запятая.&lt;/p&gt;

&lt;p&gt;Причина в следующем: если все числа целые, то в какой-то момент они портят
игровой баланс. Такая сложная игра как Старкрафт обязана учитывать фракции,
чтобы не вышла досадная вещь, которую мы рассмотрим ниже.&lt;/p&gt;

&lt;p&gt;Про морпеха вы уже знаете; а у зергов есть юнит под названием ультралиск. Это
слонопотам, который хорош против морпехов. Изначально у него 1 единица
брони. Всем юнитам зергов можно сделать три апгрейда на броню +1. Наконец,
ультралиску доступен его личный апгрейд +2 к броне. В сумме они дают 6 брони.&lt;/p&gt;

&lt;p&gt;Единица брони снижает урон на единицу. А теперь смотрите: ультралиск со всеми
апгрейдами имеет 6 брони, а морпех без апгрейдов на атаку наносит 6 урона. 6 - 6
= 0. Если задача решается в целых числах, то морпех наносит нулевой урон, и
ультралиск полностью для него неуязвим!&lt;/p&gt;

&lt;p&gt;Проблему можно решить тем, чтобы обязать игрока, который играет за терранов,
сделать апгрейд +1 для мариков. Тогда морпех будет наносить единичку урона: 6 -
7 = -1. Но это слабое решение. Общая беда в том, что если у юнита высокая броня,
он неуязвим для всех юнитов с атакой ниже брони.&lt;/p&gt;

&lt;p&gt;Возможна абсурдная ситуация: у юнита слабая атака, но высокая броня, скажем
атака 10 и броня 20. Представим теперь, что игроки выбрали одинакову расу. Оба
настроили этих юнитов и пошли воевать. В результате ни одна армия не победит
другую: каждая атака на 10 единиц нивелируется броней в 20 единиц. Такой себе
геймдизайн.&lt;/p&gt;

&lt;p&gt;На самом деле броня в 6 единиц – это не показатель -6 ко вражеской атаке. Это
коэффициент ее снижения. В Старкрафте и других играх Blizzard броня устроена
логарифмически. Вспомним как устроен логарифмический рост: на малых значениях он
почти линейный. Значения брони 1 или 2 дают снижение урона на 1 и 2
соответственно. Однако далее рост замедляется: показатель 3 снижает урон не на
3, а на 2.9, 4 – на 3.5 и так далее.&lt;/p&gt;

&lt;p&gt;Числа я беру из головы, потому что не готов запускать Старкрафт и все это
проверять. Однако такая же механика справедлива для Warcraft, Diablo и других
игр Blizzard. Логарифмическая броня – это залог того, юнит никогда не станет
неуязвим. Даже если выдать юниту 100 брони, урон по нему будет снижен до
условных 0.01 здоровья. Да, это мало, и здоровье будет тикать по единичке в
минуту. Но урон все-таки будет, и сотня-другая обычных юнитов смогут победить
тяжеловеса.&lt;/p&gt;

&lt;p&gt;Как следствие, даже ультралиска с броней +6 можно победить морпехами без
апгрейдов. Каждый марик будет наносить не 0, а 0.25 урона (условно). Разумеется,
игроку за терранов в такой ситуации ничего не светит, но дизайн игры в этом не
виноват. Не получится создать юнита, который полностью неуязвим, и это
правильно.&lt;/p&gt;

&lt;p&gt;Должно быть, вы замечали в других играх: долбишь врага, а здоровье убывает на
единичку каждый сотый удар. На самом деле его здоровье — дробное число и урон
наносится тоже дробный. В интерфейсе все показатели выглядят целыми, чтобы не
смущать игрока длинными хвостами. Кому понравится &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.513523734353450003&lt;/code&gt; урона?&lt;/p&gt;

&lt;p&gt;Поскольку логарифмы ведут к дробным числам, вот откуда нужда в числах с
фиксированной запятой.&lt;/p&gt;

&lt;p&gt;По той же причине в раннем Старкрафте был баг: “мертвые” юниты с нулевым
здоровьем. Такой юнит мог воевать и погибал только от следующего удара. Как вы
поняли, он не был мертвым: целая часть здоровья была нулевой, но оставались
единичные биты в дробной части. Это случалось из-за показателей брони,
сплеш-урона с затуханием и многих других факторов. Старкрафт не округлял дробные
числа: чтобы показать целую часть, он просто сдвигал fp8 на восемь битов
вправо. Результат был 0, но юнит считался живым. Позже это исправили.&lt;/p&gt;

&lt;p&gt;Да, многое я бы мог рассказать про Старкрафт – сколько времени в него прощелкал!
В качестве бонуса предлагаю &lt;a href=&quot;https://makingcomputerdothings.com/brood-war-api-the-comprehensive-guide-index-for-the-posts/&quot;&gt;цикл статей&lt;/a&gt; разработчика, который пишет
ботов и понимает устройство игры как бог.&lt;/p&gt;
</description>
        <pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/numbers-15/</link>
        <guid isPermaLink="true">https://grishaev.me/numbers-15/</guid>
        
        <category>numbers</category>
        
        
      </item>
    
      <item>
        <title>Текущий case</title>
        <description>&lt;p&gt;Один из худших атавизмов в программировании – это протекающий оператор
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch/case&lt;/code&gt;. Он ведет родословную со времен Си и в настоящее время есть во
всех императивных языках.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;switch (expression) {
    case constant1:
        // Code to execute if expression == constant1
        break;
    case constant2:
        // Code to execute if expression == constant2
        break;
    default:
        // Code to execute if no case matches
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В чем проблема? В том, что без оператора &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;break&lt;/code&gt; управление передается в
ниже. Если expression подходит под разные условия, сработают две ветки одна за
другой. Счастливой отладки.&lt;/p&gt;

&lt;p&gt;Сколько людей погорело на протечках в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch/case&lt;/code&gt; – я даже боюсь предположить.&lt;/p&gt;

&lt;p&gt;В Джаве &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch/case&lt;/code&gt; устроен аналогично, но к счастью там это поправили. Есть
другой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch/case&lt;/code&gt; со стрелками, который гарантирует, что будет выполнена
только одна ветка. Вдобавок он возвращает значение:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;A&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Processing A&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;B&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Processing B&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В последних Джавах много внимания уделяют паттерн-матчингу. Можно сказать, что
любой switch лучше свести к паттерн-матчингу, потому что иначе получается минное
поле из-за протечек.&lt;/p&gt;

&lt;p&gt;В Кложе, например, макрос &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; гарантирует, что сработает только одна
ветка. При этом выражения должны быть литералами, потому что компилятор
вычисляет от них хеши для таблицы переходов:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(case (:status item)

  &quot;active&quot;
  (process-active item)

  &quot;pending&quot;
  (process-pending item)

  ;; default
  (throw-error &quot;wrong status&quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Есть макрос &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cond&lt;/code&gt; для условий, которые вычисляются в рантайме. Будет вычеслена
первая ветка, для которой условие вернет истину:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(cond

  (-&amp;gt; item :status (= &quot;active&quot;))
  (process-active item)

  (or (-&amp;gt; item :status (= &quot;pending&quot;))
      (queue-is-blocked ...)
  (process-pending item)

  :else
  (default-case &quot;...&quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Наконец, есть скользящий, “протекающий” &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cond-&amp;gt;&lt;/code&gt; со стрелкой. Он принимает пары
(условие-&amp;gt;выражение) и пропускает сквозь них исходное выражение.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(cond-&amp;gt; []

  :always
  (conj (get-some-item ..))

  (item-is-active? item)
  (conj the-item)

  :finally
  (into (get-additional-items)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Пишу я это потому, что погорел со &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch/case&lt;/code&gt; в Питоне. Хотя с версии 3.10 в
него завезли оператор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;match&lt;/code&gt;, многие до сих пор пишут каскады
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if...elif...elif...elif...else&lt;/code&gt;. И вот я добавил новую ветку в середину, а
вместо &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;elif&lt;/code&gt; написал &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;foobar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;do_this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;kek&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;do_that&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;some_action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;do_lol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ветка выполняется, но первый оператор &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; обрывается и начинается второй. Для
него условие не находится и срабатывает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;else&lt;/code&gt;, где бросается
исключение. Получилось так, что действие выполняется, но из-за того, что было
исключение, задача запускается еще раз. В итоге мой код вызвал действие три
раза. Линтер ничего не заметил.&lt;/p&gt;

&lt;p&gt;На мой взгляд, старого оператора &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch/case&lt;/code&gt; в коде быть не должно – только
паттерн-матчинг, который гарантирует, что сработает только одна ветка. А второе
– не знаю, как можно жить с языком без макросов. Это словно красть у себя
самого.&lt;/p&gt;
</description>
        <pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/leaky-case/</link>
        <guid isPermaLink="true">https://grishaev.me/leaky-case/</guid>
        
        <category>programming</category>
        
        
      </item>
    
      <item>
        <title>Закончили Гарри Поттера</title>
        <description>
&lt;p&gt;Вчера закончили с дочкой Гарри Поттера – все семь книг. Это заняло год и пять
месяцев. Я вычислил срок с помощью блога: оказалось, в январе 2025 года я
публиковал &lt;a href=&quot;/harry-potter-money/&quot;&gt;маленькую заметку&lt;/a&gt; о денежной системе в мире ГП.&lt;/p&gt;

&lt;p&gt;О каждой книге можно сказать разное: где-то лучше, где-то хуже. Кто-что
затянуто; какие-то сцены никуда не ведут; от некоторых персонажей можно было
отказаться. Однако в целом ГП – замечательный, монументальный труд. Это новая
вселенная по образу Звездный Войн, и по ней еще долго будут делать
приквелы-сиквелы, фанатский контент и прочее.&lt;/p&gt;

&lt;p&gt;Гарри Поттер – это современная детская литература. Ключевое слово “современная”,
то есть описывает персонажа нашего времени. Несмотря на все нелепости вселенной
ГП, ключевые темы вроде отношений, любви, долга, преданности совпадают с
ориентирами нормального современного человека. При этом книга взрослеет вместе с
читателем.&lt;/p&gt;

&lt;p&gt;Я жалею лишь об одном: мы читали ГП в неудачном переводе. Нам подарили первые
несколько книг, и на тот момент мне были безразличны все эти Снейпы-Злеи. Когда
я разобрался, то уже был полный комплект, и покупать новый не хотелось. Считаю,
что переводчиков Махаона, которые исковеркали все имена, нужно отпинать и
сбросить в канализацию. Может, так они поймут, что нельзя портить чужой труд.&lt;/p&gt;

&lt;p&gt;Прикупил пару книжек о ГП на английском. Развивает: много прилагательных,
разговорных оборотов, художественные вещи, с которыми не имеешь дела на
созвонах.&lt;/p&gt;

&lt;p&gt;ГП — важная веха современности. Читайте детям Гарри Поттера. И детям хорошо, и
вам понравится.&lt;/p&gt;
</description>
        <pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/harry-potter-finished/</link>
        <guid isPermaLink="true">https://grishaev.me/harry-potter-finished/</guid>
        
        <category>life</category>
        
        <category>books</category>
        
        <category>harry-potter</category>
        
        
      </item>
    
      <item>
        <title>Keeping things apart</title>
        <description>&lt;p&gt;Один кудрявый разработчик сказал: design is about keeping things apart. Дизайн –
это удежание вещей по отдельности. Полная цитата звучала так:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;design is fundamentally about taking things apart (decomplecting) so they can
be managed, understood, and put back together (composed) in a flexible
wayСмысл в том, что пока элементы более-менее свободны, свободен и
дизайн. Следовательно, свободны и мы в принятии решений.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Сегодня я осознал, насколько это важно.&lt;/p&gt;

&lt;p&gt;Где-то полгода назад я запрограммировал сложный процесс. Много шагов и
состояния, работает почти час, поднимает тысячу лямбд. Ширина картинки с
диаграммой – 10 тысяч пикселей. Я этой сложностью не горжусь: была бы моя воля,
я бы сделал проще и вообще по-другому. Но меня заставили сделать так.&lt;/p&gt;

&lt;p&gt;Этот процесс производит много данных, которые нужны всем. Со временем я заметил,
что разработчики “подсасываются” к процессу: добавляют новые шаги и побочные
эффекты. Я говорю: ребята, давайте вы запустите свой процесс, который считает
ваши штучки параллельно моему, а не во время. Я не хочу, чтобы мой процесс падал
из-за ваших вычислений, да и вообще – растет сложность. Давайте по отдельности.&lt;/p&gt;

&lt;p&gt;А мне говорят: уж если есть убер-комбайн, давай педалить его. Я свое мнение
защитить не смог и проиграл. В итоге на пайплайн навесили кучу других операций.&lt;/p&gt;

&lt;p&gt;И вот вчера случилась классика. Мне понадобилось вызвать процесс еще раз, но с
особыми параметрами. Ну знаете, такой же, но с перламутровыми пуговицами
(с). Новое бизнес-требование. Я запустил, и все эти допики, которые навесили
другие разработчики, посыпались. Где-то создались дубли, где-то не те поля и так
далее. Пришлось чистить и откатывать.&lt;/p&gt;

&lt;p&gt;И вот теперь я должен пройтись по всей цепочке шагов и добавить if-else. Если
режим запуска такой-то, то не вызывать эту примочку, не обращаться в этот
сервис, не выполнять этот запрос и так далее. Фактически – вынести все обвесы в
условие.&lt;/p&gt;

&lt;p&gt;В итоге у нас things нифига не apart, и design тоже не задался. А если бы
вынесли в отдельные пайплайны вместо того, чтобы бесконтрольно усложнять
исходный, дело бы обошлось.&lt;/p&gt;

&lt;p&gt;У докладов Рича Хикки двойное дно. Ты их смотришь и не понимаешь: кажется, что
дед несет пургу. А через много лет понимаешь, но уже через боль. Жаль, что
понимание приходит именно так.&lt;/p&gt;
</description>
        <pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/desing-apart/</link>
        <guid isPermaLink="true">https://grishaev.me/desing-apart/</guid>
        
        <category>programming</category>
        
        
      </item>
    
      <item>
        <title>Числа №14. Фиксированная запятая</title>
        <description>&lt;p&gt;Мы много говорили о числах с плавающей запятой. Есть и другой формат – дробные
числа с фиксированной запятой. Давайте кратко их рассмотрим.&lt;/p&gt;

&lt;p&gt;Об этих числах я упоминал &lt;a href=&quot;/numbers-12/&quot;&gt;в заметке&lt;/a&gt; о первой Playstation. Это
эмуляция дробных чисел для архитектур без математического сопроцессора. Главное
их преимущество в том, что все операции с ними выполняются как целочисленные.&lt;/p&gt;

&lt;p&gt;У фиксированных чисел, как ясно из определения, дробный разделитель не плавает
туда-сюда. Например, мы решили, что после запятой хранится два разряда, и так
будет всегда. Не нужно нормализовать числа и приводить их к общим экспонентам.&lt;/p&gt;

&lt;p&gt;Число с фиксированной запятой хранится как целое, умноженное на некий
коэффициент.  Для простоты рассмотрим десятичные числа. Будем хранить цены в
рублях с двумя десятичными знаками (копейками):&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Кофе     80.99
Булочка 115.50
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Коэффициент равен &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10^-2&lt;/code&gt; (или 0.01), а их целые представления равны:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Кофе     8099
Булочка 11550
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Складываются и вычитаются такие числа в одну машинную команду. Найдем сумму покупки:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  1
 8099
11550
-----
19649
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Приведем к дробному виду, чтобы были видны копейки:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;19649 * 10^-2 = 196.49
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Это было сложение, а теперь рассмотрим умножение. Умножать булочку на кофе
немного странно, но давайте не думать об этом.&lt;/p&gt;

&lt;p&gt;Числа 80.99 и 115.50 можно записать таким образом:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; 80.99 =  8099 * 10^-2
115.50 = 11550 * 10^-2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Их произведение:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;8099 * 10^-2 * 11550 * 10^-2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Две степени десяти схлопываются в одну: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-2 + -2 = -4&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;8099 * 11550 * 10^-4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Умножение на степень десяти равносильно тому, чтобы сдвинуть разряд влево или
вправо. Произведение &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8099 * 11550&lt;/code&gt; дает &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;93543450&lt;/code&gt;, и нужно сдвинуть
разделитель влево на 4 позиции. Результат:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;9354.3450
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Однако мы храним только две цифры после запятой. Поэтому хвост &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3450&lt;/code&gt;
округляется до двух знаков. Цифра 5 округляется вверх, и на конце оказывается
35:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;9354.35
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Целое (внутреннее) представление становится таким образом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;935435&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Вот как устроены числа с фиксированной запятой.&lt;/p&gt;

&lt;p&gt;Выше мы рассматрели их в десятичном виде, потому что так удобней. В двоичном
виде происходит то же самое. Договариваются, что первые 16 бит хранят целую
часть, а остальные 16 – дробную. Сокращенно записывают так: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q16.16&lt;/code&gt;. Могут быть
и другие варианты: больше бит на целую часть (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q24.8&lt;/code&gt;) или наоборот – на дробную
(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q8.24&lt;/code&gt;). Вместо десятичного сдвига происходит битовый силами процессора.&lt;/p&gt;

&lt;p&gt;С такими числами работали игровые консоли вроде Playstation, Sega Saturn и
GameBoy. Очевидный их минус в том, что, во-первых, диапазон значений гораздо
ниже, чем у float. Во-вторых, нужно следить за переполнением целой части. Чтобы
не допустить переполнения, числа приводят к аналогам, где у целой части больше
бит. Например, конвертируют Q16.16 в Q24.8, выполняют расчеты, а затем
возвращаются к Q16.16.&lt;/p&gt;

&lt;p&gt;Раньше числа с фиксированной запятой использовались часто. Например, в Doom для
расчета углов; в LaTeX для растеризации шрифтов; в ранних играх Blizzard
(Starcraft).&lt;/p&gt;

&lt;p&gt;В следующей заметке я хочу рассказать о Старкрафте: какую роль в нем играли
числа с фиксированной запятой и почему разработчики не использовали целые.&lt;/p&gt;
</description>
        <pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/numbers-14/</link>
        <guid isPermaLink="true">https://grishaev.me/numbers-14/</guid>
        
        <category>numbers</category>
        
        
      </item>
    
      <item>
        <title>Числа №13. Ошибка Pentium FDIV</title>
        <description>&lt;p&gt;Еще одна кулстори на тему чисел с плавающей запятой.&lt;/p&gt;

&lt;p&gt;Мы привыкли, что железо не ошибается, но иногда бывает обратное. В 1994 году
Intel выпустила первые процессоры линейки Pentium. Процессоры эти печально
прославились тем, что неправильно считали некоторые флоаты (инструкция
FDIV). Вот это правильный результат:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;4195835,0/3145727,0 = 1,333820449136241002
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;А вот что выдавал бракованный процессор:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;4195835,0/3145727,0 = 1,333739068902037589
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Казалось бы, ошибка начинается с четвертой цифры после запятой, пустяки. Но вот
что случиться, если поделить и умножить на одно и то же число:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(4195835/3145727)*3145727 = 4195835
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;против&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(4195835/3145727)*3145727 = 4195579
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Разница 256 – прилично.&lt;/p&gt;

&lt;p&gt;Ошибку обнаружил ученый Томас Найсли. Он написал код, проверяющий процессор на
корректность, уведомил Intel об ошибке и вообще сделал многое для того, чтобы о
проблеме узнали.&lt;/p&gt;

&lt;p&gt;Реакция Intel была потрясающей. Во-первых, в фирме знали о неверных операциях с
числами, но считали, что проблема коснется только узкого круга лиц. Во-вторых,
компания предложила замену процессора только тем, кто докажет, что испытывает
проблемы. Мол, если ты хомяк, купивший процессор ради Doom, тебе и так сойдет.&lt;/p&gt;

&lt;p&gt;К счастью, реакция общества, конкурентов и даже партнеров Intel была крайне
негативной. Уже тогда процессорам доверяли лифты, оборудование, самолеты. Было
ясно, что процессор, который неверно считает – это тикающая бомба. В результате
Intel все-таки заменила процессоры, и общий убыток составил 475 миллионов
долларов. Даже сегодня это много, а в 1994 году сумма была космической.&lt;/p&gt;

&lt;p&gt;Проблема Пентиума коснулась и софта. В компиляторы Delphi, Basic и других языков
добавили костыль: если текущий процессор равен “Pentium такой-то”, то операции с
числами эмулируются программно. Это и замедление вычислений, и лишний код в
компиляторе.&lt;/p&gt;

&lt;p&gt;Больше Intel не позволяла себе таких фокусов. Наработки Томаса Найсли включили в
процесс верификации процессоров, чтобы не допустить ошибок в будущем.&lt;/p&gt;

&lt;p&gt;Почитать об ошибке Pentium FDIV можно &lt;a href=&quot;https://en.wikipedia.org/wiki/Pentium_FDIV_bug&quot;&gt;в Википедии&lt;/a&gt;; есть краткая версия
статьи на русском. На Ютубе по словам Pentium FDIV ищутся подробные разборы
ошибки.&lt;/p&gt;
</description>
        <pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/numbers-13/</link>
        <guid isPermaLink="true">https://grishaev.me/numbers-13/</guid>
        
        <category>numbers</category>
        
        
      </item>
    
      <item>
        <title>Числа №12. Числа в приставках</title>
        <description>&lt;p&gt;Думаю, с числами мы разобрали самые сложные моменты. Приведение к двоичной
дроби, нормализация, битовый формат, сложение – все это мы прошли. Тему пора
заканчивать, и чтобы плавно из нее выйти, расскажу несколько шуток-прибауток.&lt;/p&gt;

&lt;p&gt;Первая из них в следующем. Мы привыкли, что числа с плавающей запятой есть в
каждом утюге. Так было не всегда: ранние процессоры считали только целые
числа. Дробные, если они были нужны, писали вручную. Использовали комбинации
шагов, что мы рассмотрели. Конечно, это был бардак: представьте, что числа
складываются по-разному в разрезе даже не компиляторов, а их версий. Именно из
этого бардака вышел стандарт IEEE 754 Floating Point Arithmetics.&lt;/p&gt;

&lt;p&gt;Позже в компьютерах появился математический сопроцессор. Среди прочего он считал
не только флоаты, но и тригонометрию, степени и логарифмы. Сегодня такой
сопроцессор является частью любого процессора, но раньше был отдельным
устройством.&lt;/p&gt;

&lt;p&gt;Какое-то время математический сопроцессор был дорогим удовольствием. Его не было
в игровых приставках. Поскольку они ориентированы на массового потребителя, то
устроены максимально дешево. Игры под них писали без использования флотов.&lt;/p&gt;

&lt;p&gt;Скорее всего, вы подумали о приставках типа Денди и Сеги. Все верно, но
мат-сопроцессора не было и в приставках более высокого класса, например
Playstation! Да, первая плойка ничего не знала про числа с плавающей запятой, но
при этом стала прорывом в индустрии.&lt;/p&gt;

&lt;p&gt;Как же выкручивались разработчики? На Playstation были спортивные симуляторы,
стратегии, писишные порты Quake 2 или Diablo. Все это добро работало на целых
числах. Если точнее, сишный SDK для Playstation предлагал эмуляцию дробных чисел
в двух вариантах: 16/16 или 20/12. Цифры означают число бит под целую и дробные
части.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/static/aws/numbers-12/1.jpeg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Операции с такими числами выполнялись медленно, потому что раскладывались в
серию машинных команд. Но графический процессор Playstation, который воплощал
цифры в полигоны и вершины, не знал ни про какие дробные числа. Он работал
только с целыми – пикселями.&lt;/p&gt;

&lt;p&gt;Из-за того, что пикселей приставка выдавала немного, некоторые объекты
дребезжали – чуть подергивались. Это называлось Jitter и было особенно заметно в
играх “а-ля Кодзима”, то есть с закосом под кинематографичность. Сцены на движке
игры, плавные движения камеры… при малейшем ее смещении объект мог дернуться
на несколько пикселей, потому что все расчеты были в целых числах. Отсюда эти
дребезжания и подергивания. Частично эту проблему решили в эмуляторах.&lt;/p&gt;

&lt;video controls=&quot;&quot;&gt;
  &lt;source src=&quot;/assets/static/aws/numbers-12/subpixel-line.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;video controls=&quot;&quot;&gt;
  &lt;source src=&quot;/assets/static/aws/numbers-12/jitter-mgs.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;Материал этой заметки основан на статье &lt;a href=&quot;https://pikuma.com/blog/how-to-make-ps1-graphics&quot;&gt;How PlayStation Graphics &amp;amp; Visual
Artefacts Work&lt;/a&gt;. Я прочитал ее пару лет назад и был поражен сложностью, на
которую шли инженеры и программисты, чтобы порадовать нас –
школьников-нищебродов. Спасибо им за игры, сюжеты, контент, на котором я
вырос. Не все мои друзья увлекались плейстейшеном, но для меня он был главным
увлечением и дал много пищи для ума.&lt;/p&gt;
</description>
        <pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/numbers-12/</link>
        <guid isPermaLink="true">https://grishaev.me/numbers-12/</guid>
        
        <category>numbers</category>
        
        
      </item>
    
      <item>
        <title>Числа №11. Большие десятичные типы</title>
        <description>&lt;p&gt;Из прошлых заметок ясно, почему Float нельзя использовать для денег: вы не
контролируете точность. В этих случаях говорят: используйте классы &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigInteger&lt;/code&gt;
и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigDecimal&lt;/code&gt;. Они хранят циклопические числа с гарантией точности. Что это за
классы такие и как они устроены?&lt;/p&gt;

&lt;p&gt;Вспомним, что целое число можно представить массивом разрядов. При сложении
разряды складываются и проверяется переполнение. Если оно было, то к следующему
разряду прибавляется единица и так далее.&lt;/p&gt;

&lt;p&gt;Условный класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigInteger&lt;/code&gt; устроен именно так: он хранит знак и числовой
массив. Каждый его элемент — разряд. При этом разряд очень велик: как правило
это integer, то есть около двух миллиардов! За счет этого достигается
невероятная емкость. Если в каждом элементе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAX_INT&lt;/code&gt; значений, а всего их —
тоже &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAX_INT&lt;/code&gt;, то итоговое число равно&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MAX_INT ^ MAX_INT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;, то есть два миллиарда в степени два миллиарда. Наверное, если бы каждый атом
был Вселенной, то и тогда итоговое число атомов не превысило бы этой цифры.&lt;/p&gt;

&lt;p&gt;Складываются такие числа поразрядно, в столбик. Берутся нулевые элементы
массивов и суммируются как два integer с учетом переполнения. Если оно было, в
нулевой разряд идет остаток, а в следующий накидывается единичка. Аналогично
устроено вычитание.&lt;/p&gt;

&lt;p&gt;Класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigDecimal&lt;/code&gt; устроен просто: это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigInteger&lt;/code&gt; с данными о том, сколько
разрядов хранится после десятичной запятой. Как и в случае с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Float&lt;/code&gt;, при
сложении и вычитании число “плавает”, но на этот раз без потери точности. Мы не
ограничены 32 битами, в нашем распоряжении вся оперативная память.&lt;/p&gt;

&lt;p&gt;Получается, что условные &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigDecimal&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigInteger&lt;/code&gt; — это программная эмуляция
больших чисел. Операции над ними раскладываются в серию машинных команд,
например когда складываются два разряда. Однако таких разрядов много, плюс мы
проверяем переполнение вручную. Такие операции очень медленны в сравнении с
теми, что выполняются на процессоре. Поэтому к программной эмуляции прибегают
только если точность критически важна.&lt;/p&gt;

&lt;p&gt;Postgres предлагает тип &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numeric&lt;/code&gt; — высокоточное хранилище чисел. Он хранит
знак, масштаб и точность — сколько разрядов выделить на число и сколько
проходится на дробную часть. Далее идет массив типа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;short[]&lt;/code&gt; — двухбайтовых
элементов. Каждый элемент — это разряд от 0 до 9999; их число не ограничено.&lt;/p&gt;

&lt;p&gt;Один элемент хранит 10.000 значений, два элемента — 100.000.000 значений. Три —
накиньте еще четыре нуля и так далее. Складываются &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numeric&lt;/code&gt; как описано выше,
только переполнение проверяется для порога в 10 тысяч.&lt;/p&gt;

&lt;p&gt;Почему в Postgres выбрали именно такой порог, я не знаю. Скорее всего, это
ускоряет десятичные операции, например умножение на десятки, деление на сто и
так далее.&lt;/p&gt;

&lt;p&gt;Если вы пользуетесь &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numeric&lt;/code&gt;, обязательно указывайте точность. Иначе возможна
ситуация, когда в целой части, скажем, 123, а в дробной — километровый
хвост. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Numeric&lt;/code&gt; относится к типам произвольной длины, и если значение не
поместиться в 512 байтов (или около того), то будет сброшено в
TOAST-хранилище. Проблемы можно избежать, указав точность: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sum numeric(15, 2)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Впрочем, даже столь мощные десятичные типы бывают избыточны. Иногда деньги
хранят в микроцентах, положение на плоскости — в нанометрах и так далее. Типа
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;long&lt;/code&gt; вполне хватает для этого. Главное — найти такой масштаб, в рамках
которого вам удобно.&lt;/p&gt;
</description>
        <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/numbers-11/</link>
        <guid isPermaLink="true">https://grishaev.me/numbers-11/</guid>
        
        <category>numbers</category>
        
        
      </item>
    
      <item>
        <title>Числа №10. Десятичное представление</title>
        <description>&lt;p&gt;Предположим, мы объявили переменную x, равную 0.1:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;float x = 0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Мы выяснили, что под капотом она становится следующим набором битов:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0 1111011 10011001100110011001101
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Однако если мы напечатаем x, число предстанет в понятном виде:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;println(x)
# 0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Стало быть, некий алгоритм приводит двоичную колбасу к десятичному виду
обратно. Какой же?&lt;/p&gt;

&lt;p&gt;Наивное решение в том, привести двоичную дробь к десятичной, суммируя степени
двойки. Говоря проще, бит N умножается на 2 в степени -N, и все это суммируется,
после чего умножается на два в степени экспоненты. Пример на псевдокоде:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sum(for n in [0..p]: get_bit(n) * 2^(-n)) * 2^e
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;где &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt; – точность (число бит), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; – номер текущего бита, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e&lt;/code&gt; –
экспонента. Картинка с формулой:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/static/aws/numbers-10/1.jpeg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Это математическая формула; она предполагает, что у нас бесконечная точность и
ресурсы для вычислений. Это, конечно, не так. Также обратите внимание, что
получается порочный круг: чтобы привести число с плавающей запятой к строке,
нужно выполнить много других операций с плавающей запятой.&lt;/p&gt;

&lt;p&gt;Должен быть алгоритм, который в идеале:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;выполняется за константное время;&lt;/li&gt;
  &lt;li&gt;использует только целочисленные операции и битовый сдвиг;&lt;/li&gt;
  &lt;li&gt;обратим: если привести строку к float обратно, получим тот же набор битов.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Такие алгоритмы есть, вот некоторые из них: Dragon4, Grisu, Ryū, Giulietti,
Dragonbox.&lt;/p&gt;

&lt;p&gt;Я немного повозился с Giulietti, потому что именно он используется в большинстве
JVM. Если у вас открыта Идея, перейдите в класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java.lang.Float&lt;/code&gt;, метод
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toString&lt;/code&gt;. Вы провалитесь в класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FloatToDecimal.java&lt;/code&gt; и найдете там
реализацию Giulietti. В шапке файла ссылка на академический пейпер с описанием
алгоритма.&lt;/p&gt;

&lt;p&gt;Скажу честно, я не осилил алгоритм во всех деталях. Он сводится к тому, чтобы
найти два целых числа, между которыми заключено исходное число. Затем при помощи
целочисленных операций и сдвигов найти такое дробное число, которое при заданной
точности неотличимо от исходного.&lt;/p&gt;

&lt;p&gt;Реализацию на C++ с подробным разбором можно посмотреть в этой статье:
https://vitaut.net/posts/2025/smallest-dtoa/&lt;/p&gt;

&lt;p&gt;В этой заметке мне нечем похвастать: чтобы разобраться с алгоритмами, нужно
хорошо понимать математику и природу чисел. Математик из меня такой себе, так
что я просто делюсь с вами ссылками – может, кто-то объяснит простыми словами.&lt;/p&gt;

&lt;p&gt;Вывод такой: приведение плавающих чисел к десятичным строкам – тоже незаурядная
задача.&lt;/p&gt;
</description>
        <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://grishaev.me/numbers-10/</link>
        <guid isPermaLink="true">https://grishaev.me/numbers-10/</guid>
        
        <category>numbers</category>
        
        
      </item>
    
  </channel>
</rss>
