Преобразование SIGNED дробей в UNSIGNED с фиксированной точкой для сложения и умножения
Как мы можем преобразовать числа с плавающей точкой в их "представления с фиксированной точкой" и использовать их "представления с фиксированной точкой" в операциях с фиксированной точкой, таких как сложение и умножение? Результат в операции с фиксированной запятой должен давать правильный ответ при преобразовании обратно в число с плавающей запятой.
Сказать:
(double)(xb_double) + (double)(xb_double) = ?
Затем мы конвертируем оба сложения в представление с фиксированной точкой (целое число),
(int)(xa_fixed) + (int)(xb_fixed) = (int) (xsum_fixed)
Чтобы получить (double)(xsum_double), мы конвертируем (int)(sum_fixed) обратно в число с плавающей запятой и получаем тот же ответ,
FixedToDouble(xsum_fixed) => xsum_double
В частности, если диапазон значений xa_double и xb_double находится между -1,65 и 1,65, я хочу преобразовать xa_double и xb_double в их соответствующие 10-битные представления с фиксированной точкой (от 0x0000 до 0x03FF)
ЧТО Я ПОПРОБОВАЛ
int fixed_MAX = 1023;
int fixed_MIN = 0;
double Value_MAX = 1.65;
double Value_MIN = -1.65;
double slope = ((fixed_MAX) - (fixed_MIN))/((Value_MAX) - (Value_MIN));
int DoubleToFixed(double x)
{
return round(((x) - Value_MIN)*slope + fixed_MIN); //via interpolation method
}
double FixedToDouble(int x)
{
return (double)((((x) + fixed_MIN)/slope) + Value_MIN);
}
int sum_fixed(int x, int y)
{
return (x + y - (1.65*slope)); //analysis, just basic math
}
int subtract_fixed(int x, int y)
{
return (x - y + (1.65*slope));
}
int product_fixed(int x, int y)
{
return (((x * y) - (slope*slope*((1.65*FixedToDouble(x)) + (1.65*FixedToDouble(y)) + (1.65*1.65))) + (slope*slope*1.65)) / slope);
}
И если я хочу добавить (double)(1.00) + (double)(2.00) =, который должен уступить (double)(3.00),
С моим кодом,
xsum_fixed = DoubleToFixed(1.00) + DoubleToFixed(2.00);
xsum_double = FixedToDouble(xsum_fixed);
Я получаю ответ:
xsum_double = 3.001613
Что очень близко к правильному ответу (двойной) (3,00)
Кроме того, если я выполняю умножение и вычитание, я получаю 2.004839 и -1.001613 соответственно.
ЗДЕСЬ ЗАМОК:
Итак, я знаю, что мой код работает, но как я могу выполнять сложение, умножение и вычитание в этих представлениях с фиксированной точкой, не имея внутренних операций с плавающей точкой и чисел.
Таким образом, в приведенном выше коде функции sum_fixed, product_fixed и subtract_fixed имеют внутренние числа с плавающей запятой (наклон и 1.65, 1.65 - вход MAX с плавающей запятой). Я вывел свой код по базовой математике, правда.
Поэтому я хочу реализовать функции сложения, вычитания и произведения без каких-либо внутренних операций или чисел с плавающей запятой.
ОБНОВИТЬ:
Я также нашел более простой код в преобразовании дробных чисел в фиксированную точку:
//const int scale = 16; //1/2^16 in 32 bits
#define DoubleToFixed(x) (int)((x) * (double)(1<<scale))
#define FixedToDouble(x) ((double)(x) / (double)(1<<scale))
#define FractionPart(x) ((x) & FractionMask)
#define MUL(x,y) (((long long)(x)*(long long)(y)) >> scale)
#define DIV(x, y) (((long long)(x)<<16)/(y))
Однако это преобразует только дроби UNSIGNED в UNSIGNED с фиксированной запятой. И я хочу преобразовать SIGNED-дроби (от -1,65 до 1,65) в UNSIGNED с фиксированной запятой (от 0x0000 до 0x03FF). Как я могу сделать это с использованием этого кода выше? Диапазон или количество битов как-то связано с процессом преобразования? Этот код только для положительных дробей?
кредиты @chux
1 ответ
Вы можете сделать так, чтобы мантисса представления вашего числа с плавающей точкой была равна его представлению с фиксированной точкой. Поскольку добавление FP сдвигает мантиссу меньшего операнда до тех пор, пока оба операнда не имеют одинаковый показатель степени, вы можете добавить определенное "магическое число", чтобы форсировать его. Для double это 1<< (точность 52) (52 - размер мантиссы double, точность - необходимое количество двоичных цифр точности). Таким образом, преобразование будет выглядеть так:
union { double f; long long i; } u = { xfloat+(1ll<<52-precision) }; // shift x's mantissa
long long xfixed = u.i & (1ll<<52)-1; // extract the mantissa
После этого вы можете использовать xfixed в целочисленной математике (для умножения вам придется сдвигать результат вправо на "точность"). Чтобы преобразовать его обратно в удвоение, просто умножьте его на 1,0/(1 << точность);
Обратите внимание, что он не обрабатывает негативы. Если они вам нужны, вам нужно будет вручную преобразовать их в дополнительное представление (сначала добавьте двойное число, а затем отрицайте результат int, если входное значение было отрицательным).