• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Научите считать float

Urbas81

Member
Удалось пока выяснить что при передаче числа 0,999 в строку попадает 0,099, если добавить еще одну "9" 0,9999 в строку попадает 0,0099, сравнил два файла strnum, прикрепленный выше и скачанный раннее, было пару отличий но это никак не повлияло на результат, еще заменил в функции double на float, я так понимаю в esp он не поддерживается да и я работаю с float? но и это не дало результата, удалось добиться нормального отображения удалив в коде прибавления 0.5:
Код:
int n = ((int)(fract*10.0+0.0))/10;
теперь не работает округление, но вывод без искажений, в чем причина пока не пойму.:(
 
Удалось пока выяснить что при передаче числа 0,999 в строку попадает 0,099, если добавить еще одну "9" 0,9999 в строку попадает 0,0099, сравнил два файла strnum, прикрепленный выше и скачанный раннее, было пару отличий но это никак не повлияло на результат, еще заменил в функции double на float, я так понимаю в esp он не поддерживается да и я работаю с float? но и это не дало результата, удалось добиться нормального отображения удалив в коде прибавления 0.5:
Код:
int n = ((int)(fract*10.0+0.0))/10;
теперь не работает округление, но вывод без искажений, в чем причина пока не пойму.:(

Я позже гляну (сейчас некогда), скажу только, что в ESP double тоже работает, можно в функциях не менять, float в double компилятор конвертирует всегда сам (не важно какой они разрядности) и все функции лучше делать в double. А причина может быть (надо проверять, может я и ошибаюсь) в том, что в бинарном представлении 0.5 - это не ровно 0.5, понятно, что если вместо 0.5 0 написать, то округлять не будет.
 

nikolz

Well-known member
Удалось пока выяснить что при передаче числа 0,999 в строку попадает 0,099, если добавить еще одну "9" 0,9999 в строку попадает 0,0099, сравнил два файла strnum, прикрепленный выше и скачанный раннее, было пару отличий но это никак не повлияло на результат, еще заменил в функции double на float, я так понимаю в esp он не поддерживается да и я работаю с float? но и это не дало результата, удалось добиться нормального отображения удалив в коде прибавления 0.5:
Код:
int n = ((int)(fract*10.0+0.0))/10;
теперь не работает округление, но вывод без искажений, в чем причина пока не пойму.:(
функция для преобразования вещественного числа в строку
======================
int _ftoa(float y, int aft, char *s) //s -результат, aft - число цифр в дробной части
{ int i=0; int z=0;
if (y!=0.){
if (y<0) {y=-y; z=1;}
int ip = (int)y; int m=aft;
if (m<0) m=8; if (z==1) s[i++] = '-'; if (ip!=0) i=itoa(ip,s+i);
float fp = y - (float)ip;
if (m!=0){
s[i++] = '.'; while (m>0) { fp=fp*10; ip=(int)fp; s[i++] = ip+'0'; fp=fp-(float)ip; m--; }
}
} else s[i++]='0';
s = '\0';
return i;
}
 
функция для преобразования вещественного числа в строку
======================
Код:
int _ftoa(float y, int aft, char *s) //s -ðåçóëüòàò, aft - ÷èñëî öèôð â äðîáíîé ÷àñòè
{
int i=0; int z=0;
if (y!=0.)
  {
   if (y<0) {y=-y; z=1;}
   int ip = (int)y;
   int m=aft;
   if (m<0) m=8;
   if (z==1) s[i++] = '-';
   if (ip!=0) i=itoa(ip,s+i);
   float fp = y - (float)ip;
   if (m!=0)
    {
     s[i++] = '.';
     while (m>0)
      {
       fp=fp*10;
       ip=(int)fp;
       s[i++] = ip+'0';
       fp=fp-(float)ip;
       m--;
      }
    }
   } else s[i++]='0';
  s = '\0';
  return i;
}
Округления тут вообще не видно. Просто несколько знаков дробной части.
 
Я позже гляну (сейчас некогда), скажу только, что в ESP double тоже работает, можно в функциях не менять, float в double компилятор конвертирует всегда сам (не важно какой они разрядности) и все функции лучше делать в double. А причина может быть (надо проверять, может я и ошибаюсь) в том, что в бинарном представлении 0.5 - это не ровно 0.5, понятно, что если вместо 0.5 0 написать, то округлять не будет.
Таки ошибся, и 0.5 и 10.0 представляются точно, да и вообще на PC я не вижу этой проблемы. Исправил там одну мелочь с отрицательными числами, но проблема не в этом была. Попробую еще на ESP, но не знаю когда время будет.
 

nikolz

Well-known member
Округления тут вообще не видно. Просто несколько знаков дробной части.
тут нет округления это преобразование в строку
просто указываем сколько знач цифр и получаем строку
т е погрешность половина младшей цифры дроби.
 
тут нет округления это преобразование в строку
просто указываем сколько знач цифр и получаем строку т е погрешность половина младшей цифры дроби.
Без округления не интересно и совсем тривиально. Вот почему проблемы с моим кодом на ESP (на PC я их не вижу) не понятно, проверять это мне сейчас некогда, совсем другой проект на столе. У меня округление делает строчка

int n = ((int)(fract*10.0+0.5))/10.0;

просто замена ее на int n = (int)fract; сделает перевод в строку без округления.
 

nikolz

Well-known member
Без округления не интересно и совсем тривиально. Вот почему проблемы с моим кодом на ESP (на PC я их не вижу) не понятно, проверять это мне сейчас некогда, совсем другой проект на столе. У меня округление делает строчка

int n = ((int)(fract*10.0+0.5))/10.0;

просто замена ее на int n = (int)fract; сделает перевод в строку без округления.
а что такое "fract" ?
 
а что такое "fract" ?
Просто переменная, дробная часть.
Код:
//floating point double to string
int dtostr(char *str, double d, int decimals)
{
  int res = 0;
  str[0] = '\0';
  char minus = 0;
  if (d < 0)
   {
    d = -d;
    str+=add(str, '-');
    minus = 1;
   }
  int whole = (int)d;
  double fract = (d - whole);
  res += itos(str, whole);
  int wlen = res;
  res+=add(str, '.');
  while(decimals)
   {
    decimals--;
    fract *= 10.0;
    int n = ((int)(fract*10.0+0.5))/10.0;
    char c = '0'+n%10;
    res+=add(str, c);
   }
  while((res>wlen)&&((str[res-1]=='0')||(str[res-1]=='.'))) str[--res] = '\0';
  return res+minus;
}
 

nikolz

Well-known member
Просто переменная, дробная часть.
а если так поправить?
Код:
//floating point double to string
int  dtostr(char *str, double d, int dec)
{
char *ps=str;  *ps=0;
if (d <0)  { d=-d; *ps++='-';  *ps=0;  }
int n = (int)d;  
double fract =d - n;
itos(ps, n);
*ps='.';  ps++; *ps=0;
while(dec--) { fract*=10.0;  n = ((int)(fract*10.0+0.5))/10.0; *ps++='0'+n%10; }
while(ps>str && *ps=='0' ) {*ps--=0; }
if (*ps=='.') *ps=0;
return ps-str;  //возвращаем длину строки
}
 
а если так поправить?
Код:
//floating point double to string
int  dtostr(char *str, double d, int dec)
...
Вроде, тоже самое. У меня там еще куча функций есть для работы с числами и строками. В принципе, я почти сделал свой sprintf (с инженерным форматом и без использования внешних либ), осталось немного закончить - и тут появились другие дела, и он так и ждет, тем более, что никуда вот прямо сейчас не нужен. В том проекте, для которого я это делал, используется как раз только инженерный формат, там принципиально выводить с указанием порядка, причем буквой. Для всех реальных данных это самое удобное представление.
 

nikolz

Well-known member
Вроде, тоже самое. У меня там еще куча функций есть для работы с числами и строками. В принципе, я почти сделал свой sprintf (с инженерным форматом и без использования внешних либ), осталось немного закончить - и тут появились другие дела, и он так и ждет, тем более, что никуда вот прямо сейчас не нужен. В том проекте, для которого я это делал, используется как раз только инженерный формат, там принципиально выводить с указанием порядка, причем буквой. Для всех реальных данных это самое удобное представление.
У Вас использованы Ваши функции вставки. Я их заменил на запись по косвенному адресу.
Так быстрее.
еще у Вас по-моему неудачно решена ФУНКЦИЯ ПРЕОБРАЗОВАНИЯ ЦЕЛОГО В СТРОКУ.
Вы в ней используете вашу функцию вставки в начало
При этом вы сдвигаете всю строку при каждой вставке. Это лишняя трата времени.
проще выделить 16 байт для массива поместить туда все цифры а потом их разом записать в строку.
--------------------
Для float интереснее и быстрее сделать с распаковкой мантиссы и порядка.
 
У Вас использованы Ваши функции вставки. Я их заменил на запись по косвенному адресу.
Так быстрее.
еще у Вас по-моему неудачно решена ФУНКЦИЯ ПРЕОБРАЗОВАНИЯ ЦЕЛОГО В СТРОКУ.
Вы в ней используете вашу функцию вставки в начало
При этом вы сдвигаете всю строку при каждой вставке. Это лишняя трата времени.
проще выделить 16 байт для массива поместить туда все цифры а потом их разом записать в строку.
--------------------
Для float интереснее и быстрее сделать с распаковкой мантиссы и порядка.
Возможно, но терминальный (или еще какой-то текстовый) вывод по-любому достаточно громоздкий и медленный, чтобы экономия тактов имела смысл, я особо этим не заморачивался. Единственно, старался поменьше использовать стандартную библиотеку, просто потому, что на мелких встраиваемых платформах они часто непонятно обрезаны, или наоборот тянут с собой слишком много лишнего.
 

Urbas81

Member
Без округления не интересно и совсем тривиально. Вот почему проблемы с моим кодом на ESP (на PC я их не вижу) не понятно, проверять это мне сейчас некогда, совсем другой проект на столе. У меня округление делает строчка

int n = ((int)(fract*10.0+0.5))/10.0;

просто замена ее на int n = (int)fract; сделает перевод в строку без округления.
Я в общем так и сделал, пока меня это устраивает.

Нашел еще такой вариант
Код:
int n_tu(int number, int count)
{
    int result = 1;
    while(count-- > 0)
        result *= number;

    return result;
}


void float_to_string(float f, char *r, uint8 len)
{
    long int length, length2, i, number, position, sign;
    float number2;

    sign = -1;   // -1 == positive number
    if (f < 0)
    {
        sign = '-';
        f *= -1;
    }

    number2 = f;
    number = f;
    length = 0;  // Size of decimal part
    length2 = len; // Size of tenth


    // Calculate length2 tenth part
    while( (number2 - (float)number) != 0.0 && !((number2 - (float)number) < 0.0) )
    {
         number2 = f * (n_tu(10, length2 + 1));
         number = number2;

         length2++;
    }




    // Calculate length decimal part
    for (length = (f > 1) ? 0 : 1; f > 1; length++)
        f /= 10;

    position = length;
    length = length + 1 + length2;
    number = number2;
    if (sign == '-')
    {
        length++;
        position++;
    }

    for (i = length; i >= 0 ; i--)
    {
        if (i == (length))
            r[i] = '\0';
        else if(i == (position))
            r[i] = '.';
        else if(sign == '-' && i == 0)
            r[i] = '-';
        else
        {
            r[i] = (number % 10) + '0';
            number /=10;
        }
    }
}
 

nikolz

Well-known member
Возможно, но терминальный (или еще какой-то текстовый) вывод по-любому достаточно громоздкий и медленный, чтобы экономия тактов имела смысл, я особо этим не заморачивался. Единственно, старался поменьше использовать стандартную библиотеку, просто потому, что на мелких встраиваемых платформах они часто непонятно обрезаны, или наоборот тянут с собой слишком много лишнего.
чтобы выполнить округление не надо мудрить в преобразовании.
Можно просто добавить к исходному числу половину младшего разряда точности отображения и потом сделать преобразования.
Например число положительное
надо отображать 3 цифры после точки,
то к числу прибавляем 0.0005 и результат отображаем с точность 3 цифры.
например
0.999 прибавляем 0.0005 результат 0.9995 в строке 0.999
или
0.9996 прибавляем 0.005 результат 1.000 в строке 1.0
-----------------------------
т е округление не влияет на алгоритм преобразования в строку.
 
чтобы выполнить округление не надо мудрить в преобразовании.
Можно просто добавить к исходному числу половину младшего разряда точности отображения и потом сделать преобразования.
Ну так строка n = ((int)(fract*10.0+0.5))/10.0; это и делает.
 

nikolz

Well-known member
Ну так строка n = ((int)(fract*10.0+0.5))/10.0; это и делает.
По-вашему получается, что нет разницы
сделать
сложение
или
сложение+умножение+деление+преобразование в целое.
Круто!
---------------------------------------------------
гланды тоже можно через зад смотреть.
 

pvvx

Активный участник сообщества
Извращенцы? :)
Есть масса достаточно коротких и полных vsprintf, печатающих любые float, double и long long (64 бита).
pvvx/RTL0B_WEB
Из vsprintf собираются printf и sprintf.
Пример работы: https://esp8266.ru/forum/threads/ne-rabotajut-float-double-i-biblioteka-math.2485/#post-37634
Эта либа есть и адаптированная для ESP8266. Ищите по заголовку авторов в github.
Пример nodemcu/nodemcu-firmware

format specifiers in c:
List of all format specifiers in C programming - Codeforwin
 
Последнее редактирование:
По-вашему получается, что нет разницы
сделать сложение или сложение+умножение+деление+преобразование в целое.
Круто!
---------------------------------------------------
гланды тоже можно через зад смотреть.
Чтобы определить "половину младшего разряда точности" одним сложением не обойдешься.
 
Сверху Снизу