Re: Подскажите, как отформатировать float/double
От: kov_serg Россия  
Дата: 27.04.25 15:23
Оценка: 10 (1)
Здравствуйте, Marty, Вы писали:

M>Накидайте ещё идей, как это сделать попроще?


Фрагмент древнего говнокода из старого dsp-шника, (там штататный printf слишком много памяти в стеке жрал).
Поэтому был сгорожен printf с блекждежом и шахматистками. Вот самая мяготь
  не для слабонервных
#include <math.h>

enum {
    fmt_flg_ralign  =0x01,
    fmt_flg_showsign=0x02,
    fmt_flg_leading0=0x04,
    fmt_flg_short   =0x08,
    fmt_flg_long    =0x10,
    fmt_flg_upper   =0x20
};

#define BITS(x)  (8*sizeof(x))
//#define BITS(x)  (16*sizeof(x)) // for 16bit TI DSP C2406A
#define TYPE_MAX_DEC_DIGIT(bits) (((bits)*77+255)>>8)
enum { UINT_MAX_DEC_DIGIT=TYPE_MAX_DEC_DIGIT(BITS(unsigned)) };

void istrfmt_cnt(void* ctx,char c) { ++*((int*)ctx); }

int istrfmt_i(int x,int w1, char flg,void (*wc)(void* ctx,char c),void* ctx) {
    int i,len,len0; unsigned w; char c;
    if (x<0) { x=-x; c='-'; flg|=fmt_flg_showsign; } else { c='+'; }
    w=1; len=UINT_MAX_DEC_DIGIT; for(i=1;i<len;++i) { w*=10; if ((unsigned long)x<w) len=i; }
    len0=len; if (flg&fmt_flg_showsign) len++; 
    if (  flg&fmt_flg_ralign ) {
        if (flg&fmt_flg_leading0) {
            if (flg&fmt_flg_showsign) wc(ctx,c);
            for(i=w1-len;i>0;--i) wc(ctx,'0');
        } else {
            for(i=w1-len;i>0;--i) wc(ctx,' ');
            if (flg&fmt_flg_showsign) wc(ctx,c);
        }
    } else {
        if (flg&fmt_flg_showsign) wc(ctx,c);
    }
    for(i=len0;i>0;--i) {
        if (i!=UINT_MAX_DEC_DIGIT) w/=10;
        c=(unsigned)x/w; x-=c*w; wc(ctx,'0'+c);
    }
    if (!(flg&fmt_flg_ralign)) { for(i=w1-len;i>0;--i) wc(ctx,' '); }
    return len>w1?len:w1;
}

static int istrfmt_check_nan(double x,int w1,char flg,void (*wc)(void* ctx,char c),void* ctx) {
    char c='+'; int len=0;
    if (x!=x) {
        if (w1>0 && flg&fmt_flg_ralign) { for(int i=3;i<w1;i++) wc(ctx,' '); }
        if (flg&fmt_flg_upper) { wc(ctx,'N'); wc(ctx,'A'); wc(ctx,'N'); }
        else { wc(ctx,'n'); wc(ctx,'a'); wc(ctx,'n'); }
        if (w1>0 && !(flg&fmt_flg_ralign)) { for(int i=3;i<w1;i++) wc(ctx,' '); }
        len=3; if (w1>len) len=w1;
        return len;
    }
    if (x<0) { x=-x; c='-'; }
    if (x>1 && x*x==x) {
        if (w1>0 && flg&fmt_flg_ralign) { for(int i=4;i<w1;i++) wc(ctx,' '); }
        wc(ctx,c);
        if (flg&fmt_flg_upper) { wc(ctx,'I'); wc(ctx,'N'); wc(ctx,'F'); }
        else { wc(ctx,'i'); wc(ctx,'n'); wc(ctx,'f'); }
        if (w1>0 && !(flg&fmt_flg_ralign)) { for(int i=4;i<w1;i++) wc(ctx,' '); }
        len=4; if (w1>len) len=w1;
    }
    return len;
}

