it-swarm.com.ru

Получение дробной части числа с плавающей точкой без использования modf ()

Я разрабатываю для платформы без математической библиотеки, поэтому мне нужно создавать свои собственные инструменты. Мой текущий способ получения дроби состоит в том, чтобы преобразовать число с плавающей точкой в ​​фиксированную точку (умножить на (float) 0xFFFF, привести к int), получить только нижнюю часть (маска с 0xFFFF) и снова преобразовать ее в число с плавающей точкой.

Тем не менее, неточность убивает меня. Я использую свои функции Frac () и InvFrac () для рисования сглаженной линии. Используя modf, я получаю идеально плавную линию. С моим собственным методом пиксели начинают прыгать вокруг из-за потери точности.

Это мой код:

const float fp_amount = (float)(0xFFFF);
const float fp_amount_inv = 1.f / fp_amount;

inline float Frac(float a_X)
{
    return ((int)(a_X * fp_amount) & 0xFFFF) * fp_amount_inv;
}

inline float Frac(float a_X)
{
    return (0xFFFF - (int)(a_X * fp_amount) & 0xFFFF) * fp_amount_inv;
}

Заранее спасибо!

19
knight666

Если я правильно понимаю ваш вопрос, вы просто хотите часть после десятичного правильно? Вам не нужно на самом деле в дроби (целое число и знаменатель)?

Итак, у нас есть какое-то число, скажем 3.14159, и мы хотим в итоге просто 0.14159. Предполагая, что наш номер хранится в float f;, мы можем сделать это:

f = f-(long)f;

Который, если мы вставим наш номер, работает так:

0.14159 = 3.14159 - 3;

Что это делает, так это удаляет целую часть числа с плавающей точкой, оставляя только десятичную часть. Когда вы конвертируете число с плавающей точкой в ​​длинное, оно отбрасывает десятичную часть. Затем, когда вы вычтете это из исходного числа с плавающей запятой, у вас останется только десятичная часть. Нам нужно использовать long здесь из-за размера типа float (8 байт в большинстве систем). Целое число (всего 4 байта во многих системах) не обязательно достаточно велико, чтобы охватить тот же диапазон чисел, что и float, но long должен быть.

40
Daniel Bingham

Как я и подозревал, modf не использует никакой арифметики per se - это все смены и маски, посмотрите здесь . Разве вы не можете использовать те же идеи на своей платформе?

7
AVB

В ваших константах есть ошибка. По сути, вы пытаетесь выполнить сдвиг влево на 16 бит, замаскировать все, кроме младших, затем снова сдвиг вправо на 16 бит. Сдвиг аналогичен умножению на степень 2, но вы не используете степень 2 - вы используете 0xFFFF, который выключен на 1. Замена этого значения на 0x10000 заставит формулу работать так, как задумано.

4
Mark Ransom

Я бы рекомендовал взглянуть на то, как modf реализован в системах, которые вы используете сегодня. Проверьте версию uClibc.

http://git.uclibc.org/uClibc/tree/libm/s_modf.c

(По юридическим причинам он, по-видимому, лицензирован BSD, но вы, очевидно, захотите перепроверить)

Некоторые макросы определены здесь .

4
Bill Lynch

Я не совсем уверен, но я думаю, что то, что вы делаете, неправильно, так как вы рассматриваете только мантиссу и полностью забываете экспоненту. 

Вам нужно использовать показатель степени, чтобы сместить значение в мантиссе, чтобы найти действительную целочисленную часть.

Для описания механизма хранения 32-битных чисел, посмотрите здесь

2
Bruno Brant

Похоже, может быть, вы хотите этого.

float f = something;
float fractionalPart = f - floor(f);
1
Victor Engel

Зачем вообще идти к плавающей точке для рисования линий? Вы можете просто придерживаться своей версии с фиксированной точкой и использовать вместо этого процедуру рисования линий на основе целых/фиксированных точек - вспоминается Брезенхем. Хотя эта версия не имеет псевдонимов, я знаю, что есть и другие.

Рисование линии Брезенхэма

1
Michael Dorgan

Ваш метод предполагает, что в дробной части есть 16 бит (и, как отмечает Марк Рэнсом, это означает, что вы должны сдвинуться на 16 бит, то есть умножить на 0x1000). Это может быть не так. Показатель - это то, что определяет количество бит в дробной части.

Чтобы поместить это в формулу, ваш метод работает, вычисляя (x modf 1.0) как ((x << 16) mod 1<<16) >> 16, и именно эта кодированная цифра 16 должна зависеть от показателя степени - точная замена зависит от вашего формата с плавающей запятой.

0
MSalters

Одним из вариантов является использование fmod(x, 1).

0
emlai