Получение дробной части числа с плавающей точкой без использования modf () - PullRequest
22 голосов
/ 07 апреля 2010

Я разрабатываю для платформы без математической библиотеки, поэтому мне нужно создавать свои собственные инструменты. Мой текущий способ получения дроби - преобразовать число с плавающей точкой в ​​фиксированную точку (умножить на (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;
}

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

Ответы [ 9 ]

43 голосов
/ 07 апреля 2010

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

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

f = f-(long)f;

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

0.14159 = 3.14159 - 3;

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

7 голосов
/ 07 апреля 2010

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

4 голосов
/ 09 апреля 2010

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

4 голосов
/ 07 апреля 2010

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

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

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

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

2 голосов
/ 07 апреля 2010

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

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

Описание механизма хранения 32-битных чисел можно найти здесь .

1 голос
/ 09 декабря 2012

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

float f = something;
float fractionalPart = f - floor(f);
1 голос
/ 09 апреля 2010

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

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

0 голосов
/ 13 января 2018

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

0 голосов
/ 08 апреля 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...