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

Ну, хранить так хранить. Что вообще значит — «хранить» числа после вычислений, выполненных с бо?льшей точностью, чем допускает формат хранения? Это значит — округлять, а округлять значит вносить погрешности. Погрешности можно оценивать разными способами — и чтобы не повторяться, сегодня мы используем спектральный анализ с помощью преобразования Фурье.

Очень краткое введение


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

Начало


Чтобы было ещё интереснее, в качестве тестового сигнала возьмём не одну синусоиду, а несколько; при этом необходимо следить, чтобы периоды этих синусоид нацело укладывались в период дискретного преобразование Фурье. В Wolfram Mathematica это можно сделать, например, так:

sz = 8192;
data = Table[2 Sum[
Sin[Prime[j] k 2 Pi/sz + j*j]/sz, 
{j, 100, 200, 2}] // N, 
{k, 0, sz - 1}];

Простые числа здесь используются для неравномерного прореживания частот; а j*j сдвигает фазу синусоиды в зависимости от частоты во избежание сильных выбросов в тестовом сигнале, обеспечивая ему более-менее равномерную амплитуду. Визуально полученный сигнал выглядит вот так:



Далее мы нормируем его к единице по максимальному значению, затем преобразуем в целочисленный 32-битный Int, Float, Posit и снова в Double. Если утверждения авторов верны, то погрешность, вносимая преобразованием Double>Posit>Double будет ближе к Doublе, чем к Float.

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

сравнительная таблица
Порог слышимости 0 дБ
Шелест листьев 10 дБ
Шепот 20 дБ
Тиканье часов 30 дБ
Тихая комната 40 дБ
Тихая улица 50 дБ
Разговор 60 дБ
Шумная улица 70 дБ
Опасный для здоровья уровень 75 дБ
Пневматический молоток 90 дБ
Поезд метро 100 дБ
Громкая музыка 110 дБ
Болевой порог 120 дБ
Сирена 130 дБ
Старт ракеты 150 дБ
Смертельный уровень 180 дБ
Шумовое оружие 200 дБ


Итак:

синий — Float
красный — Posit
фиолетовый — Int32
голубой — Double



Posit, конечно же, оказался чуточку лучше Float — но до уровня Double ему ещё далеко. И при этом — хуже Int32! Логично — ведь часть бит у него уходит на порядок… Давайте используем этот порядок — увеличим амплитуду нашего сигнала до 1000:



Внезапно (а на самом деле вполне ожидаемо) шум у Float и Posit сравнялся. Идём дальше — увеличиваем амплитуду до миллиарда:



Float показывает тот же уровень, а Posit начинает отставать. Дальнейшее увеличение амплитуды (здесь 1015) приводит к дальнейшему повышению шумовой полки:



Заключение


Чуда по прежнему не произошло. Спектральный анализ не подтвердил заявления авторов о том, что использование формата Posit в качестве хранения может обеспечить точность близкую к Double. Даже в наилучших условиях шумовая полка у Posit оказались лишь на 20 децибел ниже Float, но при этом выше Int32 на 10 децибел, и выше Double — на 60 децибел.

