Как стать автором
Обновить

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

Числа с фиксированной точкой в своих комментариях я отмечаю как QN.M, где N — разрядность числа, M — количество разрядов после запятой.
Получилось практически как в фортране. Там в форматах вывода используют такую же логику.

Думаю многим будет интересно, если расскажите как правильно поступать, если требуется сложить/умножить большое и маленькое число с какой-то точностью.
Не очень понял последний вопрос, если честно. То есть, возможно, понял неправильно.
Что значит «большое» и «маленькое» число в данном случае?
Разной разрядности или одной разрядности с сильно различающимися мантиссами?
Разрядности сильно отличаются. Мб в 10 раз и больше.
Это, видимо, из FPGA?
На «обычных» процессорах разрядности максимум в 16 раз сейчас отличаются (128 bit vs. 8 bit)
Если по сути вопроса, то есть уточняющий вопрос:
в Вашем FPGA вычислитель любой разрядностью оперирует или есть ограничения?
Т.е. он может перемножить числа максимальной требуемой разрядности и полностью сохранить результат в регистре?
Если да, то хитростей никаких нет.
в моей/матлабовской нотации запись выглядит так:
QN1.M1 x QN2.M2 = Q(N1 + N2).(M1 + M2), то есть имеем число с (N1 + N2) значащих бит с (M1 + M2) бит на дробную часть числа. Дальше следует округлить результат до желаемой точности.

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

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

С форматом плавающей точки как раз все гораздо определенней.
Как правило, процессорные модули для вычислений с плавающей точкой
реализуют стандарт IEEE 754, и имеют набор команд (F*) работающий с тремя типами данных длиной 4,8,10 байт (собственно: float, double, long double)
При портировании на разные ОС, проблем как раз с этими типами данных возникнуть не должно.

В ядре Linux, например, использование вычислений с плавающей запятой затруднено (требуется полное сохранение контекста FPU), да и вообще крайне не рекомендуется. А если придётся переносить уже написанный код на микроконтроллер, который в принципе не имеет FPU? Так что всё зависит от конкретного применения.
Насколько я понимаю вы собираетесь переносить код на Си,
а это значит, что вы будете использовать компилятор заточенный под ту или иную архитектуру,
который сам реализует описанные вами действия (я не беру в расчет компиляторы на Си, которые не знают что такое float и double).
В бытность разработчика приборной техники я переносил ГОСТовские алгоритмы для вычислений по газу с устройств на базе контроллеров PIC на ARM. Проблем небыло, коды с использованием типа float перенеслись и работали.

Про ядро сказать ничего не могу.

>… вы будете использовать компилятор заточенный под ту или иную архитектуру…
В тех случаях, когда сам микроконтроллер не поддерживает работу с плавающей запятой, используется программная эмуляция, так называемый soft-float. Да, оно скомпилируется и будет работать, но это применимо только в тех случаях, когда скорость вычислений особого значения не имеет. Понятно что fixed-point вычисления быстрее.
> Про ядро сказать ничего не могу.
А тут особо ничего нового и не скажешь. Вот что про это дело Линус Торвальдс говорил: lkml.indiana.edu/hypermail/linux/kernel/0405.3/1620.html
Основная мысль сводится к следующей фразе:
In other words: the rule is that you really shouldn't use FP in the kernel.
А что там делать плавающей запятой, кстати?
Просто интересно, сам не смотрел :)
Вообще, фиксированная точка — очень хорошо портируемое дело.
Гонять floating-point на PIC и ARM (до ARM с VFP) — издевательство :)
«Работали» и «битэкзектно» — разные вещи немножко. Наверное, удовлетворяли требованиям по точности? По скорости все хорошо было?
На TI не пробовали запускать? Числодробилки, а на плавающей точке не живут (только вот 67хх немножко выправил ситуацию).
Нагрузочного тестирования как такового не проводилось,
прибор отрабатывал раз в секунду, делал вычисления и все.
По точности проблем не было. По скорости, вобщем-то, по барабану,
на 8MHz pic16 более 90% времени ничего не делал.
Ну, мне сложно судить, насколько сложный у Вас алгоритм был реализован, но раз работало на 8МГц PIC16, то, видимо, не очень.
Я в свое время Герцелем где-то по 10-и полосам мощность 8кГц звукового сигнала в плавающей точке считал, 200МГц ARMv5E не хватало.
Ключевое слово в данном случае «как правило» :)
Модули вычислений с плавающей точкой на разных процессорах дают разные результаты. С различными опциями компиляции — тоже.
Что для Вас «портирование»? Если просто перенести, чтобы работало и давало похожий результат — то да, проблем не возникнет, если надо перенести код так, чтобы результат был битэкзактен, то проблем не оберешься. Попробуйте одинаковый код погонять на ARM и х86 в качестве упражнения.
Можно также пройти по ссылкам на другие топики в начале моей статьи — там рассказано и о «переносимости» плавающей запятой.
N.B. Вас никто не неволит фиксированную точку использовать :)
Все зависит от решаемых задач и целевых платформ. Плавающая запятая может быть быстрее и удобнее, но не всегда подходит для конкретной платформы.
Согласен с вами, особенно про битэкзактность.

