Введение


В научных вычислениях мы часто используем числа с плавающей запятой (плавающей точкой). Эта статья представляет собой руководство по выбору правильного представления числа с плавающей запятой. В большинстве языков программирования есть два встроенных вида точности: 32-битная (одинарная точность) и 64-битная (двойная точность). В семействе языков C они известны как float и double, и здесь мы будем использовать именно такие термины. Есть и другие виды точности: half, quad и т. д. Я не буду заострять на них внимание, хотя тоже много споров возникает относительно выбора half vs float или double vs quad. Так что сразу проясним: здесь идёт речь только о 32-битных и 64-битных числах IEEE 754.

Статья также написана для тех из вас, у кого много данных. Если вам требуется несколько чисел тут или там, просто используйте double и не забивайте себе голову!

Статья разбита на две отдельные (но связанные) дискуссии: что использовать для хранения ваших данных и что использовать при вычислениях. Иногда лучше хранить данные во float, а вычисления производить в double.

Если вам это нужно, в конце статьи я добавил небольшое напоминание, как работают числа с плавающей запятой. Не стесняйтесь сначала прочитать его, а потом возвращайтесь сюда.

Точность данных


У 32-битных чисел с плавающей запятой точность примерно 24 бита, то есть около 7 десятичных знаков, а у чисел с двойной точностью — 53 бита, то есть примерно 16 десятичных знаков. Насколько это много? Вот некоторые грубые оценки того, какую точность вы получаете в худшем случае при использовании float и double для измерения объектов в разных диапазонах:

Масштаб Одинарная точность Двойная точность
Размер комнаты микрометр радиус протона
Окружность Земли 2,4 метра нанометр
Расстояние до Солнца 10 км толщина человеческого волоса
Продолжительность суток 5 миллисекунд пикосекунда
Продолительность столетия 3 минуты микросекунда
Время от Большого взрыва тысячелетие минута

(пример: используя double, мы можем представить время с момента Большого взрыва с точностью около минуты).

Итак, если вы измеряете размер квартиры, то достаточно float. Но если хотите представить координаты GPS с точностью менее метра, то понадобится double.

Почему всегда не хранить всё с двойной точностью?


Если у вас много оперативной памяти, а скорость выполнения и расход аккумулятора не являются проблемой — вы можете прямо сейчас прекратить чтение и использовать double. До свидания и хорошего вам дня!

Если же память ограничена, то причина выбора float вместо double проста: он занимает вдвое меньше места. Но даже если память не является проблемой, сохранение данных во float может оказаться значительно быстрее. Как я уже упоминал, double занимает в два раза больше места, чем float, то есть требуется в два раза больше времени для размещения, инициализации и копирования данных, если вы используете double. Более того, если вы считываете данные непредсказуемым образом (случайный доступ), то с double у вас увеличится количество промахов мимо кэша, что замедляет чтение примерно на 40% (судя по практическому правилу O(vN), что подтверждено бенчмарками).

Влияние на производительность вычислений с одинарной и двойной точностью


Если у вас хорошо подогнанный конвейер с использованием SIMD, то вы сможете удвоить производительность FLOPS, заменив double на float. Если нет, то разница может быть гораздо меньше, но сильно зависит от вашего CPU. На процессоре Intel Haswell разница между float и double маленькая, а на ARM Cortex-A9 разница большая. Исчерпывающие результаты тестов см. здесь.

Конечно, если данные хранятся в double, то мало смысла производить вычисления во float. В конце концов, зачем хранить такую точность, если вы не собираетесь её использовать? Однако обратное неправильно: может быть вполне оправдано хранить данные во float, но производить некоторые или все вычисления с двойной точностью.

Когда производить вычисления с увеличенной точностью


Даже если вы храните данные с одинарной точностью, в некоторых случаях уместно использовать двойную точность при вычислениях. Вот простой пример на С:

float sum(float* values, long long count)
{
    float sum = 0;
    for (long long i = 0; i < count; ++i) {
        sum += values[i];
    }
    return sum;
}

Если вы запустите этот код на десяти числах одинарной точности, то не заметите каких-либо проблем с точностью. Но если запустите на миллионе чисел, то определённо заметите. Причина в том, что точность теряется при сложении больших и маленьких чисел, а после сложения миллиона чисел, вероятно, такая ситуация встретится. Практическое правило такое: если вы складываете 10^N значений, то теряете N десятичных знаков точности. Так что при сложении тысячи (10^3) чисел теряются три десятичных знака точности. Если складывать миллион (10^6) чисел, то теряются шесть десятичных знаков (а у float их всего семь!). Решение простое: вместо этого выполнять вычисления в формате double:

float sum(float* values, long long count)
{
    double sum = 0;
    for (long long i = 0; i < count; ++i) {
        sum += values[i];
    }
    return (float)sum;
}

Скорее всего, этот код будет работать так же быстро, как и первый, но при этом не будет теряться точность. Обратите внимание, что вовсе не нужно хранить числа в double, чтобы получить преимущества увеличенной точности вычислений!

Пример


Предположим, что вы хотите точно измерить какое-то значение, но ваше измерительное устройство (с неким цифровым дисплеем) показывает только три значимых разряда. Измерение переменной десять раз выдаёт следующий ряд значений:

3.16, 3.15, 3.16, 3.18, 3.15, 3.11, 3.14, 3.11, 3.14, 3.15

Чтобы увеличить точность, вы решаете сложить результаты измерений и вычислить среднее значение. В этом примере используется число с плавающей запятой в base-10, у которого точность составляет точно семь десятичных знаков (похоже на 32-битный float). С тремя значимыми разрядами это даёт нам четыре дополнительных десятичных знака точности:

3.160000 +
3.150000 +
3.160000 +
3.180000 +
3.150000 +
3.110000 +
3.140000 +
3.110000 +
3.140000 +
3.150000 =
31.45000

В сумме уже четыре значимых разряда, с тремя свободными. Что если сложить сотню таких значений? Тогда мы получим нечто вроде такого:

314.4300

Всё ещё остались два неиспользованных разряда. Если суммировать тысячу чисел?

3140.890

Десять тысяч?

31412.87

Пока что всё хорошо, но теперь мы используем все десятичные знаки для точности. Продолжим складывать числа:

31412.87 +
    3.11 =
31415.98

Заметьте, как мы сдвигаем меньшее число, чтобы выровнять десятичный разделитель. У нас больше нет запасных разрядов, и мы опасно приблизились к потере точности. Что если сложить сто тысяч значений? Тогда добавление новых значений будет выглядеть так:

314155.6  +
     3.12 =
314158.7

Обратите внимание, что последний значимый разряд данных (2 в 3.12) теряется. Вот теперь потеря точности действительно происходит, поскольку мы непрерывно будем игнорировать последний разряд точности наших данных. Мы видим, что проблема возникает после сложения десяти тысяч чисел, но до ста тысяч. У нас есть семь десятичных знаков точности, а в измерениях имеются три значимых разряда. Оставшиеся четыре разряда — это четыре порядка величины, которые выполняют роль своеобразного «числового буфера». Поэтому мы можем безопасно складывать четыре порядка величины = 10000 значений без потери точности, но дальше возникнут проблемы. Поэтому правило следующее:

Если в вашем числе с плавающей запятой P разрядов (7 для float, 16 для double) точности, а в ваших данных S разрядов значимости, то у вас остаётся P-S разрядов для манёвра и можно сложить 10^(P-S) значений без проблем с точностью. Так, если бы мы использовали 16 разрядов точности вместо 7, то могли бы сложить 10^(16-3) = 10 000 000 000 000 значений без проблем с точностью.

