• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе 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
 
Последнее редактирование:
По-вашему получается, что нет разницы
сделать сложение или сложение+умножение+деление+преобразование в целое.
Круто!
---------------------------------------------------
гланды тоже можно через зад смотреть.
Чтобы определить "половину младшего разряда точности" одним сложением не обойдешься.
 
Сверху Снизу