Всем привет, готов ещё один разбор. Сегодня будем смотреть доклад не с JPoint, а с DotNext! Автор доклада — Андрей DreamWalker Акиньшин, и посвящено его выступление деталям реализации арифметики с плавающей точкой в .NET:


Слайды можно найти здесь.

Дисклеймер: про реализацию арифметики только сам разбираемый доклад, а не собственно статья.

Сюжет


В этом месте нужно сказать, что я не очень люблю паззлеры. Они требуют знания покрытых мраком уголков спецификации, которое в реальной жизни обычно бесполезно. Просто представьте, что вы сопровождаете код, написанный любителем загадок.

Заметим, заставить человека самого подумать о чём-то важном, прежде чем рассказывать ему, «как правильно» — в общем случае хорошо, он гораздо лучше запомнит результат. Но начать с сути, то есть выбрать знание, которое нужно передать людям, и проиллюстрировать его парадоксальной задачей с простыми условиями — трудно. Для паззлеров часто проще взять те вещи, которые помнить не очень критично, зато их никто не знает, и сделать из них чистое развлечение.

В общем, внимательный читатель легко проследит в этой статье мою нелюбовь к жанру.

Сюжетные ходы и игра актёров


Нет ли тут противоречия: на сцену приходят помощники, чтобы создать иллюзию соревнования, но видно, что ответы и переходные реплики между задачами известны заранее. Например, на 12:00-12:20 Юля рассуждает о том, что приведение к целому — не та операция, и нужно использовать округление, и тут же появляется следующая задача, как раз на округление. Я бы в таких ситуациях менял порядок задач, чтобы тема, которая якобы «пришла в голову» ассистенту, возникала не сразу, а чуть позже. Для большего правдоподобия ассистенты могли бы иногда соглашаться друг с другом.

Также мне кажется неудачным камео Андрея Дмитриева (39:00). Понятно, зачем эта сценка нужна: прорекламировать следующий DotNext и следующий доклад. Но раз уж мы говорим, что кончилось время, хотя есть ещё задачки, то пусть всё выглядит, как будто это правда. Во-первых, выступать «под обрез» и не говорить, что у нас есть ещё несколько минут на вопросы. Во-вторых, засветить две-три дополнительных задачи с ответами и промотать слайды с ними в момент прерывания. Тогда народ, глядишь, заинтересуется, вдруг ещё эффект Зейгарник сработает, будут презентацию качать, всем хорошо.

А так у меня осталось недоумение: что это вообще было? Интеграл ещё…

Не только как, но и почему


Мне в некоторых местах не хватило понимания, что именно курили разработчики спецификации. Зачем-то же они сделали именно так? Это помогло бы не только лучше запомнить, но и отделить важные задачи от случайной ерунды. Посмотрим, например, на ранее упомянутое округление (начинается в 12:20):



Откуда взялось округление до ближайшего чётного? Почему оно включено по умолчанию? Когда нужен режим «прочь от нуля»? Возможно, только я всё пропустил, а нормальные люди давно знают, но я этот вопрос впервые в жизни осмыслил при подготовке данного разбора. Оказывается, режим носит название «банковское округление» и придуман в основном для случаев, когда нужно складывать деньги с центами/копейками в дробной части. Поскольку разных значений центов всего 100, хвост ровно в 50 центов будет встречаться в случайном наборе денежных сумм довольно часто. Если их все округлять прочь от нуля, мы накопим систематическую ошибку, а если к ближайшему чётному, то ошибки округления усреднятся примерно в ноль. При этом каждое конкретное число округляется детерминированно, а не в случайную сторону (а то есть и такой подход).

Поскольку считать деньги с помощью float и double не следует по многим причинам, настройки по умолчанию выглядят нелогичными. Но, по крайней мере, понятно, что за ними стоит. А если кто-то знает другой реалистичный сценарий использования банковского округления, поделитесь, пожалуйста.

