Получение дробной части поплавка без использования modf()
Я разрабатываю платформу без математической библиотеки, поэтому мне нужно создать собственные инструменты. Мой текущий способ получения фракции - преобразовать float в фиксированную точку (умножить на (float) 0xFFFF, cast to int), получить только нижнюю часть (маску с 0xFFFF) и снова преобразовать ее обратно в float.
Однако неточность убивает меня. Я использую функции 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;
}
Спасибо заранее!
Ответы
Ответ 1
Если я правильно понял ваш вопрос, вам просто нужна часть после десятичного правого? Вам это не нужно на самом деле во фракции (целочисленный числитель и знаменатель)?
Итак, у нас есть некоторое число, скажем 3.14159
, и мы хотим получить только 0.14159
. Предполагая, что наш номер хранится в float f;
, мы можем сделать это:
f = f-(long)f;
Что, если мы вставляем наш номер, работает следующим образом:
0.14159 = 3.14159 - 3;
То, что это делает, - это удаление целой числовой части поплавка, оставляющей только десятичную часть. Когда вы конвертируете float в длинный, он отбрасывает десятичную часть. Затем, когда вы вычитаете это из вашего исходного поплавка, вы остаетесь только с десятичной частью. Нам нужно использовать длинный номер из-за размера типа float
(8 байтов в большинстве систем). Целое число (всего 4 байта на многих системах) не обязательно достаточно велико, чтобы охватить тот же диапазон чисел, что и float
, но a long
должен быть.
Ответ 2
Как я и предполагал, modf
не использует арифметику как таковую - все сдвиги и маски, посмотрите здесь. Не можете ли вы использовать те же идеи на своей платформе?
Ответ 3
Я бы порекомендовал взглянуть на то, как modf реализован в системах, которые вы используете сегодня. Проверьте версию uClibc.
http://git.uclibc.org/uClibc/tree/libm/s_modf.c
(По юридическим причинам он, по-видимому, лицензирован BSD, но вы, очевидно, хотите дважды проверить)
Некоторые макросы определены здесь.
Ответ 4
Там есть ошибка в ваших константах. Вы в основном пытаетесь сделать левый сдвиг числа на 16 бит, замаскируйте все, кроме младших бит, а затем снова сдвиньте на 16 бит снова. Сдвиг - это то же самое, что умножение на мощность 2, но вы не используете мощность 2 - вы используете 0xFFFF, который выключен на 1. Замена этого 0x10000 заставит формулу работать по назначению.
Ответ 5
Я не совсем уверен, но я думаю, что то, что вы делаете, неверно, поскольку вы рассматриваете только мантиссу и полностью забываете об экспоненте.
Вам нужно использовать экспоненту для смещения значения в мантиссе, чтобы найти действительную целочисленную часть.
Для описания механизма хранения 32-битных поплавков посмотрите здесь.
Ответ 6
Зачем переходить к плавающей запятой вообще для чертежа вашей линии? Вы можете просто придерживаться своей версии с фиксированной точкой и использовать вместо этого строчную схему рисования на основе целых чисел/фиксированных точек - Брешенхам приходит на ум. Хотя эта версия не является псевдонимом, я знаю, что есть и другие.
Рисунок линии Bresenham
Ответ 7
Похоже, что вы этого хотите.
float f = something;
float fractionalPart = f - floor(f);
Ответ 8
Ваш метод предполагает, что в дробной части есть 16 бит (и как отмечает Mark Ransom, это означает, что вы должны сдвинуть на 16 бит, то есть умножить на 0x1000). Это может быть неверно. Показатель - это то, что определяет, сколько бит есть в дробной части.
Чтобы поместить это в формулу, ваш метод работает, вычисляя (x modf 1.0)
как ((x << 16) mod 1<<16) >> 16
, и это жестко закодированное 16, которое должно зависеть от экспоненты - точная замена зависит от вашего формата float.
Ответ 9
Один из вариантов - использовать fmod(x, 1)
.