int istrfmt_f(double x,int w1,int w2,char flg,void (*wc)(void* ctx,char c),void* ctx) {
    int i,len,exp; char c;
    if (w2==0) w2=6; // if not specified
    len=istrfmt_check_nan(x,w1,flg,wc,ctx); if (len) return len;
    if (x<0 || 1/x<0) { c='-'; x=-x; flg|=fmt_flg_showsign; } else c='+';
    exp=0; len=flg&fmt_flg_showsign?1:0;
    if (x!=0) {
        if (w2>0) { double w;
            w=0.50001;
            for(i=w2;i>0;--i) w/=10;
            x+=w;
        }
        while(x>=10.0) { x/=10; exp++; }
    }
    if (w2>0) len+=1+w2;
    len+=exp+1;
    if (  flg&fmt_flg_ralign ) for(i=w1-len;i>0;--i) wc(ctx,' ');
    if (flg&fmt_flg_showsign) wc(ctx,c);
    c=(char)floor(x);wc(ctx,'0'+c);
    for(;exp>0;--exp) {
        x-=c; x*=10;
        c=(char)floor(x);wc(ctx,'0'+c);
    }
    if (w2>0) {
        wc(ctx,'.');
        for(i=0;i<w2;++i) {
            x-=c; x*=10;
            c=(char)floor(x);wc(ctx,'0'+c);
        }
    }
    if (!(flg&fmt_flg_ralign)) for(i=w1-len;i>0;--i) wc(ctx,' ');
    return len>w1?len:w1;
}

int istrfmt_e(double x,int w1,int w2,char flg,void (*wc)(void* ctx,char c),void* ctx) {
    int i,len,exp; char c;
    enum { exp_len=3,exp_flg=fmt_flg_showsign|fmt_flg_leading0|fmt_flg_ralign };
    if (w2==0) w2=6; // if not specified
    len=istrfmt_check_nan(x,w1,flg,wc,ctx); if (len) return len;
    if (x<0 || 1/x<0) { c='-'; x=-x; flg|=fmt_flg_showsign; } else c='+';
    exp=0; len=flg&fmt_flg_showsign?1:0;
    if (x!=0) {
        while(x>1.0) { x/=10; exp++; }
        while(x>0 && x<1.0) { x*=10; exp--; }
        { double w;
            w=0.50001; i=-w2;
            for(;i<0;++i) w/=10;
            for(;i>0;--i) w*=10;
            x+=w;
        }
    }
    len+=3+w2;
    istrfmt_i(exp,exp_len,exp_flg,istrfmt_cnt,&len);
    if ( flg&fmt_flg_ralign ) for(i=w1-len;i>0;--i) wc(ctx,' ');
    if (flg&fmt_flg_showsign) wc(ctx,c);
    c=(char)floor(x);wc(ctx,'0'+c);
    if (w2>0) {
        wc(ctx,'.');
        for(i=0;i<w2;++i) {
            x-=c; x*=10;
            c=(char)floor(x);wc(ctx,'0'+c);
        }
    }
    wc(ctx,flg&fmt_flg_upper?'E':'e');
    istrfmt_i(exp,exp_len,exp_flg,wc,ctx);
    if (!(flg&fmt_flg_ralign)) for(i=w1-len;i>0;--i) wc(ctx,' ');
    return len>w1?len:w1;
}

int istrfmt_g(double x,int w1,int w2,char flg,void (*wc)(void* ctx,char c),void* ctx) {
    int i,len,zd,exp; char c,q; double w;
    enum { exp_len=3,exp_ll=-4,exp_flg=fmt_flg_showsign|fmt_flg_leading0|fmt_flg_ralign };
    if (w2==0) w2=6; // if not specified
    len=istrfmt_check_nan(x,w1,flg,wc,ctx); if (len) return len;
    if (x<0 || 1/x<0) { c='-'; x=-x; flg|=fmt_flg_showsign; } else c='+';
    zd=1;exp=0;len=flg&fmt_flg_showsign?1:0;
    if (x!=0.0) {
        while(x>1.0) { x/=10; exp++; }
        while(x>0 && x<1.0) { x*=10; exp--; }
        w=5.001; i=-w2;
        for(;i<0;++i) w/=10;
        for(;i>0;--i) w*=10;
        x+=w;
        w=x; q=(char)floor(w); for(i=1;i<w2;++i) { w-=q; w*=10; q=(char)floor(w); if (q!=0) zd=1+i; }
    }
    if (exp<exp_ll || exp+1>w2) {
        len+=zd+2; // d.e
        istrfmt_i(exp,exp_len,exp_flg,istrfmt_cnt,&len);
    } // exp form
    else {
        if (exp+1==zd) len+=zd; // 123
        else if (exp>=0) len+=(zd<exp+1?exp:zd)+1; // 1.23
        else len+=zd+1-exp; // 0.0123
    } // normal form
    if (  flg&fmt_flg_ralign ) for(i=w1-len;i>0;--i) wc(ctx,' ');
    if (flg&fmt_flg_showsign) wc(ctx,c);
    if (exp<exp_ll || exp+1>w2) { // exp
        for(i=0;i<zd;++i) { // 1.23e+45
            c=(char)floor(x); x-=c; x*=10;
            if (i==1) wc(ctx,'.');
            wc(ctx,'0'+c);
        }
        wc(ctx,flg&fmt_flg_upper?'E':'e');
        istrfmt_i(exp,exp_len,exp_flg,wc,ctx);
    } else { // norm
        if (exp+1==zd) { // 123
            for(i=0;i<zd;++i) {
                c=(char)floor(x); x-=c; x*=10;
                wc(ctx,'0'+c);
            }
        } else if (exp>=0) { // 1.23
            for(i=0;i<zd || i<=exp;++i) {
                c=(char)floor(x); x-=c; x*=10;
                if (i-1==exp) wc(ctx,'.');
                wc(ctx,'0'+c);
            }
        } else { // 0.0123
            wc(ctx,'0');wc(ctx,'.');
            for(i=-1;i>exp;--i) wc(ctx,'0');
            for(i=0;i<zd;++i) {
                c=(char)floor(x); x-=c; x*=10;
                wc(ctx,'0'+c);
            }
        }
    }
    if (!(flg&fmt_flg_ralign)) for(i=w1-len;i>0;--i) wc(ctx,' ');
    return len>w1?len:w1;
}
В общем случае не самый эффективный алгоритм, можно использовать более эффективный логарифм, но оно работало вроде нормально.
#include <stdio.h>