Мне кажется, поднимать логику, стоящую за спецификацией, и рассказывать её зрителям — полезно и хорошо. Запоминать не только факты, но и их причины гораздо удобнее.

Вот кто сейчас вспомнит таблицу истинности для операций из упражнения 8 (начало 17:23), хотя в презентации она была?



Это вообще важно знать? Если бы понимать сценарии использования, которые имели в виду разработчики, восстановить её было бы легче.

Актуальность упражнения 10, напротив, не поставить под сомнение (начало на 21:18):



Андрей рассказывает, в каких ситуациях можно наступить на проблему с OverflowException и заканчивает рассказ оптимистичным пожеланием быть аккуратнее. А все ли сразу поняли, как именно нужно быть аккуратнее? Тут бы не помешали подробности.

Как устроен float


Конечно, в ВУЗе у нас были фрагменты курсов, а то и целые курсы, посвящённые представлению данных. И конечно, про числа с плавающей точкой нам там рассказывали, но без практики многое забывается. В некоторых задачах Андрей затрагивает фундаментальные свойства типа float и double и вытекающие из них проблемы, но не вполне акцентирует на этом внимание зрителей. Самый яркий пример — задача про ассоциативность (упражнение 14, начало в 31:30):



Учитывая, что не все в зале ответили правильно, материал первого-второго курса стоит освежить. В данном случае важно, что числа распределены неравномерно: чем дальше от нуля, тем больше между ними промежутки. Давайте рассмотрим два соседних float'а максимального порядка (double по смыслу такие же, только рисовать больше):





С точки зрения рассматриваемого формата между ними ничего нет. При этом в реальном мире первое число равняется 1.7014122 * 1038, второе — 1.701412 * 1038, и разность между ними составляет 0.0000002 * 1038. Это, скажем аккуратно, офигительно много. Прибавить к числу максимального порядка единицу, тысячу или даже миллиард невозможно.

Зная эту механику, легко понять, как ещё нарушается правило ассоциативности: сумма нескольких маленьких чисел может оказаться больше, чем расстояние между двумя соседними большими, тогда как каждое маленькое число в отдельности будет просто уничтожено, если начинать сложение с большого числа.

Выводы


В целом каждый паззлер — увлекательная история, которая даёт докладчику кредит внимания аудитории, и этим кредитом можно пользоваться, чтобы хотя бы из части загадок вывести какую-нибудь мораль (она там на самом деле есть!). Уверен, что доклад от этого выиграл бы.

Слайды


Слайды сделаны хорошо, поэтому не стану придираться к мелочам, а отмечу решения, достойные подражания.

Последовательное появление элементов слайда


В докладе много слайдов с большим количеством информации. Сразу всё на экране разглядеть и понять трудно, но у Андрея все элементы возникают по очереди. Таким образом, зритель ни в какой момент не теряет того места, на которое нужно смотреть и о котором говорит докладчик. Это очень облегчает восприятие больших схем, кусков кода, списков. Делайте, как Андрей.

Единообразие


Пусть в презентации почти нет смешных картинок, если не считать Гомера Симпсона (которого, в принципе, можно было поискать в разрешении получше) и интеграла, она от этого не становится менее весёлой. Математика сама по себе — это весело.

При этом каждое упражнение оформлено одинаково, решение выделено одинаково, объяснение помечено в заголовке слайда — зритель быстро привыкает и ничто не отвлекает его от решения головоломок. Если у вас презентации много маленьких историй, имеет смысл придерживаться сходного оформления, как в данном случае.

Регулярные разборы


Если вы хотите получить обратную связь по своему выступлению, то я с радостью вам её предоставлю.

Что для этого нужно?
  • Ссылка на видеозапись выступления.
  • Ссылка на слайды.
  • Заявка от автора. Без согласия самого докладчика ничего разбирать не будем.