(Существуют численно стабильные способы сложения большого количества значений. Однако простое переключение с float на double гораздо проще и, вероятно, быстрее).

Выводы


  • Не используйте лишнюю точность при хранении данных.
  • Если складываете большое количество данных, переключайтесь на двойную точность.

Приложение: Что такое число с плавающей запятой?


Я обнаружил, что многие на самом деле не вникают, что такое числа с плавающей запятой, поэтому есть смысл вкратце объяснить. Я пропущу здесь мельчайшие детали о битах, INF, NaN и поднормалях, а вместо этого покажу несколько примеров чисел с плавающей запятой в base-10. Всё то же самое применимо к двоичным числам.

Вот несколько примеров чисел с плавающей запятой, все с семью десятичными разрядами (это близко к 32-битному float).

1.875545 · 10^-18 = 0.000?000?000?000?000?001?875?545
3.141593 · 10^0 = 3.141593
2.997925 · 10^8 = 299?792?500
6.022141 · 10^23 = 602?214?100?000?000?000?000?000

Выделенная жирным часть называется мантиссой, а выделенная курсивом — экспонентой. Вкратце, точность хранится в мантиссе, а величина в экспоненте. Так как с ними работать? Ну, умножение производится просто: перемножаем мантисссы и складываем экспоненты:

1.111111 · 10^42 · 2.000000 · 10^7
= (1.111111 · 2.000000) · 10^(42 + 7)
= 2.222222 · 10^49

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

3.141593 · 10^0 + 1.111111 · 10^-3 =
3.141593 + 0.0001111111 =
3.141593 + 0.000111 =
3.141704

