Ну, хранить так хранить. Что вообще значит — «хранить» числа после вычислений, выполненных с бо?льшей точностью, чем допускает формат хранения? Это значит — округлять, а округлять значит вносить погрешности. Погрешности можно оценивать разными способами — и чтобы не повторяться, сегодня мы используем спектральный анализ с помощью преобразования Фурье.
Очень краткое введение
Если взять сигнал в виде синусоиды и выполнить над ним преобразование Фурье, то в спектре мы должны получить один-единственный пик; по факту же в спектре могут присутствовать как гармоники с частотой кратной основному тону, полученных вследствие нелинейных искажений, так и шумовая полка, полученная вследствие шумов, наводок и оцифровки. Вот уровень этих шумов мы и будем измерять.
Начало
Чтобы было ещё интереснее, в качестве тестового сигнала возьмём не одну синусоиду, а несколько; при этом необходимо следить, чтобы периоды этих синусоид нацело укладывались в период дискретного преобразование Фурье. В 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)
sysprg
19.09.2019 21:25Можно переписать на int-ы конкретно преобразование Фурье (и есть уже куча готовых библиотек для этого), но переписывать большие сложные алгоритмы использующие самую разную математику с плавающей точки на int-ы не практично, так как чрезвычайно затратно по объему квалифицированного труда инженеров. И вот в таких случаях posit может проявить себя, если будет реализован в железе. Плюс далеко не во всех приложениях важен объем памяти, где-то важнее точность и используют расширенные представления чисел с плавающей точкой в 10 или 16 байтов, а с posit для этих приложений может хватить и 64-битных чисел (а может и не хватить — зависит от задачи). То есть смысл в нем был бы при аппаратной поддержке в процессоре именно как для прямой замены float/double, прозрачной для 99.999% кода, не требующей изменений в программах, кроме некоторых самых корных функций системных библиотек и компиляторов.
Pand5461
19.09.2019 23:38Позит — это не волшебная палочка какая-то, чтобы 8 байт в 16 превращать. Максимальное теоретическое преимущество posit64 над float64 по длине мантиссы — 7 бит, потому что у float64 11 бит порядка, а у posit64 минимум 4.
sysprg
20.09.2019 04:04Это понятно, но если не хватает 52 битов в double, то иногда используют расширенную точность ради нескольких битов. Которая у некоторых платформ была реализована в виде 16-байтовых значений (IBM 370+, SPARC, Power с его double-double, может что-то еще). Overkill когда это для дополнительных пары битов, но если железо таково, то использовали это для быстроты работы. Плюс даже на x86 из-за проблем с выравниванием обычно пишут 10-байтовые long double по адресам кратным 16, а не 10.
Pand5461
20.09.2019 09:45А, ну если так — то да, может где-то быть полезно. Но тут вопрос, стоит ли овчинка выделки. А то, скажем, при покупке GPU платишь и за блок single-precision, и за блок half-precision, даже если нужны исключительно double и ни битом меньше. А тут аппаратная поддержка ещё одного типа с плавающей точкой, нужного не только лишь всем.
Refridgerator Автор
20.09.2019 11:37for (int i = 0; i < data.size(); i++) { double v = data[i]; data_posit[i] = Posit32(v).getDouble(); data_float[i] = float(v); }
Реализация Posit отсюда.ogamespec
20.09.2019 12:17А если попробовать оригинальную реализацию Posit от авторов, что получится?
gitlab.com/cerlane/SoftPosit/blob/master/source/c_convertPosit32ToDec.cRefridgerator Автор
20.09.2019 12:43То же самое должно получиться — если, конечно, реализация от авторов не является «единственно правильной». Попробуйте, может у вас другие результаты получатся — будет интересно посмотреть.
ogamespec
20.09.2019 12:48Я думал вы как автор топика попробуете это самостоятельно.
Я посмотрел код использованной вами реализации и она значительно отличается от оригинала, есть подозрения :)
И не понятно каким образом Posit 32 «уползла» от float32, вроде бы они «совместимы».
Хорошо бы добавить в статью типичный кейс числа double, которое вы используете со значениями мантисс/экспонент и таки обоснование почему Posit32 «уползает» по точности.Refridgerator Автор
20.09.2019 13:05Я думал вы как автор топика попробуете это самостоятельно
Мне, как автору двух топиков было интересно разобраться, кто прав. Сравнивать между собой различные реализации Posit уже не интересно. Если вам это интересно — сравнивайте, пишите, и тоже станете автором своего собственного топика.
Я посмотрел код использованной вами реализации и она значительно отличается от оригинала, есть подозрения :)
Я его выбрал, потому что:
— 214 звёзд
— 20 форков
— хорошо написан
— есть тесты
— С++
обоснование почему Posit32 «уползает» по точности
Подробно расписано здесь.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.Refridgerator Автор
20.09.2019 19:43Не понимаю, в чём рассогласование. Согласно этой лемме, posit32 можно приводить к double без внесения дополнительных погрешностей — а значит, оценивать погрешности posit32 после преобразования их к double вполне корректно.
ogamespec
20.09.2019 13:45Всё, понял где «уползание» происходит — при переводе double (FP64) в posit (FP32). Ну как бы да… разрядность double выше Posit32 :)
ads83
20.09.2019 15:03Я его выбрал, потому что: хорошо написан
В комментариях к вашей прошлой статье уже обсуждали, что написана эта реализация спорно. В частности, у меня были вопросы к реализации извлечения корня.
ogamespec, было бы действительно здорово, если вы сравните две реализации posit-ов. Или повторите тесты с библиотекой от авторов posit-а. Я, как человек не знающий С/С++, это сделать не могу :(
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-кейсам.
ads83
20.09.2019 15:40Если утверждения авторов верны, то погрешность, вносимая преобразованием Double>Posit>Double будет ближе к Doublе, чем к Float
Простите, а почему ближе к Double? Не ровно посередине между Float и Double? Признаюсь, я не погружался глубоко в тему и читал только статьи на Хабре, и Posit32 сравнивается с Float. В этой статье есть табличка с динамическими диапазонами:
табличкаVitter
20.09.2019 16:31Я одно не понял, почему 32-posit сравнивают с 64-double, а не с 32-float?
С 64-double нужно сравнивать 64-posit.
Но тут 64-posit даже не приводится и ни капли не тестируется ((Refridgerator Автор
20.09.2019 19:26Потому что, цитирую «Комбинированные операции (fused operations), доступные в posit, предоставляют мощное средство для предотвращения накопления ошибок округления, и в некоторых случаях позволяют безопасно использовать 32-битные числа posit вместо 64-битных float».
defuz
20.09.2019 18:08Спектральный анализ не подтвердил заявления авторов о том, что использование формата Posit в качестве хранения может обеспечить точность близкую к Double.
На правах автора предыдущей статьи: такого не утверждалось. У Double мантисса 52 бита для любых значений. Тут не нужен спектральный анализ чтобы осознать что 32-битным Posit не достичь аналогичной точности для любых значений.
Утверждалось что Posit32 могут успешно заменить Double там, где Float32 использовать недопустимо, но не из-за точности представления, а из-за накопления ошибок при вычислениях.
Posit32 дадут меньше шума чем Float32 для нормализованных значений с не очень большим разбросом. И потому могут быть хорошей альтернативой Float (предоставляя лучшую точность) или Double (там где настолько высокая точность представления ценой увеличения объема в 2 раза не нужна).
Если точности Float32/Posit32 недостаточно, можно попробовать использовать Posit64. По идее они должны давать меньше шума чем Double даже на относительно высоких амплитудах. Очень интересно было бы увидеть аналогичные графики для них.
ProLimit
То есть формат хорош для чисел близких к нулевому порядку. Это свойство может быть полезно в некоторых задачах где примерно можно оценить пороядок величин или свести их к нулевому при хранении. Старый добрый float ведет себя более стабильно и предсказуемо, когда разброс величин неизвестен заранее.
ivlis
В численных расчётах всегда так делают, приводят все константы к «порядка единицы». Амплитуда 10^15 это какая-то экзотика всё же, даже трудно сказать где это может быть востребовано.
Refridgerator Автор
Я так понимаю, в физике порядок зависит от единиц измерения. Даже если просто фундаментальные физические постоянные посмотреть — там разброс величин от 10-44 до 1032.