Допустим, если вы считаете хеш для ЭЦП, то без нее никуда.
Однако, имеет ли она смысл, когда разговор идет о дробных числах?
(особенно близких к нолю или с кучей знаков после запятой)
Их некорректно сравнивать жестко f1 == f2, а неплохо бы делать |f1-f2| < Δ.
Дельта в этом случае как раз и позволит избавиться от особенностей округления и других индивидуальных настроек FPU на разных архитектурах.


Вот как раз на fixed-point корректно сравнивать f1 == f2 ;)
А на плавающей точке тогда уж |f1-f2| / |f1| < Δ или можно просто промахнуться с выбором допустимой погрешности. Т.е. имеем лишнее деление (умножение, если вправо перенести) на простой операции сравнения. Когда такты считаешь, оно не очень приятно.
Я не ас в метрологии… :) Но попробую:
Если это какой-то конкретный параметр
(вряд ли имеет смысл сравнивать промежуточные величины),
некая конкретная физическая или математическая величина,
то требования по точности должны быть определены в ТЗ
и это вряд ли будет величина относительная.
Если требования к приборам можно задавать в абсолютных величинах (иногда), и это вызвано вполне конкретными возможностями аппаратуры, то требования к вычислительным погрешностям стоит задавать именно в относительных величинах.
Просто пример:
Зададим абсолютный порог равным 1.
Тогда 1000 и 1001 примерно равны по вышеуказанному критерию, с относительной погрешностью 1e-3 (0.1%).
С другой стороны 2 и 1 тоже равны по вышеуказанному критерию, вот только относительная погрешность уже 1 (100%) или 0.5 (50%) — в зависимости от того, относительно чего сравниваем.

Кстати, если Вы заранее знаете порядки величин, то зачем плавающую запятую использовать и заморачиваться «правильным» сравнением, когда можно на фиксированной точке алгоритм реализовать?
Интересная статья, спасибо!
Спасибо за материал, написано интересно.

Не много не понял вот этого момента
Например, q32.15 * q16.4 = q48.19. Сразу видно, что для полного представления результата надо 48-бит. Во второй нотации запись выглядит как q16.15 * q12.4 = q28.19
<.blockquote>
Здесь для 16-тибитного числа вроде должно получиться q11.4, а не q12.4? Нет? Или я не правильно понял принцип 2-ой Q-нотации

И еще немного позанудствую.

В вычислительной математике дробные значения представляют в виде пары целых чисел (n, e): мантиссы и экспоненты.

Все же по-русски это мантисса и показатель степени.

Если экспонента переменная, записываемая в регистр и неизвестная при компиляции, (n, e) называют числом с плавающей запятой. Если экспонента известна заранее, (n, e) называют числом с фиксированной точкой.

ИМХО следует использовать один вариант выражения: либо точка, либо запятая.

Ну, я сразу извинился за «фиксированную точку» :)
По-русски, наверное, правильнее в обоих случаях «запятая» — у нас именно запятая разделяет целую и дробную части десятичных дробей.
1) Извините, нажал «Написать», не дописав ответ, когда ребенок проснулся/заплакал.
По оставшимся моментам:
q12.4 — ошибка, сейчас поправлю. С учетом знака правильно q11.4.
Со знаком, кстати, вообще путаница может возникнуть — приведенные мной нотации не предусматривают беззнаковых чисел.
2) Насчет «показателя степени» — достаточно часто встречается выражение «экспоненциальная запись числа» или «запись числа с экспонентой». Поэтому, не думаю, что прямо путаница возникнет, но на всякий случай внесу Ваше замечание в статью.
Согласен, просто для меня экспонента — это конкретная функция с показаелем e=2.718… :)
Я «плюсану», но, извините, буду дальше употреблять экспоненту.
Запись короче. И так слов больше, чем кода/формул :)
Извините, если я тупой вопрос задам, но не смог сходу нагуглить ответ на него.

Объясните, пожалуйста запись переменных:

int32_t a = 0x1000L; // q15: a = 0.125
int32_t b = 0x20000L; // q20: b = 0.125

Что за L в конце? И почему это получается число с одной и той же мантиссой?
L — длинная константа. Без неё по умолчанию компилятор может при трансляции обрезать константу до (int), который не везде 32-битный.
Q.15: 0x1000 / 2^15 = 0.125
Q.20: 0x20000 / 2^20 = 0.125
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.