Конечно, Posit-у вполне можно найти полезное применение — в качестве защиты от выхода за пределы допустимого диапазона, когда выбросы, значительно превосходящие нормальные значения, не будут приводить ни к клиппингу, ни к переполнению. Но даже в таком сценарии Posit выступает скорее как компромисс между Int и Float, нежели однозначно лучший формат чисел.

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


  1. ProLimit
    19.09.2019 18:49

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


    1. ivlis
      20.09.2019 04:57

      В численных расчётах всегда так делают, приводят все константы к «порядка единицы». Амплитуда 10^15 это какая-то экзотика всё же, даже трудно сказать где это может быть востребовано.


      1. Refridgerator Автор
        20.09.2019 06:45

        Я так понимаю, в физике порядок зависит от единиц измерения. Даже если просто фундаментальные физические постоянные посмотреть — там разброс величин от 10-44 до 1032.


  1. sysprg
    19.09.2019 21:25

    Можно переписать на int-ы конкретно преобразование Фурье (и есть уже куча готовых библиотек для этого), но переписывать большие сложные алгоритмы использующие самую разную математику с плавающей точки на int-ы не практично, так как чрезвычайно затратно по объему квалифицированного труда инженеров. И вот в таких случаях posit может проявить себя, если будет реализован в железе. Плюс далеко не во всех приложениях важен объем памяти, где-то важнее точность и используют расширенные представления чисел с плавающей точкой в 10 или 16 байтов, а с posit для этих приложений может хватить и 64-битных чисел (а может и не хватить — зависит от задачи). То есть смысл в нем был бы при аппаратной поддержке в процессоре именно как для прямой замены float/double, прозрачной для 99.999% кода, не требующей изменений в программах, кроме некоторых самых корных функций системных библиотек и компиляторов.


    1. Pand5461
      19.09.2019 23:38

      Позит — это не волшебная палочка какая-то, чтобы 8 байт в 16 превращать. Максимальное теоретическое преимущество posit64 над float64 по длине мантиссы — 7 бит, потому что у float64 11 бит порядка, а у posit64 минимум 4.


      1. sysprg
        20.09.2019 04:04

        Это понятно, но если не хватает 52 битов в double, то иногда используют расширенную точность ради нескольких битов. Которая у некоторых платформ была реализована в виде 16-байтовых значений (IBM 370+, SPARC, Power с его double-double, может что-то еще). Overkill когда это для дополнительных пары битов, но если железо таково, то использовали это для быстроты работы. Плюс даже на x86 из-за проблем с выравниванием обычно пишут 10-байтовые long double по адресам кратным 16, а не 10.


        1. Pand5461
          20.09.2019 09:45

          А, ну если так — то да, может где-то быть полезно. Но тут вопрос, стоит ли овчинка выделки. А то, скажем, при покупке GPU платишь и за блок single-precision, и за блок half-precision, даже если нужны исключительно double и ни битом меньше. А тут аппаратная поддержка ещё одного типа с плавающей точкой, нужного не только лишь всем.


  1. ogamespec
    20.09.2019 11:23

    Какой код использовали для преобразования Posit? Какой разрядности Posit?


  1. Refridgerator Автор
    20.09.2019 11:37

    for (int i = 0; i < data.size(); i++)
    {
    	double v = data[i];
    	data_posit[i] = Posit32(v).getDouble();
    	data_float[i] = float(v);
    }

    Реализация Posit отсюда.


    1. ogamespec
      20.09.2019 12:17

      А если попробовать оригинальную реализацию Posit от авторов, что получится?

      gitlab.com/cerlane/SoftPosit/blob/master/source/c_convertPosit32ToDec.c


      1. Refridgerator Автор
        20.09.2019 12:43

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


        1. ogamespec
          20.09.2019 12:48

          Я думал вы как автор топика попробуете это самостоятельно.

          Я посмотрел код использованной вами реализации и она значительно отличается от оригинала, есть подозрения :)

          И не понятно каким образом Posit 32 «уползла» от float32, вроде бы они «совместимы».
          Хорошо бы добавить в статью типичный кейс числа double, которое вы используете со значениями мантисс/экспонент и таки обоснование почему Posit32 «уползает» по точности.


          1. Refridgerator Автор
            20.09.2019 13:05

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

            Я посмотрел код использованной вами реализации и она значительно отличается от оригинала, есть подозрения :)
            Я его выбрал, потому что:
            — 214 звёзд
            — 20 форков
            — хорошо написан
            — есть тесты
            — С++

            обоснование почему Posit32 «уползает» по точности
            Подробно расписано здесь.


            1. ogamespec
              20.09.2019 13:32

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

              Раздел 1.2:

              The following lemma is quite useful when experimenting with
              posits:

              Lemma 1.1 (Exact cast to FP64). Any Posit8, Posit16 or Posit32
              except NaR is exactly representable as an IEEE-754 FP64 (double precision) number.


              1. Refridgerator Автор
                20.09.2019 19:43

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


            1. ogamespec
              20.09.2019 13:45

              Всё, понял где «уползание» происходит — при переводе double (FP64) в posit (FP32). Ну как бы да… разрядность double выше Posit32 :)


            1. ads83
              20.09.2019 15:03

              Я его выбрал, потому что: хорошо написан

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


              ogamespec, было бы действительно здорово, если вы сравните две реализации posit-ов. Или повторите тесты с библиотекой от авторов posit-а. Я, как человек не знающий С/С++, это сделать не могу :(


              1. ogamespec
                20.09.2019 15:54

                Проверил:

                #include "pch.h"
                #include "CppUnitTest.h"
                
                /// BFP
                
                #include "posit.h"
                
                /// SoftPosit
                
                extern "C"
                {
                #include "platform.h"
                #include "internals.h"
                };
                
                using namespace Microsoft::VisualStudio::CppUnitTestFramework;
                
                namespace PositConvertUnitTest
                {
                	TEST_CLASS(PositConvertUnitTest)
                	{
                	public:
                	
                		TEST_METHOD(TestMethod1)
                		{
                			Logger::WriteMessage("0: ");
                			TestPositConv(0);
                			Logger::WriteMessage("1: ");
                			TestPositConv(1.0);
                			Logger::WriteMessage("EPSILON: ");
                			TestPositConv(DBL_EPSILON);
                			Logger::WriteMessage("MIN: ");
                			TestPositConv(DBL_MIN);
                			Logger::WriteMessage("MAX: ");
                			TestPositConv(DBL_MAX);
                		}
                
                		void TestPositConv(double v)
                		{
                			Posit32 p32(v);
                			double vBfp = p32.getDouble();
                
                			posit32_t bits = { 0 };
                			bits.v = p32.getBits();
                
                			double vSP = convertP32ToDouble(bits);
                
                			Assert::IsTrue(vBfp == vSP);
                
                			Logger::WriteMessage(("double:" + std::to_string(v)
                				+ ", posit32 bits: " + to_hexstring(bits.v)
                				+ ", posit32->Bfp:" + std::to_string(vBfp)
                				+ ", posit32->SP: " + std::to_string(vSP)
                				+ "\n").c_str());
                		}
                
                		std::string to_hexstring(uint64_t value)
                		{
                			std::stringstream stream;
                			stream << "0x" << std::hex << value << std::dec;
                			return stream.str();
                		}
                
                	};
                }
                


                Лог:

                0: double:0.000000, posit32 bits: 0x0, posit32->Bfp:0.000000, posit32->SP: 0.000000
                1: double:1.000000, posit32 bits: 0x40000000, posit32->Bfp:1.000000, posit32->SP: 1.000000
                EPSILON: double:0.000000, posit32 bits: 0x20000, posit32->Bfp:0.000000, posit32->SP: 0.000000
                MIN: double:0.000000, posit32 bits: 0x1, posit32->Bfp:0.000000, posit32->SP: 0.000000
                MAX: double:179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000, posit32 bits: 0x7fffffff, posit32->Bfp:1329227995784915872903807060280344576.000000, posit32->SP: 1329227995784915872903807060280344576.000000

                Обе библиотеки конвертируют одинаково по edge-кейсам.


  1. ads83
    20.09.2019 15:40

    Если утверждения авторов верны, то погрешность, вносимая преобразованием Double>Posit>Double будет ближе к Doublе, чем к Float

    Простите, а почему ближе к Double? Не ровно посередине между Float и Double? Признаюсь, я не погружался глубоко в тему и читал только статьи на Хабре, и Posit32 сравнивается с Float. В этой статье есть табличка с динамическими диапазонами:


    табличка

    image


  1. Vitter
    20.09.2019 16:31

    Я одно не понял, почему 32-posit сравнивают с 64-double, а не с 32-float?
    С 64-double нужно сравнивать 64-posit.
    Но тут 64-posit даже не приводится и ни капли не тестируется ((


    1. Refridgerator Автор
      20.09.2019 19:26

      Потому что, цитирую «Комбинированные операции (fused operations), доступные в posit, предоставляют мощное средство для предотвращения накопления ошибок округления, и в некоторых случаях позволяют безопасно использовать 32-битные числа posit вместо 64-битных float».


  1. defuz
    20.09.2019 18:08

    Спектральный анализ не подтвердил заявления авторов о том, что использование формата Posit в качестве хранения может обеспечить точность близкую к Double.
    На правах автора предыдущей статьи: такого не утверждалось. У Double мантисса 52 бита для любых значений. Тут не нужен спектральный анализ чтобы осознать что 32-битным Posit не достичь аналогичной точности для любых значений.

    Утверждалось что Posit32 могут успешно заменить Double там, где Float32 использовать недопустимо, но не из-за точности представления, а из-за накопления ошибок при вычислениях.

    Posit32 дадут меньше шума чем Float32 для нормализованных значений с не очень большим разбросом. И потому могут быть хорошей альтернативой Float (предоставляя лучшую точность) или Double (там где настолько высокая точность представления ценой увеличения объема в 2 раза не нужна).

    Если точности Float32/Posit32 недостаточно, можно попробовать использовать Posit64. По идее они должны давать меньше шума чем Double даже на относительно высоких амплитудах. Очень интересно было бы увидеть аналогичные графики для них.