Всё это нужно отправить хабраюзеру p0b0rchy, то есть мне. Обещаю, что отзыв будет конструктивным и вежливым, а также осветит и положительные моменты, а не только то, что надо улучшать.
Поделиться с друзьями
-->

Комментарии (14)


  1. ggrnd0
    02.02.2017 11:27

    Откуда взялось округление до ближайшего чётного? Почему оно включено по умолчанию?

    При суммировании округленных значений денег округление х.5 всегда вверх будет давать накопление ошибок.
    В случае если округляется то вверх, то вниз накопление будет невилироваться.
    Почему четное, а не нечетное не знаю.


    1. p0b0rchy
      02.02.2017 11:39

      Я скомпоновал текст неудачно: между вопросом и ответом большая картинка. Да, это действительно так, но понятно же, что для денег не надо использовать ни float, ни double с центами-копейками в дробной части, а других сценариев мне так сходу придумать не удалось.


      1. ggrnd0
        02.02.2017 12:24

        В этом то и дело.
        Когда печатается чек, стоимость каждой позиции округляется до копеек, а потом суммируется в кассовом аппарате, где скорее всего есть только float.
        В итоге, когда все проданные позиции проссумируются, вы можете из ниоткуда получить 100 рублей или потерять.


        1. Diverclaim
          02.02.2017 15:36

          Хм, но почему бы просто не считать все позиции и сумму в копейках, то есть складывать и вычитать integer? А уже при выводе обрабатывать 4450 как 44 р 50 коп.


          1. ggrnd0
            02.02.2017 15:43

            Потому что 2.37885768462 кг мяса, при стоимости 450 р/кг будут стоить 1070.48595808 р


            Отсюда и округление...


          1. Jef239
            02.02.2017 22:29

            Купили 10 тонн рыбы по цене сто тысяч рублей за тонну. НДС 20% от суммы, то есть 1/6. Это будет 166 666 рублей 66 копеек. А вот теперь распределите эту рыбу по килограммам и граммам, чтобы сумма НДС сошлась. Удастся? :-) Так что минимум сотые копеекй нужны.


        1. esaulenka
          02.02.2017 15:36

          Нету в кассовом аппарате float'а. Более того, в кассовом аппрате рублей-то нет.
          Чтобы не ходить по этим граблям, достаточно считать в копейках. В целых копейках.


          1. ggrnd0
            02.02.2017 15:43

            До целых копеек еще надо округлить...


      1. Azoh
        02.02.2017 12:25
        +1

        Есть еще decimal, для которого подобное округление нужно. Видимо для общности интерфейса решили сделать одинаковые методы.


        1. p0b0rchy
          02.02.2017 15:40

          Похоже, это самое правдоподобное объяснение. С учётом ветки комментариев выше, из всего этого на сцене нормальная такая дискуссия могла бы получиться.


      1. Jef239
        02.02.2017 21:46

        А что делать, если (реальная история 20летней давности) рыба продается килограммами, а цена — за тонну? И НДС считается с минимум с сотыми долями копейки. Так что fixed лучше, чем float, а вот расчет в целых копейках иногда не годится.


  1. pda0
    02.02.2017 18:15

    Это довольно странно. Он говорит, что проверял на 64-битной системе. Но 64-битные приложения должны использовать SSE2, а не FPU. Разве CWR влияет на SSE2? Или .NET даже в 64-битном режиме продолжает FPU использовать?

    P.S. А никто не подскажет, для 1С 8.3 какое значение CWR по умолчанию? ;-)


    1. PsyHaSTe
      02.02.2017 20:23

      Да, х64 использует SSE, только вот как это поможет решить проблему, связанную с самим типом данных?..


      1. pda0
        02.02.2017 21:58

        Я просто этот вопрос как-то не изучал, но разве команды sse2 подчиняются CWR и инициируют исключение? Я просто недавно немного возился с этим и заметил, что только в 32-битной программе у меня получается управлять режимом исключение/NaN, в 64 битах я всегда получел NaN на операциях с NaN…