void stdout_wc(void*,char c) { putc(c,stdout); }

int main(int argc, char const *argv[]) {
    double nan=sqrt(-1), inf=1e300; inf*=inf; double x=M_PI;
    int flg=fmt_flg_ralign|fmt_flg_upper;

    istrfmt_f(x,10,4,flg,stdout_wc,0); printf(" %10.4f\n",x);
    istrfmt_e(x,10,4,flg,stdout_wc,0); printf(" %10.4e\n",x);
    istrfmt_g(x,10,4,flg,stdout_wc,0); printf(" %10.4g\n",x);

    printf("---\n");
    x=inf;  istrfmt_g(x,10,4,flg,stdout_wc,0); printf(" %10.4g\n",x);
    x=-inf; istrfmt_g(x,10,4,flg,stdout_wc,0); printf(" %10.4g\n",x);
    x=nan;  istrfmt_g(x,10,4,flg,stdout_wc,0); printf(" %10.4g\n",x);
    x=0;    istrfmt_g(x,10,4,flg,stdout_wc,0); printf(" %10.4g\n",x);
    x=-x;   istrfmt_g(x,10,4,flg,stdout_wc,0); printf(" %10.4g\n",x);
    return 0;
}

    3.1416     3.1416
3.1416E+00 3.1416e+00
     3.142      3.142
---
      +INF        inf
      -INF       -inf
       NAN       -nan
         0          0
        -0         -0
Отредактировано 27.04.2025 18:38 kov_serg . Предыдущая версия .
Подскажите, как отформатировать float/double
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 27.04.25 14:09
Оценка:
Здравствуйте!

Нужно отформатировать float/double/long double в соответствии со спецификаторами std::format a/A/e/E/f/F/g/G.

AlexGin
Автор: AlexGin
Дата: 17.04 08:40
кидал ссылку на либу, совместимую с std::format, я там глянул — что-то там не слишком просто, хотелось бы попроще. Сильно углубляться в вопросы с вещественными числами не особо хочется, но результат хотелось бы получать корректный.

В спеке по std::format что like std::to_chars(...), но что-то не очень понятно.

Пока думаю тупо сформировать форматную строку, отформатировать число в буфер при помощи std::sprintf, потом строку обработать самому, добавив разделители разрядов и заменив при необходимости десятичную точку на локализованную.

Накидайте ещё идей, как это сделать попроще?
Маньяк Робокряк колесит по городу
Re: Подскажите, как отформатировать float/double
От: Pzz Россия https://github.com/alexpevzner
Дата: 27.04.25 17:27
Оценка:
Здравствуйте, Marty, Вы писали:

M>Нужно отформатировать float/double/long double в соответствии со спецификаторами std::format a/A/e/E/f/F/g/G.


А чем тебе fcvt/ecvt/gcvt плохи? Ну или там, strfromd (не знаю, есть ли она не венде)?
Re[2]: Подскажите, как отформатировать float/double
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 27.04.25 17:34
Оценка:
Здравствуйте, Pzz, Вы писали:

M>>Нужно отформатировать float/double/long double в соответствии со спецификаторами std::format a/A/e/E/f/F/g/G.


Pzz>А чем тебе fcvt/ecvt/gcvt плохи? Ну или там, strfromd (не знаю, есть ли она не венде)?


Зависят от локали, как минимум. Хз, что там с многопоточностью — форматируют во внутренний буфер
Маньяк Робокряк колесит по городу
Re[3]: Подскажите, как отформатировать float/double
От: Pzz Россия https://github.com/alexpevzner
Дата: 27.04.25 19:39
Оценка:
Здравствуйте, Marty, Вы писали:

Pzz>>А чем тебе fcvt/ecvt/gcvt плохи? Ну или там, strfromd (не знаю, есть ли она не венде)?


M>Зависят от локали, как минимум. Хз, что там с многопоточностью — форматируют во внутренний буфер


Ты не просил без локали. Ты просил, как std::format

Вообще, непонятно, что ты хочешь. В частности, непонятно, чем тебе sprintf не угодил...
Re[4]: Подскажите, как отформатировать float/double
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 27.04.25 20:06
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Ты не просил без локали. Ты просил, как std::format


Раз уж ты решил позанудствовать, то и я позанудствую: std::format без локали форматирует


Pzz>Вообще, непонятно, что ты хочешь. В частности, непонятно, чем тебе sprintf не угодил...


Тоже использует локаль, как минимум. Плюс — надо форматную строку ещё формировать.

Кстати, нигде не нашел вгятного описания, самое внятное если погуглить "MSVC _fcvt"

Есть ли вариант точность явно не задавать, а оставить на усмотрение функции?
Маньяк Робокряк колесит по городу
Re[5]: Подскажите, как отформатировать float/double
От: Pzz Россия https://github.com/alexpevzner
Дата: 27.04.25 20:54
Оценка:
Здравствуйте, Marty, Вы писали:

Pzz>>Вообще, непонятно, что ты хочешь. В частности, непонятно, чем тебе sprintf не угодил...


M>Тоже использует локаль, как минимум. Плюс — надо форматную строку ещё формировать.


Я все-таки не могу понять, что ты хочешь. В частности, тебе локаль нужна или нет?

M>Кстати, нигде не нашел вгятного описания, самое внятное если погуглить "MSVC _fcvt"


The ecvt() function converts number to a null-terminated string of ndig‐
its digits (where ndigits is reduced to a system-specific limit deter‐
mined by the precision of a double), and returns a pointer to the
string. The high-order digit is nonzero, unless number is zero. The
low order digit is rounded. The string itself does not contain a deci‐
mal point; however, the position of the decimal point relative to the
start of the string is stored in *decpt. A negative value for *decpt
means that the decimal point is to the left of the start of the string.
If the sign of number is negative, *sign is set to a nonzero value, oth‐
erwise it is set to 0. If number is zero, it is unspecified whether
*decpt is 0 or 1.


Есть еще ecvt_r и т.п., им дают буфер, куда отложить строку. Не знаю, есть ли аналоги в венде.

M>Есть ли вариант точность явно не задавать, а оставить на усмотрение функции?


Меня обычно бесит, как printf форматирует плавучку, и я делаю руками switch по диапазонам, с шагом 1, 10, 100, 1000, ... и т.д. (в обе стороны) и индивидуально подгоняю удобный формат под каждый диапазон.
Re[6]: Подскажите, как отформатировать float/double
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 27.04.25 21:09
Оценка:
Здравствуйте, Pzz, Вы писали:

M>>Тоже использует локаль, как минимум. Плюс — надо форматную строку ещё формировать.


Pzz>Я все-таки не могу понять, что ты хочешь. В частности, тебе локаль нужна или нет?


Должно управляться — можно с локалью, можно без локали.


Pzz>Есть еще ecvt_r и т.п., им дают буфер, куда отложить строку. Не знаю, есть ли аналоги в венде.


Возможно, что есть. Я затычку под старый GCC решил сделать на этих функциях, а под GCC>=11 и под остальные компилеры — решил на std::to_chars


M>>Есть ли вариант точность явно не задавать, а оставить на усмотрение функции?


Pzz>Меня обычно бесит, как printf форматирует плавучку, и я делаю руками switch по диапазонам, с шагом 1, 10, 100, 1000, ... и т.д. (в обе стороны) и индивидуально подгоняю удобный формат под каждый диапазон.


Мне наплевать, как форматирует плавучку printf, мне надо сделать, чтобы результат был как у std::format.

Впрочем, глянул повнимательнее — там, если точность не задана, используется 6 знаков, так что режим авто не требуется
Маньяк Робокряк колесит по городу
Re[2]: Подскажите, как отформатировать float/double
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 27.04.25 23:44
Оценка:
Здравствуйте, Pzz, Вы писали:

M>>Нужно отформатировать float/double/long double в соответствии со спецификаторами std::format a/A/e/E/f/F/g/G.


Pzz>А чем тебе fcvt/ecvt/gcvt плохи? Ну или там, strfromd (не знаю, есть ли она не венде)?


Кстати, fcvt и ecvt иногда возвращают ошибку (первые два кейса), а ecvt — ещё иногда и лажу гонит (последний кейс)

https://godbolt.org/z/bxheovjba
Маньяк Робокряк колесит по городу
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.