Перейти к содержанию
    

Преобразование fixed point в строку

С преобразованием целой части все ясно. По поводу дробной пока размышляю так:

К примеру имеем Q8.8 = 1

Т.е. в строку надо вывести 1/256 т.е. 0,00390625

 

Как этого добиться? К примеру хотим вывести с точностью до 4 знаков.

FracAsInt = 1 * 10000 / 256 = 39 (операции целочисленные)

Мы потеряли два нуля, но если делать эту операцию в цикле, последовательно, сначала умножить на 10 потом на 100 и т.д. до нужного кол-ва знаков. Нули выловить таким образом можно.

 

Помимо общей корявости тут есть проблемка. А что если выводить придется Q0.32 до восемнадцатого знака после точки(ну предположим вот надо) так ведь можно и 64бита переполнить с таким алгоритмом...

 

Как это делается по правильному?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Как это делается по правильному?

 

Как надо правильно, не знаю. Я же сама алгоритм сочиняла. Получилось вот так:

 

void PushFix( long val, long divisor, unsigned char fix)
{
 ldiv_t temp;
 if( val >= 0)
temp = ldiv( val, divisor);
 else
 { temp = ldiv( -val, divisor);
temp.quot = -temp.quot;
 }
 PushLong( temp.quot);
 Push( '.');

 unsigned long base;
 switch( fix)
 { case 1: base=10; break;
case 2: base=100; break;
case 3: base=1000; break;
case 4: base=10000; break;
case 5: base=100000; break;
default: return;
 }

 char str[11];
 unsigned char len;
 val = temp.rem * base / divisor;
 ltoa( val, str, 10);
 len = strlen( str);
 while( len++ < fix) Push( '0');
 PushStr( str);
}

где:

PushFix( long val, long divisor, unsigned char fix); // числитель, знаменатель, число знаков после запятой (< 6)

PushLong(long); // печатает длинное целое

Push(char); // печатает символ

PushStr(char*); // печатает строку символов

(на самом деле они у меня не на печать выводятся, а сперва в буфер засоваются - отсюда и названия Push+).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А что если выводить придется Q0.32 до восемнадцатого знака после точки(ну предположим вот надо) так ведь можно и 64бита переполнить с таким алгоритмом..

В чём проблема? Умножаете на 10, выводите цифру, вычитаете её из числа, далее - след. цифра и т.д.

Как тут можно переполнить 64????

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Хмм, вычитаать...
Даже вычитать не надо - просто отбрасываете целую часть

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В общем вычитать что и куда я немного не понял... сделал так:

 

for( uint8_t i = 0; i < fracDigits; i++ )
    {
        T digit = frac * 10 / POW2F;
        fracBuf[i] = digit + '0';
        frac = frac * 10 % POW2F;
    }

Перемудрил?

 

На первой итерации frac = значение дробной части FixedPoint переменной, полученное из нее с помощью битовой маски.

fracDigits - требуемое кол-во знаков после точки.

fracBuf[] строка

POW2F - 2 в степени кол-во бит дробной части. т.е. для Q8.8 это будет 2^8

T - тип, метод шаблонный....

 

Для целой части всё стандартно, там ведь просто преобразование int в строку.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Что-ж тут непонятного???

//arg - в формате Q0.32
void f(u32 arg)
{
  if (!arg) return;
  putc('.');
  for (int i = 10; --i >= 0; arg = k) {
    u64 k = (u64)arg * 10;
    putc((u32)(k >> 32) + '0');
  }    
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну и k с каждой итерацией умножается на 10, всё это будет иметь всё те-же перспективы переполнения u64 о которых я говорил в первом посте....

 

Итого по сути для получения 10 знаков после точки нужно оперировать числами с десятью нулями.

 

И еще в приведенном коде arg = k наверное бомба замедленного действия ибо arg 32бита, а k - 64 :)

Надо подумать что будет если arg поступивший на вход функции был близок к максимальному значению 2^32 - 1?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

И еще в приведенном коде arg = k наверное бомба замедленного действия ибо arg 32бита, а k - 64 :)
Именно за счет этого и работает. И переполнения не возникнет. Вы умножили arg на 10. Старшие биты вылезли за пределы 32-битного числа. Вы их выделяете и выводите сдвигом на 32 вправо. Потом присвоили получившийся k обратно 32-битному числу, обрезав те самые выдвинувшиеся за 32 бита (и уже напечатанные) лишние разряды. И цикл повторяется снова.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну и k с каждой итерацией умножается на 10, всё это будет иметь всё те-же перспективы переполнения u64 о которых я говорил в первом посте....
Внимательнее на код посмотрите. Где Вы видите умножение k на 10????

 

И еще в приведенном коде arg = k наверное бомба замедленного действия ибо arg 32бита, а k - 64 :)
И что?

 

Надо подумать что будет если arg поступивший на вход функции был близок к максимальному значению 2^32 - 1?
Очевидно первая цифра после '.' будет '9'. B)

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Именно за счет этого и работает. И переполнения не возникнет. Вы умножили arg на 10. Старшие биты вылезли за пределы 32-битного числа. Вы их выделяете и выводите сдвигом на 32 вправо. Потом присвоили получившийся k обратно 32-битному числу, обрезав те самые выдвинувшиеся за 32 бита (и уже напечатанные) лишние разряды. И цикл повторяется снова.

Даа, похоже именно на этом всё и держится, действительно должно работать. А сразу и не сообразишь так сходу )

 

 

Внимательнее на код посмотрите. Где Вы видите умножение k на 10????

Сначала исходил из простой логики что если в цикле

arg = k

k = (u64)arg * 10;

 

То в общем то и arg и k в каждой итерации множаются на 10. Так сходу не очевидно что вы заставили переполнение работать на себя, но Сергей Борщ всё разъяснил.

Не знаю, как по мне - рабочий, но слегка запутанный код со спецэффектами.... Безусловно признак хакерского мастерства ))) Жму руку!

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А у меня статейка по этому поводу есть: http://we.easyelectronics.ru/Soft/preobraz...ey-tochkoy.html

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо, почитаю обязательно!

 

К слову, именно ваша статья про работу с портами на шаблонной магии подтолкнула меня начать вникать в эту самую магию и С++ в целом.

Так что даже двойное спасибо! :cheers:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В общем вычитать что и куда я немного не понял... сделал так:

 

for( uint8_t i = 0; i < fracDigits; i++ )
    {
        T digit = frac * 10 / POW2F;
        fracBuf[i] = digit + '0';
        frac = frac * 10 % POW2F;
    }

На случай, если где-нибудь еще потребуется частное и остаток для одинаковых делимого и делителя: в библиотеке stdlib есть замечательные функции div(), ldiv() и lldiv().

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Про эти функции слыхал, но не стал использовать т.к. это всё реализовано в шаблонном методе и тип T меняется от 8 до 32бит в зависимости от формата числа с фиксированной точкой.

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

Потом немного по оптимизирую, тем более статью neiver подбросил, может оттуда какие идеи позаимствую.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...