арифметика с фиксированной точкой в ​​современных системах

Я хотел бы начать с того, что речь идет не об оптимизации, поэтому, пожалуйста, воздержитесь от перетаскивания этой темы по этому пути. Моя цель использования арифметики с фиксированной запятой заключается в том, что я хочу контролировать точность своих вычислений без использования чисел с плавающей запятой.

С учетом сказанного давайте двигаться дальше. Я хотел иметь 17 бит для диапазона и 15 бит для дробной части. Дополнительный бит предназначен для значения со знаком. Вот несколько макросов ниже.

const int scl = 18;
#define Double2Fix(x) ((x) * (double)(1 << scl))
#define Float2Fix(x) ((x) * (float)(1 << scl))
#define Fix2Double(x) ((double)(x) / (1 << scl))
#define Fix2Float(x) ((float)(x) / (1 << scl))

Сложение и вычитание довольно просты, но с mul и div все становится немного сложнее.

Я видел два разных способа обработки этих двух типов операций. 1) если я использую 32 бита, используйте временную 64-битную переменную для хранения промежуточных шагов умножения, а затем масштабируйте в конце.

2) прямо на этапе умножения масштабируйте обе переменные до меньшего битового диапазона перед умножением. Например, если у вас есть 32-битный регистр с 16 битами для всего числа, вы можете сдвинуть его следующим образом:

(((a)>>8)*((b)>>6) >> 2) or some combination that makes sense for you app.

Мне кажется, что если вы разрабатываете математику с фиксированной точкой вокруг 32 бит, может быть непрактично всегда зависеть от наличия 64-битной переменной, способной хранить ваши промежуточные значения, но, с другой стороны, переход к более низкому масштабу серьезно уменьшит ваш диапазон и точность .

вопросы. Поскольку я хотел бы избежать попытки заставить процессор создавать 64-битный тип в середине моих вычислений, является ли переход на более низкие битовые значения единственной альтернативой?

Также я заметил

    int b = Double2Fix(9.1234567890);
    printf("double shift:%f\n",Fix2Double(b));

    int c = Float2Fix(9.1234567890);
    printf("float  shift:%f\n",Fix2Float(c));

    double shift:9.123444
    float  shift:9.123444

Является ли эта потеря точности частью использования чисел с фиксированной запятой?


person user1610950    schedule 26.07.2015    source источник
comment
Что вы имеете в виду под дальностью?   -  person pablo1977    schedule 26.07.2015
comment
17 + 15 уже 32. Похоже, вы хотите 1 + 16 + 15.   -  person Potatoswatter    schedule 26.07.2015
comment
Попробуйте printf с %.10f для отображения с плавающей запятой, тогда вы увидите все цифры. Также: docs.oracle.com/cd/E19957-01/ 806-3568 / ncg_goldberg.html   -  person Koshinae    schedule 26.07.2015
comment
@ pablo1977, если у меня 32 бита, и я использую 12.20, 12 - это диапазон, поэтому для беззнакового я могу представлять числа от 0 до 4096 или (1 * 2 ^ 12) для подписанных, я могу представлять от -2048 до 2047. Это часть диапазона. Опечатка с моей стороны Potatoswatter. Koshinae, что этот код также создает ошибки округления.   -  person user1610950    schedule 26.07.2015


Ответы (1)


Поскольку я хотел бы избежать попытки заставить ЦП попытаться создать 64-битный тип в середине моих вычислений, является ли переход на более низкие битовые значения единственной альтернативой?

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

  1. Умножение N x N => младшие N бит (собственное умножение C)
  2. Умножить N x N => старшие N бит (в языке C для этого нет оператора)
  3. Умножить N x N => все 2N бит (преобразовать в более широкий тип, затем умножить)

Если в наборе команд есть номер 3, и ЦП реализует его эффективно, то не нужно беспокоиться о сверхшироких результатах, которые он дает. Для x86 это можно в значительной степени принять как данность. Во всяком случае, вы сказали, что это не вопрос оптимизации :).

Придерживаясь только №1, вам нужно будет разбить операнды на части (N / 2) бит и выполнить длинное умножение, что, вероятно, потребует больше работы. Есть еще случаи, когда это правильно, например, реализация №3 (программная расширенная арифметика) на ЦП, у которого его нет, или №2.

Является ли эта потеря точности частью использования чисел с фиксированной запятой?

log2 (9.1234567890 - 9.123444) = –16,25, и вы использовали 16-битную точность, так что да, это очень типично.

person Potatoswatter    schedule 26.07.2015
comment
спасибо, это было очень полезно. Мне придется придерживаться минимального умножения N бит и убедиться, что я правильно справляюсь с переполнениями. - person user1610950; 26.07.2015
comment
Хотя оптимизация здесь не важна, может быть полезно знать, что она есть, если вы разделите два N-битных операнда на четыре N / 2-битных операнда для умножения, используя Алгоритм Карацубы уменьшит количество кратных с 4 до 3, но потребует еще нескольких сложений и вычитаний. Это несколько бессмысленно, если ваш процессор имеет очень быструю инструкцию умножения. - person rcgldr; 26.07.2015