Заметьте, как мы сдвинули некоторые из значимых десятичных знаков, чтобы запятые совпадали. Другими словами, мы теряем точность, когда складываем числа разных величин.
Поделиться с друзьями
-->

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


  1. iburyl
    28.06.2017 13:49
    +9

    Какой-то субурный набор наблюдений про числа с плавающей точкой.

    Самые большие относительные ошибки внезапно лезут не при сложении, а при вычитании близких значений.
    3.1415 — 3.1414 -> В результате этой операции в мантиссе младшие 4 знака заполнены нулями, т.е. мусором.
    На импортном языке называется catastrophic cancellation.

    В общем и среднем по архитектурам, можно ожидать, что векторизованный код должен отличатся float vs. double по производительности в 2x для арифметических операций ±/*. С делилкой уже не так, с корнями, экспонентами и прочими синусами разрыв больше.

    В выводах написано — много складываете, то складывайте в даблах. А что делать, если я умножаю много — float нормально?


    1. f1inx
      28.06.2017 14:19

      Возьмите сначала логарифм, тогда задача сведется к складыванию :)


  1. f1inx
    28.06.2017 14:10
    +3

    long long в примере вместо size_t ранит сердца опытных разработчиков (особенно знающих, что такое n32).
    Кроме того всегда нужно оценивать необходимую разрядность мантиссы вместо тупого использования double вместо float.

    Если в вашем числе с плавающей запятой P разрядов (7 для float, 16 для double) точности, а в ваших данных S разрядов значимости, то у вас остаётся P-S разрядов для манёвра и можно сложить 10^(P-S) значений без проблем с точностью. Так, если бы мы использовали 16 разрядов точности вместо 7, то могли бы сложить 10^(16-3) = 10 000 000 000 000 значений без проблем с точностью.

    (Существуют численно стабильные способы сложения большого количества значений. Однако простое переключение с float на double гораздо проще и, вероятно, быстрее).

    Проблемы с точностью у double могут возникнуть уже после сложения 2 чисел при разности порядков более 13, разве нет?
    Не нужно поселять необоснованную уверенность универсальности double в мозги юных подаванов.
    И это не частность, достаточно не мало задач, где нас интересует потом SAD в данных или именно младшие разряды.


  1. alexeykuzmin0
    28.06.2017 15:36

    Жаль, что не раскрыта тема вычислений на GPU, ведь именно там наблюдается наибольшая потеря скорости при переключении с float на double (например, для GeForce 900 series разница будет в 32 раза).


  1. BD9
    28.06.2017 16:03

    Обычно расчёты ведут на x86 (IA-32, AMD64). Соответственно полезно знать про внутреннее представление чисел (Extended precision). Кроме 80 бит добавляются новые виды нечисел (не-чисел).
    Вместо разрядности чисел можно выбирать другие алгоритмы расчёта (с улучшениями в сохранении точности).
    Также: округлением чисел можно управлять.
    Насколько понимаю, результат расчётов может зависеть от использования обычного FPU с обычными числами или использования SIMD (SSE, AVX): в первом случае внутреннее представление будет 80 бит, во втором — 64 или 32.
    И
    https://habrahabr.ru/post/112953/#comment_3623058
    https://habrahabr.ru/post/112953/#comment_8730233


    1. iburyl
      28.06.2017 17:25

      80 бит только тогда, когда вас совершенно не интересует производительность решения, но есть ощущение, что ещё чуть-чуть и хватит точности. По большому счёту, в 2017 году можно смело рекомендовать не смотреть на 80 бит никогда, если только это не жуткое легаси.
      Если даблов не хватает, а производительность не интересует, то есть библитеки для вычислений произвольной точность, типа GNU MPFR.
      Если даблов не хватает, но производительность важна — меняйте алгоритм.


    1. marsianin
      28.06.2017 22:24

      Инструкции из набора x87 это legacy, в процессорах эти инструкции работают весьма медленно. И если нет требований именно к точности вычислений, лучше про них вообще забыть. Но выполнять точные вычисления на x87 это так себе занятие. В документации описаны ситуации, когда младший бит мантиссы результата принимает непредсказуемое значение. К сожалению, прямо сейчас я не смогу предоставить ссылку на конкретный раздел документации, но если интересно, завтра могу уточнить.


      1. netch80
        30.06.2017 08:03

        > Инструкции из набора x87 это legacy, в процессорах эти инструкции работают весьма медленно.

        На десктопных и серверных процессорах это пока что не так. (Коллега khim@ говорил про проблемы на Atom и Bobcat, надо уточнять.) Я рядом показывал результаты от его теста после устранения проблем передачи параметров, разницы вообще не было.
        Для сравнения стащил очень прямолинейный расчёт СЛАУ по Гауссу, матрица 1000*1000, случайные коэффициенты; i3-4170 (Haswell).
        FPU+double: 4.6сек; FPU+long double: 5.5 сек — соответствует оценке влияния кэширования; SSE: 3.6 сек — с тем, что в выхлопе clang есть лёгкая векторизация.
        Это ближе к тому, что времена собственно вычисления не отличаются.

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

        Прошу таки уточнить, и заодно к каким версиям это относится. Где-то до 387-го у них было ещё много странностей, но потом вроде всё вычистили?

        P.S. Есть один момент, в котором я полностью согласен, что x87 это legacy: это его неуместный стек регистров. Кому и зачем это стукнуло в голову — ХЗ, но это не единственная, мягко говоря, странность Intel. Но сейчас и его помеху снизили до минимума.


        1. marsianin
          30.06.2017 13:25

          Относительно неточных вычислений, можно прочитать в «Intel® 64 and IA-32 Architectures Software Developer’s Manual», том 1, раздел 8.3.10, «Transcendental Instruction Accuracy»:


          The accuracy of these instructions is measured in terms of units in the last place (ulp).

          With the Pentium processor and later IA-32 processors, the worst case error on transcendental functions is less
          than 1 ulp when rounding to the nearest (even) and less than 1.5 ulps when rounding in other modes.


          Если это интерпретировать, получается, что младший бит в половине случаев будет неправильным. Чтобы он был правильным, необходима точность 0.5 ulp или лучше.


          1. vadimr
            30.06.2017 16:00
            +1

            Более того, Intel и AMD дают разные результаты в младшем бите.


            1. khim
              01.07.2017 00:42

              Более того — это реально создаёт проблемы. У меня есть знакомые, которые исследовали как белок сворачивается. И вот это вот свойство выпило им много крови при написаниях тестов.

              Понятно что получались два примерно разных результата. Понятно что «в природе» белок мог свернуться туда и сюда в таких случаях. Но когда на Intel'е он сворачивается в одну сторону, а на AMD — в другую… это просто усложняет отладку, вот и всё… но всё равно выливается в серьёзные затраты.


              1. netch80
                01.07.2017 21:48
                +1

                Если на разных FPU белок сворачивается в разные стороны, значит, вычисления очень чувствительны к погрешностям такого порядка (~1ulp), а это уже признак, что им надо или повышать точность значений (double недостаточно), или менять модель на более устойчивую. При устойчивой модели такие различия могут сбить младшие разряды (нарушение духа IEEE754 — результаты должны быть воспроизводимы на любом железе), но не привести к принципиально различным результатам.


                1. khim
                  02.07.2017 02:17

                  Если на разных FPU белок сворачивается в разные стороны, значит, вычисления очень чувствительны к погрешностям такого порядка (~1ulp), а это уже признак, что им надо или повышать точность значений (double недостаточно), или менять модель на более устойчивую.
                  А теперь, внимание, вопрос: как вы собрались «менять модель на более устойчивую», если оригинальное явление обладает неустойчивостью? Которую мы, собственно, и исследуем? Как я уже сказал: если этот белок реально синтезировать и посмотреть — куда он сворачивается, то там возможны несколько вариантов. Причём это явление настолько распространено, что в клетке бывают даже специальные «помощники», помогающие определённым белкам выбрать правильное направление сворачивания. В совсем клинических случаях один и тот же белок, свёрнутый по разному, используется клеткой для разных целей (но, понятно, в лекарствах такого ужаса стараются не допускать).

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

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


                  1. netch80
                    02.07.2017 07:35

                    > А теперь, внимание, вопрос: как вы собрались «менять модель на более устойчивую», если оригинальное явление обладает неустойчивостью? Которую мы, собственно, и исследуем?

                    Для неустойчивых явлений должна быть устойчивая модель анализа собственно неустойчивости. Методы для этого в общем известны, хотя там много уникальной теоретической работы в каждом случае.

                    > Понятно, что когда у вас явление неустойчиво малые модификации кода могут приводить к тому, что результаты будут меняться — тут ничего страшного нет, ещё раз повторюсь, мы исследуем явление, которое само по себе неустойчиво. Но когда результаты меняются без модификации кода — это просто неприятно и неудобно…

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


          1. netch80
            30.06.2017 18:17

            Мнэээ… это, мягко говоря, не то, что Вы описывали раньше. Вы говорили про «непредсказуемое» значение младшего бита. В данной же документации, кроме этих слов, сказано ещё одно:

            > The functions are guaranteed to be monotonic, with respect to the input operands, through the domain supported by the instruction.

            Всё вместе наводит на несколько иное понимание — результат конкретной функции для конкретного аргумента всегда один и тот же, но все эти результаты вместе — могут показывать систематическое смещение относительно идеального значения.

            Кроме того, это всё явно описано для максимальной точности (64 бита мантиссы). Уже для double (53 бита) эти ошибки становятся несущественными, потому что менее 1/2048 от ulp.

            Для совсем глубокой раскопки можно было бы поискать, как именно эти систематические ошибки отличаются для разных моделей FPU. Но важно ли это, с учётом предыдущего абзаца?


            1. netch80
              30.06.2017 19:29

              Сам себе поправлю (сбили с толку этим «необходима точность 0.5 ulp или лучше») — не «необходима точность», а «получается точность». Необходимая по IEEE754 — идеальная точность, но с округлением.
              Непредсказуемым от этого результат в стиле FPU не становится, но нестандартным — вполне может.


            1. vadimr
              30.06.2017 19:37

              На самом деле речь идёт просто о том, что машинный результат операции не обязательно является ближайшим представимым приближением к идеальному результату. Но при этом если идеальный1 > идеальный2, то и машинный1 >= машинный2.


            1. marsianin
              30.06.2017 23:06

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


              1. marsianin
                30.06.2017 23:12

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


                1. marsianin
                  30.06.2017 23:23

                  Чтобы быть более конкретным, в данном случае я исхожу из позиции Design Validation, когда процессорные инструкции оцениваются с точки зрения «для вот таких значений регистров на входе должно получиться строго конкретное значение результата, путём выполнения преобразований, описанных документацией».


              1. netch80
                01.07.2017 08:50

                > Завтра Вы купите новый процессор, и в нём FPU будет реализован по-другому.

                С этим безусловно согласен. Но термин «непредсказуемый» мне тут откровенно не нравится, потому что слишком сильно намекает на варианты типа «сразу повторили ту же операцию, а результат другой».
                Считаю тогда, что разногласий тут нет.


                1. marsianin
                  01.07.2017 12:43

                  Под определение «дважды подряд выполнили операцию на одних и тех же исходных данных — получили разные результаты» скорее подойдёт термин «невоспроизводимый».


    1. marsianin
      28.06.2017 22:43

      Что касается управления точностью: если особая точность не нужна, можно вообще включить режим denormal flush to zero. Работает в этом режим обычно пошустрее. Ну и в 32-битном ARM единственная возможность использовать векторизацию — это использовать single precision арифметику и denormal flush to zero.


  1. Regis
    28.06.2017 17:17

    А еще не стоит забывать, что на самом деле бинарная арифметика плохо подходит для многих видов вычислений из реальной жизни. Например для финансовых данных. Так как все вычисления должны воплняться по правилам десятичной арифметики, а не двоичной.


    Бинарная арифметика хорошо подходит там, где основание системы счисления не имеет значения и не требуется контроль округления младших разрядов. Например почти все виды данных связанных с измерением объектов реального физического мира (цаще всего это научные и инженерные данные). Кстати, все примеры в статье — это как раз такие данные ("Размер комнаты", "Окружность Земли" и т.д.).


    1. iburyl
      28.06.2017 17:33
      +1

      В частности поэтому в IEEE 754 2008 года добавили десятичную арифметику. Существую даже железки которые поддерживают десятичную арифметику с плавающей точкой на уровне инструкций.


      1. Regis
        28.06.2017 18:01

        Увы, поддержка IEEE 754 decimal32/decimal64 очень слабая и на сколько мне известно, на поддержку десятичной арифметики в новом железе все производители забили (поправьте если это не так). Плюс дополнительно проблема осложнаяется тем, что тут требуется некоторая поддержка со стороны языков программирования. А её нет (за редким исключением).


        1. netch80
          28.06.2017 21:09

          Десятичная плавучка, считаем, работает только у IBM, но сразу на двух их железных платформах — zSeries (S/390) и Power. Зато у них работает сразу с BCD (Chen-Ho) мантиссой.
          Есть поддержка в GCC (почему-то только для C), софтово, с двоичной мантиссой.


      1. netch80
        28.06.2017 21:23
        +1

        Десятичная плавучка сама по себе имеет для финансовых целей только одно объективное существенное преимущество: это возможность контроля за корректностью операций через inexact exception. Если в decfloat32 ровно 7 цифр мантиссы, а мы складываем 99999.99+0.02 и получаем 100000.00 (100000.01 округлилось) — нам про это inexact скажет. В двоичном варианте практически любые операции с долями приведут к жуткому зашумлению inexact?ами даже на вполне законных операциях.

        А где нет inexact?а (как .NET Decimal) — нет даже этого преимущества.

        Второе преимущество уже не в качестве операций, но в устранении проблем понимания. Когда вопрос «Floating point is broken? 0.1+0.2 != 0.3» задаётся на Stackoverflow каждые пару дней — это показывает реальный барьер: людям сложно понимать двоичную плавучку. И десятичная тут помогает не отпугнуть широкие массы тех, кого нельзя по-честному называть программистами, но без кого на нынешнем безрыбье не обойтись ;( да, я тут чуть снобствую, для ясности объяснения.

        Всё остальное у десятичной арифметики хуже. Точность операций хуже, за счёт размера младшего разряда. Реализации сильно сложнее, и аппаратно, и программно.

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


    1. khim
      28.06.2017 17:36
      +2

      Так как все вычисления должны воплняться по правилам десятичной арифметики, а не двоичной.
      А чисел с фиксированной точкой (то бишь, на самом деле, целых) — не хватает? Просто интересно — что за задача должна быть, чтобы нельзя было всё посчитать, условно говоря, «в копейках» и требовались числа с плавающей десятичной точкой…


      1. Regis
        28.06.2017 17:57
        -2

        Двоичной арифметики с любой точностью не хватает если вам нужны точные (или контролируемо огругляемые) вычисления.


        Канонический пример:


        if (0.1+0.2==0.3) {
             // This will NOT be shown
             printf("Literals are decimal\n");
        }

        Больше примеров здесь:
        http://speleotrove.com/decimal/decifaq1.html


        Кстати, там же говорится, что по проводившемуся IBM по анализу данных от порядка 50 крупных организаций только порядка 2% от общего количества данных использовали двоичное представление (и 55% у десятичных).


        1. netch80
          28.06.2017 21:32
          +2

          Вам оппонент говорит про вычисления с фиксированной точкой (которые обычно делаются в целых числах с подразумеваемым порядком), а Вы возражаете про двоичную плавучку. Это совсем другой случай. Те же 0.1 + 0.2 в варианте в сотых долях просто превратились бы в 10+20 и вычислились бы абсолютно точно и беспроблемно.

          А вот (частично повторю соседний комментарий) 99999.99 + 0.02 при вычислении в decfloat32 окажутся 100000 вместо верного, но уже не представимого, 100000.01. И только inexact exception может свистнуть вам о проблеме, но кто на него обращает внимание?


          1. Regis
            29.06.2017 04:10

            Виноват, я упустил, что khim писал про вычисления с фиксированной точкой.


            Да, чаще всего именно через нее вычисления с контролируемой точностью и проводятся.


          1. nikolayv81
            04.07.2017 19:30

            В таких случаях хорошо бы использовать что-то типа numeric(n,m) используемых в БД.


            1. netch80
              04.07.2017 22:33

              Да, но при жёсткой дисциплине на их использование. Например, сложение-вычитание с разным m недопустимо; при умножении и делении надо уточнять m результата; присвоение с другим m тоже недопустимо без явной конверсии с указанием правила округления… В сумме таких правил накапливается столько, что часто легче явно вести в целых, а масштаб (то самое m) подразумевать контекстом.


  1. mbait
    28.06.2017 21:52
    +6

    Я заметил, что в последнее время стали всё чаще появляться такие вот статьи в стиле "я вам тут щас всё опясню, пацаны". Видимо, теория "в XXI веке можно нагуглить всё" работает не полностью, потому что поверхностные знания не успевают осесть, отфильтроваться и получить практическое подкрепление.


    точность примерно 24 бита

    Ну да, где-то +- бит, в зависимости от температуры процессора.


    Итак, если вы измеряете размер квартиры, то достаточно float. Но если хотите представить координаты GPS с точностью менее метра, то понадобится double.

    Тут я не уверен, но что-то мне подсказывает, что координаты GPS имеют какой-то фиксированные формат. Или автор решил, что как можно сделать, как в той на картинке из CSI?


    Если у вас много оперативной памяти, а скорость выполнения и расход аккумулятора не являются проблемой — вы можете прямо сейчас прекратить чтение и использовать double.

    Смешались в кучу кони, люди. Быстродействие, размер памяти и расход батареи сильно зависят от модели процессора, архитектуры компюьтера, алгоритма и вообще от множества параметров. На некоторых процессорах double вообще не поддерживается аппаратно.


    Если у вас хорошо подогнанный конвейер с использованием SIMD, то вы сможете удвоить производительность FLOPS, заменив double на float. Если нет, то разница может быть гораздо меньше, но сильно зависит от вашего CPU. На процессоре Intel Haswell разница между float и double маленькая, а на ARM Cortex-A9 разница большая.

    Снова кони и люди. Ни слова о том, что, например, у Intel в FPU всегда используется 80-bit precision, а результат потом просто округляется до нужного.


    Исчерпывающие результаты тестов см. здесь.

    Цикл вида C = А [operator] B. Супер-исчерпывающе. Давно известные тесты производительности? Ерунда, возьмём цикл и нарисуем умные графики. Видимо, автору намекнули про это, потому что вверху статьи он пишет: "IMPORTANT: Useful feedback revealed that some of these measures are seriously flawed. A major update is on the way."


    Причина в том, что точность теряется при сложении больших и маленьких чисел

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


    Скорее всего, этот код будет работать так же быстро, как и первый, но при этом не будет теряться точность

    Можно подобрать числа так, что и этот код будет терять точность. Опят же — надо смотреть алгоримт.


    Что в сухом остатке? Сомнительная статья с ошибками и неопределённостями. Читайте лучше классику.


    1. marsianin
      28.06.2017 22:34
      +1

      В Intel 80-битная арифметика — это legacy 1980 года издания. Если процессор поддерживает наборы инструкций SSE и SSE2 (вообще все процессоры Intel начиная с Pentium 4), single и double precision floating point там считается гораздо быстрее, чем в формате 80bit x87. И расчёты в SSE/SSE2 происходят именно в 32-битном и 64-битном форматах. Почитайте что ли архитектурную документацию на досуге.


      1. mbait
        28.06.2017 22:40

        Я что-то говорил про SSE? SIMD это отдельная тема для разговоров. Выше уже упомянули, что на GPU вообще всё по-другому. В этом-то как раз и проблема статьи — всё в кучу, без привязки к конкретным алгоритмам и устройствам.


        UPD: А, я понял. Вас смутил "хорошо подогнанный конвейер с использованием SIMD". Я же хотел сказать, что нельзя в кучу смешивать FPU, SIMD и разные процессорные архитектуры.


        1. marsianin
          28.06.2017 22:47
          +3

          Насколько я помню, SSE — это не только про SIMD, там есть и скалярные операции. Поэтому, собственно, нормальные компиляторы и не генерируют кода с инструкциями x87, если без этого можно обойтись.


          1. khim
            29.06.2017 01:18

            Поэтому, собственно, нормальные компиляторы и не генерируют кода с инструкциями x87, если без этого можно обойтись.
            Только в 64-битном режиме. В x86-64 SSE2 (как минимум) присутствует всегда и используется, в частности, для передачи параметров (не long double) в функции. В 32-битных легаси системах по прежнему используется в разы более медленный FPU — но кому они сейчас интересны?


        1. khim
          29.06.2017 01:15

          Я же хотел сказать, что нельзя в кучу смешивать FPU, SIMD и разные процессорные архитектуры.
          Разные архитектуры смешиваете вы.

          Рассмотрим простейшую программу:
          #include <iostream>
          
          FLOAT __attribute__((noinline)) incld(FLOAT x) {
            return x + 1;
          }
          
          int main() {
            FLOAT x = 0;
            for (long int i=0;i<1000000000;i++) {
              x = incld(x);
            }
            std::cout << x << std::endl;
          }


          И запустим её:
          
          $ g++ -O3 -DFLOAT="float" test.cc
          $ ./test
          1.67772e+07
              0m02.25s real     0m02.19s user     0m00.00s system
          $ g++ -O3 -DFLOAT=double test.cc -o test
          $ ./test
          1e+09
              0m02.22s real     0m02.19s user     0m00.00s system
          $ g++ -O3 -DFLOAT="long double" test.cc -o test
          $ ./test
          1e+09
              0m10.60s real     0m10.41s user     0m00.00s system
          $ cat /proc/cpuinfo | grep model
          model           : 90
          model name      : Intel(R) Atom(TM) CPU  Z3560  @ 1.00GHz
          model           : 90
          model name      : Intel(R) Atom(TM) CPU  Z3560  @ 1.00GHz
          model           : 90
          model name      : Intel(R) Atom(TM) CPU  Z3560  @ 1.00GHz
          model           : 90
          model name      : Intel(R) Atom(TM) CPU  Z3560  @ 1.00GHz
          $ gcc --version
          gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
          Copyright (C) 2013 Free Software Foundation, Inc.
          This is free software; see the source for copying conditions.  There is NO
          warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
          

          Как видим 80-битная арифметика сасем-сасем чут-чут процесс замедляет. В четыре-пять раз всего-навсего. Если для вас это неважно — можете использовать 80-битную арифметику, кто ж вам запретит?

          P.S. Для справки: в современных операционках FPU уже давно никто не использует и реализовано оно во многих современных процессорах «на отвяжись». Отсюда такие результаты. Если уж вы хотите кого-то критиковать — то стоило бы самому «изучить матчасть».


          1. mbait
            29.06.2017 01:50

            Во-первых, такими циклами производительности никто не меряет. Во-вторых, я действительно не знал, что x87 не используется сейчас, но моя критика почти никак не затрагивает этот факт. Я просто привёл конкретный пример, где выбор между float и double никак не влияет на производительность. Можно привести пример другой полярности, и моя позиция будет ровно такая же.


            1. khim
              29.06.2017 03:43

              Во-первых, такими циклами производительности никто не меряет.
              А какими меряет?

              Я просто привёл конкретный пример, где выбор между float и double никак не влияет на производительность.
              А давайте на ваш комментарий посмотрим, а?

              Если у вас хорошо подогнанный конвейер с использованием SIMD, то вы сможете удвоить производительность FLOPS, заменив double на float. Если нет, то разница может быть гораздо меньше, но сильно зависит от вашего CPU. На процессоре Intel Haswell разница между float и double маленькая, а на ARM Cortex-A9 разница большая.
              Снова кони и люди. Ни слова о том, что, например, у Intel в FPU всегда используется 80-bit precision, а результат потом просто округляется до нужного.
              Так что же авторы статьи напутали? Скрыли от читателей «необычайно важный факт», который так же интересен в современном мире, как какая-нибудь PDP-10 с 36-битными и 72-битными числами?

              Можно привести пример другой полярности, и моя позиция будет ровно такая же.
              Ваша позиция, как я понял, заключается в том, что статьи, позволяющие за полчаса сделать выбор в 90% случаев «никому не нужны», а нужны фундаментальные статьи, которые позволяют сделать такой выбор правильно в 99% случаев, но требуют на изучение месяц.

              Но это — не очень конструктивно. Разработчику какой-нибудь MMORPG просто никто не даст изучать месяц фундаментальную литературу — ему нужно принять решение «здесь и сейчас»… ну хорошо — может до завтра есть ещё время подумать…


          1. Refridgerator
            29.06.2017 06:21

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


            1. netch80
              29.06.2017 09:11

              Параллелизацию можно остановить, если пометить x как volatile (это вообще надо было с самого начала сделать). Но и с этим я вижу 4-кратную разницу (на подручном AMD E1-1200 — так что это не только Intel).

              Насколько синтетичность теста влияет — вопрос более серьёзный — тут надо таки взять что-то образцовое (ну, linpack, наверно, overkill, но что-то в эту сторону).


              1. khim
                29.06.2017 13:36

                Насколько синтетичность теста влияет — вопрос более серьёзный — тут надо таки взять что-то образцовое (ну, linpack, наверно, overkill, но что-то в эту сторону).
                Как раз изучать явление лучше на простых, синтетических тестах. Вот оценивать насколько ваше ускорение какой-нибудь мелочи влияет на реальную программу — вот тут да, linpack, SPEC CPU и прочие.


            1. khim
              29.06.2017 13:34

              Здесь ускорение достигнуто исключительно за счёт параллелизации.
              Вы это сейчас серьёзно?

              В этом легко убедиться, если посмотреть ассемблерный код.
              Ну если бы там был векторизованный цикл, то да. Но у вас тут, извините, Аристотелева муха получилась. Вы реально это сделать не пробовали? Попробуйте! Hint: __attribute__((noinline)) там не зря стоит, ох не зря.

              В тех алгоритмах, где автоматическое распараллеливание затруднено или невозможно, такого выигрыша даже и близко не будет.
              Как раз будет. Может и больше будет, та как тут на каждое вычисление вызов функции всунут.


          1. netch80
            29.06.2017 09:37
            +3

            > Разные архитектуры смешиваете вы.

            Ну я его тоже понял так, что SIMD это включая GPU.

            > Как видим 80-битная арифметика сасем-сасем чут-чут процесс замедляет. В четыре-пять раз всего-навсего.

            В вашем тесте основная проблема в конвенции вызова. Компиляторы (gcc, clang) отказываются инлайнить эту incld(). single, double идут только через xmm регистры, а long double — передачи fp в обе стороны через стек. Это и даёт основное замедление. Ваше явное noinline тут резко нечестно — портит результаты именно на вызовах.

            Вот FreeBSD 10.3, процессор Intel i3-4170 (Haswell), компилятор clang 4.2; noinline я явно убрал: во всех 9 комбинациях ('float', 'double', 'long double') * ('-m32 -mfpmath=387', '-m32 -msse -msse2 -mfpmath=sse', '-m64') — время одинаково с точностью до лёгких плаваний — 0.82 секунды. Это означает, что в плотных вычислениях разницы между FPU и SSE разницы нет.

            Что именно тормозит процессор тут в случае передачи через стек (при вашем noinline) — надо копать глубже. Но в варианте с SSE в 32 битах, тест с long double быстрее в 2 раза чем с double (!) — видимо, настолько дорогие у него движения типа «записать из FPU, прочитать для SSE». Что показывает ещё одну сторону некорректности теста.

            Для полезного теста таки надо брать реальную задачу (какие-нибудь СЛАУ или Рунге-Кутты) и сравнивать на ней. Вся эта синтетика не годится ни к чёрту. Вернусь в город — попробую.


            1. Refridgerator
              29.06.2017 11:10

              Я в своё время делал кучу таких сравнений непосредственно на ассемблере, и результат тот же — идентичны. Команды SSE (неупакованные) могут давать слегка лучший результат, потому что:
              1) иногда параллелятся в конвейерах (команды FPU не параллелится из-за стековой архитектуры),
              2) чуть более эффективный код из-за возможности произвольного доступа к регистрам.

              Также где-то в документации Intel встречалось упоминание, что и SSE, и FPU используют один и тоже мат.процессор (архитектурно).


              1. khim
                29.06.2017 13:48

                Я в своё время делал кучу таких сравнений непосредственно на ассемблере, и результат тот же — идентичны.
                «В своё время» — это как раз ключевое. «В своё время» программы были 32-битными и использовали FPU и в хвост и в гриву. Потому скорость его работы была такая же (и иногда и быстрее), чем у SSE.

                А вот на Atom'ах и Bobcat'ах — да, FPU уже начали резать. Интересно узнать что там на Ryzen'е — не удивлюсь, что у него FPU уже тоже работает в режиме «заглушки».

                Также где-то в документации Intel встречалось упоминание, что и SSE, и FPU используют один и тоже мат.процессор (архитектурно).
                В каком-нибудь Pentium4 — возможно, в современных процессорах — нет. Именно вследствии векторизации. AVX требует кучи ALU — и их глупо делать 80-битными, если только один из восьми будет использоваться в таком режиме. Потому на процессорах с поддержкой AVX FPU — это уже отдельный модуль, а дальше возникает вопрос: а нафига ему быть таким большим и сложным, если он, по факту, никем и ни для чего не используется?


                1. Refridgerator
                  29.06.2017 17:32

                  «В своё время» — это два года назад, в том числе и на Core I5.


                1. netch80
                  30.06.2017 07:31

                  > А вот на Atom'ах и Bobcat'ах — да, FPU уже начали резать.

                  Если это «уже» относится к тому, что они они планшетные — ok, соглашусь (таких зверей под рукой удобно нет). Если к развитию архитектуры чего-то уровня хотя бы настольника — не пойдёт, в тех объёмах FPU уже ничего существенно не значит.

                  > Интересно узнать что там на Ryzen'е — не удивлюсь, что у него FPU уже тоже работает в режиме «заглушки».

                  Скоро у нас, похоже, такие будут, проверю. Но — заранее полагаю, что в нём не будут делать такую глупость. AMD всегда заботилась об FPU лучше, чем Intel.


              1. marsianin
                29.06.2017 21:42

                Не может публично доступная документация Intel говорить о том, что SSE и x87 реализованы на одном и том же исполнительном устройстве. Это детали реализации, которые не раскрывает ни один разработчик CPU. Архитектурная документация описывает только то, каким будет результат вычислений, а не то, как он будет достигнут в железе.


                1. netch80
                  30.06.2017 07:22

                  Откройте документ «Intel® 64 and IA-32 Architectures Optimization Reference Manual» и убедитесь, что для конкретных семейств процессоров расписано: количество исполнительных портов, какие команды в них поступают в каком случае, какие задержки (в тактах) на вход конкретной команды, как они увеличиваются при специфическом сочетании с предыдущими командами.
                  Например, для SandyBridge там сказано, что все умножения (кроме чисто целочисленных) и деления в SSE поступают в порт 0, а сложения плавучих и целочисленные умножения — в порт 1 (таблица 2-14).
                  Видимо, Intel имеет несколько отличное от Вашего мнение, что можно публиковать, они не боятся давать такие подробности ;)


                  1. marsianin
                    30.06.2017 13:11

                    Согласен, Intel публикует некоторые микроархитектурные данные. Только приведённая Вами таблица ничего не говорит об исполнительных устройствах. Она говорит, на какие порты scheduler может отправить микрооперацию конкретной группы. То есть, если x87 и AVX FP обрабатываются портом 0, это не значит, что за это отвечает одна и та же логика.

                    И если посмотреть на что-нибудь свежее, типа Skylake (схема 2-1 приведённого Вами документа), то там вообще не говорится про x87 и MMX. Что может говорить о том, что обработка этих инструкций вытащена из общего пайплайна и лежит где-то сбоку. Это, как вы понимаете, не способствует производительности этих инструкций.


                    1. netch80
                      30.06.2017 16:09

                      > То есть, если x87 и AVX FP обрабатываются портом 0, это не значит, что за это отвечает одна и та же логика.

                      В общем случае — да. Но с учётом обычной прямой логики (насколько она применима к Intel) и сложности такого блока — это уже ближе к конспирологии.
                      Не буду дальше вглубляться — у нас ни у кого точных данных нет — закроем на этом.

                      > И если посмотреть на что-нибудь свежее, типа Skylake (схема 2-1 приведённого Вами документа), то там вообще не говорится про x87 и MMX.

                      Skylake у меня есть, мерил — в соседнем комментарии. Максимум найденной разницы — около 1.4 раза. Могли усложнить их выборку — факт. Но это ещё даже не разы. Посмотрим на следующие версии…


                  1. marsianin
                    30.06.2017 13:30

                    Да, и если уж речь зашла про Optimization reference manual, там тоже написано, что x87 может работать медленнее:

                    Assembly/Compiler Coding Rule 63. (M impact, M generality) Use Streaming SIMD Extensions 2 or Streaming SIMD Extensions unless you need an x87 feature. Most SSE2 arithmetic operations have shorter latency then their X87 counterpart and they eliminate the overhead associated with the management of the X87 register stack.


                    1. netch80
                      30.06.2017 16:14

                      Стек — да, безусловно (какой безумец его ввёл в x87?)
                      Latency — да, там в таблицах видно, что обычно на такт-два больше.
                      Ещё они могли тут иметь в виду проблемы с денормализованными. SSE тормозит на заметно меньшем количестве случаев с денормализованными, чем FPU; вообще же это дикий позор, что за 30+ лет с KCS реализации это не исправили.
                      Но в среднем это всё-таки даже не разы.


                      1. marsianin
                        30.06.2017 16:33

                        На SSE есть ещё способ поднять performance — отказаться от обработки denormal, включив FTZ и DAZ, что заставит все denormal числа интерпретировать как нули. Как я понимаю, в x87 такой возможности нет.


                        1. netch80
                          30.06.2017 17:46

                          Вот просто потрясающий пример, как можно из одних и тех же данных делать противоположные выводы. ;)

                          Денормализованные — это не ужас, и не повод тратить тысячи тактов на ерунду. Алгоритмы для этого отработаны уже годами, максимум потерь в аппаратной реализации — два такта на результат. И если у SSE, сделанного через 20 лет после FPU, есть проблема с денормализованными — это значит, что внутри там взята всё та же тупая реализация времён судорожного клепания K-C-S.

                          А то, что Вы видите флаг справляться с ними в случае SSE, и не видите в случае FPU — это уже фирменный стиль Intel, который просто отказался помочь пользователям FPU, с мотивацией типа «нефиг, пусть быстрее сбегают на SSE».


                          1. marsianin
                            30.06.2017 22:28

                            В том и вопрос: производитель CPU совершенно открыто говорит: x87 это legacy. Не нужно его использовать без крайней необходимости.

                            И да, я нигде не говорил о том, что x87 в разы медленнее, не надо мне приписывать чужих слов. Я говорил, что он просто медленнее, вы и сами это признали.


                            1. netch80
                              01.07.2017 08:42

                              > И да, я нигде не говорил о том, что x87 в разы медленнее

                              OK, у Вас было «весьма медленнее». Это эмоциональная оценка, но по-моему таки «весьма» это «не менее чем в разы» (иначе это несущественно по сравнению с прочими обстоятельствами).

                              > Я говорил, что он просто медленнее, вы и сами это признали.

                              Более точно — что уже начался период, что он хоть и немного, но медленнее.
                              Далее khim@ настаивает, что это будет нарастать, а я неизбежно соглашаюсь ;(


                              1. marsianin
                                01.07.2017 09:42

                                Ну, не знаю, для меня 1.4 раза — это уже «весьма», особенно когда я наблюдаю, как народ борется за лишние 5% перфоманса.


                      1. nikolayv81
                        04.07.2017 23:33

                        || какой безумец его ввёл в x87?
                        x87 был отдельным сопроцессором ( физически отдельная микросхема ) без стека там, вероятно, сложно было…


                        1. netch80
                          05.07.2017 09:11

                          Control & status передаются в FPU и обратно без всякого стека. Значит, могли, если хотели.
                          Тут всё-таки идеологические соображения. Но непонятно, какие именно.


                          1. nikolayv81
                            06.07.2017 00:11

                            Не стану настаивать, но насколько помню была проблема в т.ч. с количеством ног. 8086 у меня не было но на z80 их уже не хватало, и в т.ч. поэтому было мультиплекирование (И ИМХО существовал 8088, по крайней мере это было бы логичным поводом для его выпуска)
                            В итоге, т.к. два чипа висели на одном потоке команд нужно было как-то данные сохранять в сопроцессоре для обработке (там же ещё операции долгие и основной поток команд мог выполнятся параллельно на основном процессоре).
                            После выхода 486 такое поведение стало ненужным но осталось, а потом intel решил постепенно заменять сопроцессор на наборы инструкций для основного ядра mmx и т.п. в порчдке их востребованности и возможностей по реализации.


            1. khim
              29.06.2017 13:42

              Вот FreeBSD 10.3, процессор Intel i3-4170 (Haswell)
              Выкидываем. На Haswell'е ещё приличный FPU.

              Но в варианте с SSE в 32 битах, тест с long double быстрее в 2 раза чем с double (!) — видимо, настолько дорогие у него движения типа «записать из FPU, прочитать для SSE».
              Да, пересылки между FPU и SSE весьма и весьма дороги. Это отдельные модули. Но мой тест был не об этом.

              Для полезного теста таки надо брать реальную задачу (какие-нибудь СЛАУ или Рунге-Кутты) и сравнивать на ней.
              В реальной задаче как раз «затыки» сложнее увидеть. Хотя для «оценки масштабов бедствия» — да, нужна реальная задача…


              1. netch80
                30.06.2017 08:33

                > Выкидываем. На Haswell'е ещё приличный FPU.

                По сравнению с чем?
                Если с супермелкими изделиями вроде Atom — ok, может быть (приму пока сам не померю).
                Если с более новыми поколениями — «меня терзают смутные сомнения» (tm)

                Вот есть лаптоп с «Intel® Pentium® CPU 4405U @ 2.10GHz» (Skylake, но урезанный до мобильной версии, SSE весь есть, AVX отсутсвует).

                Решение СЛАУ по Гауссу, прямолинейное; матрица 1000*1000 случайных чисел; Ubuntu 16.04; gcc5; -O3.

                32 бита, FPU, double: 3.6 сек.
                32 бита, FPU, long double: 5.5 сек (явно, затраты на cache misses)
                32 бита, SSE, double: 3.2 сек. (код показывает частичную векторизацию)
                64 бита, SSE, double: 2.3 сек.
                64 бита, FPU, long double: 4.6 сек.

                Как-то всё равно не укладывается в Вашу концепцию «FPU стал в разы медленнее», максимум подтверждаемого именно для отношения SSE/FPU это где-то 1.4 раза, при тщательном исследовании наверняка будет ещё меньше.

                И я уверен, что для всех уровней процессоров от хорошего лаптопа и выше это сохранится ещё долго.


    1. netch80
      29.06.2017 09:54

      > Тут я не уверен, но что-то мне подсказывает, что координаты GPS имеют какой-то фиксированные формат. Или автор решил, что как можно сделать, как в той на картинке из CSI?

      Я уверен, имелись в виду координаты в виде «50.44467/35.21212», как на maps-сайтах.


      1. khim
        29.06.2017 13:49

        Там всё ещё веселее. GPS — это ж не машинка из которой координаты вылезают. Из железки только время до спутника получается, остальное — нужно считать. Триангуляция, поправки разные, вот это вот всё.


  1. Refridgerator
    29.06.2017 11:20

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


    1. khim
      29.06.2017 13:54

      Вот тут — вы правы, конечно. Именно за счёт __float128 POWER'ы до сих пор из Top500 не пропали.

      Но такой большой и сложный тип нужно применять осмысленно, а не из соображений «на всякий случай, пусть будет».


      1. Refridgerator
        29.06.2017 17:37

        Я освоил технологию double-double и живу счастливо. И кстати, при её реализации на ассемблере 80-битный real оказался весьма полезным)


  1. vadimr
    29.06.2017 21:28
    -1

    Забудьте про существование типа float и не парьте себе мозг.


    1. marsianin
      29.06.2017 22:11

      Эммм…
      А что вы предлагаете использовать вместо float?


      1. vadimr
        29.06.2017 22:11

        double


        1. netch80
          30.06.2017 16:15
          +1

          Почему не сразу quad?


          1. vadimr
            30.06.2017 16:20

            17 цифр достаточно для большинства применений. В двойной точности работает большинство численных библиотек. С другой стороны, 64 бита – удобный размер для выравнивания в памяти.

            Четырёхбайтовые вещественные числа – реликт времён, когда память была ограничена 64 килобайтами.


            1. marsianin
              30.06.2017 16:29

              Что ж тогда ARM вообще half precision вычисления вводит в ARMv8.1? Они-то вообще на 16-битных числах.


              1. vadimr
                30.06.2017 16:33

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


                1. marsianin
                  30.06.2017 16:35
                  +1

                  Ага, 64-битная архитектура ARMv8.1 с аппаратной виртуализацией — это очень встроенные решения. Осталось только понять, куда они встроены (-:


                  1. vadimr
                    30.06.2017 16:36

                    В мобильные телефоны?

                    В научных расчётах half precision точно не имеет никакого применения.


                    1. marsianin
                      30.06.2017 16:48
                      +2

                      Пардон, ошибся, не ARMv8.1, а ARMv8.2.

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

                      Я, конечно, не специалист в вычислениях, но в интернетах пишут, что half precision арифметика вполне неплохо показывает себя в задачах, связанных с deep learning. Так что, может быть не стоит считать, что нужны только double precision?


                      1. vadimr
                        30.06.2017 16:58

                        Давно известны строгие математические оценки на этот счёт, с которыми автор статьи не дал себе труда познакомиться, прежде чем заявлять о преимуществе float (или, тем более, half) для экономии памяти. Предположим, у нас есть 4 гигабайта памяти, то есть миллиард чисел типа single. Чтобы получить из анализа всех этих данных какой-то практический результат, мы должны произвести, как минимум, миллиард последовательных операций над ними (иначе либо мы используем не все данные, либо цепочка операций не сойдётся к одному результату). При погрешности полбита на операцию, мы получаем в результате потерю точности не менее полумиллиарда цены младшего разряда, или 9 десятичных цифр, что уже превосходит всю разрядную сетку float. Вот и весь лёнинг.

                        Это не относится к параллельным преобразованиям из матрицы в матрицу. Но матрица из миллиарда чисел не может непосредственно восприниматься человеком и потому не является конечным результатом.


                        1. khim
                          30.06.2017 19:50
                          +1

                          Но матрица из миллиарда чисел не может непосредственно восприниматься человеком и потому не является конечным результатом.
                          Это кто вам такую чушь сказал? DCI 4K, 60FPS, три компоненты изображения — вот вам уже больше полутора миллиардов чисел.

                          Или вы хотите сказать, что человек неспособен видеоизображение воспринимать?

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

                          Чтобы получить из анализа всех этих данных какой-то практический результат, мы должны произвести, как минимум, миллиард последовательных операций над ними
                          Зачем? Подумайте о компьютере, работающем на частоте 500 герц и порождающем ответ за 1/10 секунды, опираясь на матрицу из миллиардов чисел! Довольно очевидно, что этот компьютер не производит миллиардов последовательных вычислений — что не мешает ему быть весьма и весьма полезным в разных случаях… Или вы не верите в существование такого?
                          Посмотрите в зеркало!


                          1. vadimr
                            30.06.2017 20:09

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


                            1. khim
                              30.06.2017 20:25

                              Видеоизображение состоит из целых чисел, точность которых может не уменьшаться при операциях с ними.
                              Почитайте хотя бы википедию, если ни на что другое времени нет: реальные сцены часто имеют динамический диапазон яркости в 1 000 000:1 и выше, при этом и в тенях и в свете глаз способен (из-за световой адаптации к яркости) различить детали

                              «из-за световой адаптации к яркости» — это в чистом виде плавучка и есть. При этом ни о каких double'ах или даже float'ах и речи не идёт.

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

                              Hint: наука, как бы, весьма многообразна и при каком-нибудь анализа ДНК могут применяться самые разнообразные алгоритмы, совершенно не сводящиеся к любимым вами манипуляциям над матрицами миллиард на миллиард.


                              1. vadimr
                                30.06.2017 20:37

                                Световая адаптация к яркости — это чисто механическое изменение площади зрачка, не имеющее никакого отношения к обработке сцены в мозге. Просто диафрагмирование.


                                1. khim
                                  30.06.2017 21:07

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


                                  1. vadimr
                                    30.06.2017 21:26

                                    Вы сейчас говорите про обработку в DCI 4K, 60FPS или в человеческом мозге?

                                    Кадр DCI 4K – это просто 8 миллионов целых чисел. Как целые числа, они не требуют вещественной арифметики. Динамический диапазон такого кадра равен динамическому диапазону устройства отображения, независимо от того, какие там использованы сцены.

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


                                    1. khim
                                      30.06.2017 22:31

                                      Кадр DCI 4K – это просто 8 миллионов целых чисел.
                                      Да — но это результат. Чтобы его получить вам вполне может потребоваться обрабатывать совершенно разные вещи, при этом вы не будете знать — какая часть попадёт в конечное изображение.

                                      Кто тут говорил что точность для промежуточных представлений нужна большая, чем для результата? Вот HDR — это то же самое, только с динамическим диапазоном. Если в одном кадре у вас ярко светит солнце, а в следующем — оно чем-то загорожено, то вам потребуется резко «перескочить» в другой динамический диапазон.


                                      1. vadimr
                                        30.06.2017 23:04

                                        Промежуточные представления чего? Если это реальная видеосъёмка, то там такая же ограниченная в разрядности КМОП-матрица на входе. А если анимация, то ей незачем иметь солнце реальной огромной яркости.

                                        Безусловно, всё можно сделать с огромным диапазоном, если считать тупо в лоб. Но я-то говорю как раз о том, что это низачем не нужно для потребного человеку скромного результата.


                                        1. khim
                                          30.06.2017 23:19

                                          Если это реальная видеосъёмка, то там такая же ограниченная в разрядности КМОП-матрица на входе.
                                          Во-первых может быть не одна. Во-вторых — можно синтезировать из нескольких снимков изображение. В третьих — его можно рассчитать.

                                          А если анимация, то ей незачем иметь солнце реальной огромной яркости.
                                          С чего вы взяли? Многие вполне себе наблюдаемые эффекты без этого нормально не рассчитать.

                                          Но я-то говорю как раз о том, что это низачем не нужно для потребного человеку скромного результата.
                                          В том-то и дело, что нужно. Результаты, полученные с применением HDR заметно оличаются от тех, где HDR не применяется.

                                          Можно, конечно, упираться и говорить что 640K, VGAAtari 2600 — достаточно для щастья, но это уже чистой воды волюнтаризм: да, в игры для Atari 2600 вполне можно играть, но это не значит что FP16 не нужен!


                                1. netch80
                                  01.07.2017 08:46

                                  Не только. Большинство изученных нервных датчиков, IIRC, передают сигнал как логарифм входного воздействия (при этом собственно уровень такого сигнала после логарифмирования передаётся как частота импульсов).


                                  1. vadimr
                                    01.07.2017 09:17

                                    Ну это просто вопрос выбора единицы измерения


                          1. vadimr
                            30.06.2017 20:16

                            Собственно, компьютер производит именно миллиарды последовательных вычислений, результатом которых является состояние регистров процессора. Но так как это цепочка логических операций, а не вещественных (с точки зрения работы самого компьютера), то она выполняется без погрешности.


                            1. khim
                              30.06.2017 20:27

                              Но так как это цепочка логических операций, а не вещественных (с точки зрения работы самого компьютера), то она выполняется без погрешности.
                              Так можно дойти до того, чтобы обьявить что никаких чисел с плавающей точкой в природе вообще нет — это ж фикция! Там просто два целых числа, фигли мы вокруг этого какие-то теории разводим?


                              1. vadimr
                                30.06.2017 20:33

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


                          1. vadimr
                            30.06.2017 20:24

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


                            1. khim
                              30.06.2017 20:30

                              Есть электрохимические потенциалы нейронов, имеющие, впрочем, очень высокую точность по сравнению с точностью результата.
                              Ну то есть всё точно так же, как с алгоритмами, которые используют числа с половинной точностью. И которые вы обьявили заочно «никому не нужной фигнёй».


                              1. vadimr
                                30.06.2017 20:39

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


                                1. khim
                                  30.06.2017 21:10

                                  Но это не то, что принято называть научными расчётами.
                                  Серьёзно? Методы Монте-Карло уже обьявлены лже-наукой? И выделение генов с помощью нейронных сетей — тоже?

                                  Очень узкая у вас получается наука: подавляющее большинство вычислений, имеющих практический «выхлоп» окажется «за бортом».


                                  1. vadimr
                                    30.06.2017 21:31

                                    Вы видели метод Монте-Карло, ушедший по бабам?