Испытания Posit по-взрослому. Спектральный анализ

    Обсуждения достоинств и недостатков нового революционного формата с плавающей запятой 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, нежели однозначно лучший формат чисел.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 24

      0
      То есть формат хорош для чисел близких к нулевому порядку. Это свойство может быть полезно в некоторых задачах где примерно можно оценить пороядок величин или свести их к нулевому при хранении. Старый добрый float ведет себя более стабильно и предсказуемо, когда разброс величин неизвестен заранее.
        +3
        В численных расчётах всегда так делают, приводят все константы к «порядка единицы». Амплитуда 10^15 это какая-то экзотика всё же, даже трудно сказать где это может быть востребовано.
      0

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

        0

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

          0

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

            +1

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

        0
        Какой код использовали для преобразования Posit? Какой разрядности Posit?
          0
          for (int i = 0; i < data.size(); i++)
          {
          	double v = data[i];
          	data_posit[i] = Posit32(v).getDouble();
          	data_float[i] = float(v);
          }

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

            gitlab.com/cerlane/SoftPosit/blob/master/source/c_convertPosit32ToDec.c
              0
              То же самое должно получиться — если, конечно, реализация от авторов не является «единственно правильной». Попробуйте, может у вас другие результаты получатся — будет интересно посмотреть.
                –1
                Я думал вы как автор топика попробуете это самостоятельно.

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

                И не понятно каким образом Posit 32 «уползла» от float32, вроде бы они «совместимы».
                Хорошо бы добавить в статью типичный кейс числа double, которое вы используете со значениями мантисс/экспонент и таки обоснование почему Posit32 «уползает» по точности.
                  +3
                  Я думал вы как автор топика попробуете это самостоятельно
                  Мне, как автору двух топиков было интересно разобраться, кто прав. Сравнивать между собой различные реализации Posit уже не интересно. Если вам это интересно — сравнивайте, пишите, и тоже станете автором своего собственного топика.

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

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

                    Раздел 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
                      Не понимаю, в чём рассогласование. Согласно этой лемме, posit32 можно приводить к double без внесения дополнительных погрешностей — а значит, оценивать погрешности posit32 после преобразования их к double вполне корректно.
                      0
                      Всё, понял где «уползание» происходит — при переводе double (FP64) в posit (FP32). Ну как бы да… разрядность double выше Posit32 :)
                        0
                        Я его выбрал, потому что: хорошо написан

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


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

                          0
                          Проверил:

                          #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-кейсам.
                0
                Если утверждения авторов верны, то погрешность, вносимая преобразованием Double→Posit→Double будет ближе к Doublе, чем к Float

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


                табличка

                image


                , и там Posit с es value=3 (что бы это ни значило) гораздо ближе к Float, чем Double.


                Кроме того, в комментариях я видел утверждения, что Posit32 by design превосходит Float по точности в диапазоне до 10^6, за что расплачивается меньшей точностью на числах 10^20. Это соответствует действительности?

                  0
                  Я одно не понял, почему 32-posit сравнивают с 64-double, а не с 32-float?
                  С 64-double нужно сравнивать 64-posit.
                  Но тут 64-posit даже не приводится и ни капли не тестируется ((
                    0
                    Потому что, цитирую «Комбинированные операции (fused operations), доступные в posit, предоставляют мощное средство для предотвращения накопления ошибок округления, и в некоторых случаях позволяют безопасно использовать 32-битные числа posit вместо 64-битных float».
                      +1
                      Вот с чего вы решили, что «в некоторых» это синоним «в (почти) всех»?
                        +1
                        Безусловно, в некоторых случаях 64-битные double могут быть заменены posit. В некоторых случаях 64-битные double могут быть заменены даже 32-битными float, а иногда даже — 16-битными интами. 64-битные double могут быть заменены даже 1-битными числами, если являются результатом функции sign(x).
                    0
                    Спектральный анализ не подтвердил заявления авторов о том, что использование формата Posit в качестве хранения может обеспечить точность близкую к Double.
                    На правах автора предыдущей статьи: такого не утверждалось. У Double мантисса 52 бита для любых значений. Тут не нужен спектральный анализ чтобы осознать что 32-битным Posit не достичь аналогичной точности для любых значений.

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

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

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

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

                    Самое читаемое