Pull to refresh

Comments 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%) — в зависимости от того, относительно чего сравниваем.

Кстати, если Вы заранее знаете порядки величин, то зачем плавающую запятую использовать и заморачиваться «правильным» сравнением, когда можно на фиксированной точке алгоритм реализовать?
UFO just landed and posted this here
Спасибо за материал, написано интересно.

Не много не понял вот этого момента
Например, 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
Sign up to leave a comment.

Articles