Простейшее форматирование плавающего в строку
От: Went  
Дата: 10.09.19 11:21
Оценка:
Доброго дня. Простите за ламерский вопрос, но все же
Мне нужно превратить стандартными средствами float в строку, чтобы соблюдались следующие условия:
1. Лишние нули не пишутся, лишняя точка тоже. 1.1000f -> "1.1", 4f -> "4"
2. Точность не больше двух знаков после запятой. 1.234 -> "1.23".
Все!
В С-шном формате есть %f и %g, но я нашел комбинации флагов, дающей нужного результата. Первый постоянно дописывает ненужные нули, даже если они не нужны, второе переводит в экспоненциальный формат, если число очень близко к нулю.
С++-ные варианты выдают подмножество С-шных, которые не устраивают.
Наверное, сейчас в меня полетят помидоры
Re: Простейшее форматирование плавающего в строку
От: Maniacal Россия  
Дата: 10.09.19 11:24
Оценка:
Здравствуйте, Went, Вы писали:

W>Доброго дня. Простите за ламерский вопрос, но все же

W>Мне нужно превратить стандартными средствами float в строку, чтобы соблюдались следующие условия:
W>1. Лишние нули не пишутся, лишняя точка тоже. 1.1000f -> "1.1", 4f -> "4"
W>2. Точность не больше двух знаков после запятой. 1.234 -> "1.23".

W>В С-шном формате есть %f и %g, но я нашел комбинации флагов, дающей нужного результата. Первый постоянно дописывает ненужные нули, даже если они не нужны, второе переводит в экспоненциальный формат, если число очень близко к нулю.

%.2f / %.2g?

Есть ещё набор функций gcvt/ecvt/fcvt, там нельзя указать сколько знаков после запятой должно быть, но функции gcvt и ecvt возвращают положение десятичной точки (указатель char*), отступив пару цифр и влепив туда '\0' можно обрезать до двух знаков.
Отредактировано 10.09.2019 11:31 Maniacal . Предыдущая версия .
Re[2]: Простейшее форматирование плавающего в строку
От: Went  
Дата: 10.09.19 11:29
Оценка:
Здравствуйте, Maniacal, Вы писали:
M>%.2f / %.2g?
W>>В С-шном формате есть %f и %g, но я нашел комбинации флагов, дающей нужного результата. Первый постоянно дописывает ненужные нули, даже если они не нужны, второе переводит в экспоненциальный формат, если число очень близко к нулю.
Я что-то неправильно пишу в строке форматирования?
Вариант %.2f всегда будет добавлять нули: 1.0f -> "1.00"
Вариант %.2g грешит экспонентами: 0.0000001f -> "1e-007p"
Re: Простейшее форматирование плавающего в строку
От: night beast СССР  
Дата: 10.09.19 11:32
Оценка:
Здравствуйте, Went, Вы писали:

W>В С-шном формате есть %f и %g, но я нашел комбинации флагов, дающей нужного результата. Первый постоянно дописывает ненужные нули, даже если они не нужны, второе переводит в экспоненциальный формат, если число очень близко к нулю.


а если близкое к нулю вручную округлить?
Re[2]: Простейшее форматирование плавающего в строку
От: Went  
Дата: 10.09.19 11:33
Оценка:
Здравствуйте, night beast, Вы писали:
NB>а если близкое к нулю вручную округлить?
Да, думаю, это будет работать. Просто, думал, есть стандартное решение.
Re[3]: Простейшее форматирование плавающего в строку
От: watchmaker  
Дата: 10.09.19 11:56
Оценка: 4 (1)
Здравствуйте, Went, Вы писали:

W>Вариант %.2f всегда будет добавлять нули: 1.0f -> "1.00"

Делаешь так, потом удаляешь из получившейся строки завершающие нули.
Под твои требования у printf-like функций или у iostream, действительно, нет нужной комбинации флагов форматирования.
Re[3]: Простейшее форматирование плавающего в строку
От: Maniacal Россия  
Дата: 10.09.19 12:03
Оценка:
Здравствуйте, Went, Вы писали:

W>Здравствуйте, night beast, Вы писали:

NB>>а если близкое к нулю вручную округлить?
W>Да, думаю, это будет работать. Просто, думал, есть стандартное решение.

Почти стандартное, с округлением, как предложили выше.

char acBuf[100];
float f = 10.0000001f;
sprintf(acBuf, "%.2g", int(f * 100) / 100.0);
Re[4]: Простейшее форматирование плавающего в строку
От: Went  
Дата: 10.09.19 12:33
Оценка:
Здравствуйте, Maniacal, Вы писали:

M>
M>char acBuf[100];
M>float f = 10.0000001f;
M>sprintf(acBuf, "%.2g", int(f * 100) / 100.0);
M>

Да, я так сначала и сделал, но оно стало превращать числа больше ста в экспоненту. Поэтому ".2" там не нужно, по-моему, просто "%g" достаточно.
Re[5]: Простейшее форматирование плавающего в строку
От: night beast СССР  
Дата: 10.09.19 12:42
Оценка:
Здравствуйте, Went, Вы писали:

W>Да, я так сначала и сделал, но оно стало превращать числа больше ста в экспоненту. Поэтому ".2" там не нужно, по-моему, просто "%g" достаточно.


а тебе для чего? если для рублей, то лучше в целыми работать..
Re: Простейшее форматирование плавающего в строку
От: rg45 СССР  
Дата: 10.09.19 13:24
Оценка:
Здравствуйте, Went, Вы писали:

W>Доброго дня. Простите за ламерский вопрос, но все же

W>Мне нужно превратить стандартными средствами float в строку, чтобы соблюдались следующие условия:
W>1. Лишние нули не пишутся, лишняя точка тоже. 1.1000f -> "1.1", 4f -> "4"
W>2. Точность не больше двух знаков после запятой. 1.234 -> "1.23".
W>Все!
W>В С-шном формате есть %f и %g, но я нашел комбинации флагов, дающей нужного результата. Первый постоянно дописывает ненужные нули, даже если они не нужны, второе переводит в экспоненциальный формат, если число очень близко к нулю.
W>С++-ные варианты выдают подмножество С-шных, которые не устраивают.

Может, стоит таки смириться с лишними нулями? Это действительно принципиально, или у тебя чисто спортивный интерес?
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 10.09.2019 13:29 rg45 . Предыдущая версия . Еще …
Отредактировано 10.09.2019 13:25 rg45 . Предыдущая версия .
Re: Простейшее форматирование плавающего в строку
От: rg45 СССР  
Дата: 10.09.19 14:55
Оценка: 5 (1)
Здравствуйте, Went, Вы писали:

W>Доброго дня. Простите за ламерский вопрос, но все же

W>Мне нужно превратить стандартными средствами float в строку, чтобы соблюдались следующие условия:
W>1. Лишние нули не пишутся, лишняя точка тоже. 1.1000f -> "1.1", 4f -> "4"
W>2. Точность не больше двух знаков после запятой. 1.234 -> "1.23".
W>Все!
W>В С-шном формате есть %f и %g, но я нашел комбинации флагов, дающей нужного результата. Первый постоянно дописывает ненужные нули, даже если они не нужны, второе переводит в экспоненциальный формат, если число очень близко к нулю.
W>С++-ные варианты выдают подмножество С-шных, которые не устраивают.

Ну если очень-очень нужно

https://ideone.com/uxF948

size_t digits_before_point(double x)
{
    return x ? size_t(std::max(0.0, floor(std::log(std::abs(x)) / std::log(10.0) + 1))) : 0;
}

std::string format(double x, size_t precision = 2)
{
    std::ostringstream output;
    output << std::setprecision(digits_before_point(x) + precision) << x;
    return output.str();
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Простейшее форматирование плавающего в строку
От: watchmaker  
Дата: 10.09.19 15:07
Оценка: 19 (2)
Здравствуйте, rg45, Вы писали:

R>Ну если очень-очень нужно :)


R>https://ideone.com/uxF948


Коду нужно больше юниттестов :)
Например, format(0.0625) выведет три символа после точки: 0.062. А format(1e-20) выведет в экспоненциальной форме 1e-20.
Re[3]: Исправленная версия
От: rg45 СССР  
Дата: 10.09.19 20:22
Оценка: 5 (1)
Здравствуйте, watchmaker, Вы писали:

R>>https://ideone.com/uxF948


W>Коду нужно больше юниттестов

W>Например, format(0.0625) выведет три символа после точки: 0.062. А format(1e-20) выведет в экспоненциальной форме 1e-20.

Ну вот, исправил: https://ideone.com/xGqir9

Самым сложным оказалось добиться, чтоб 0.005 выводилось как 0.01 (при точности 2).

std::string make_string(double x, int precision)
{
    std::ostringstream output;
    output << std::setprecision(precision) << x;
    return output.str();
}

int digits_before_point(double x)
{
    return x ? int(floor(std::log(std::abs(x)) / std::log(10.0) + 1)) : 0;
}

std::string format(double x, int precision = 2)
{
    const auto dbp = digits_before_point(x);
    if (dbp + precision > 0)
    {
        return make_string(x, dbp + precision);
    }
    else if (dbp + precision == 0)
    {
        const double min_value = std::exp(-precision * std::log(10));
        if (x >= min_value / 2)
        {
            return make_string(min_value, 1);
        }
        else if(x <= -min_value / 2)
        {
            return make_string(-min_value, 1);
        }
    }
    return "0";
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: Исправленная версия
От: watchmaker  
Дата: 10.09.19 22:40
Оценка: 7 (1)
Здравствуйте, rg45, Вы писали:

R>Ну вот, исправил: https://ideone.com/xGqir9

Всё равно иногда выводится по три символа после точки: например https://ideone.com/msG3Jo
Re: Простейшее форматирование плавающего в строку
От: c-smile Канада http://terrainformatica.com
Дата: 11.09.19 02:27
Оценка:
Здравствуйте, Went, Вы писали:


Как-то так (псевдо код)

int n = int(f * 100);


string s = itoa(n / 100);
int cents = n % 100;
if( cents ) {
  s += ".";
  s += itoa(cents / 10);
  if( cents % 10 ) 
    s += itoa(cents % 10);
}
Re[5]: Исправленная версия
От: rg45 СССР  
Дата: 11.09.19 04:46
Оценка: :)
Здравствуйте, watchmaker, Вы писали:

R>>Ну вот, исправил: https://ideone.com/xGqir9

W>Всё равно иногда выводится по три символа после точки: например https://ideone.com/msG3Jo

Задокументируем как фичу.

Ну а если серьезно, то понятно, что происходит. Фактический параметр: 99999999999999.90625 — мантисса слишком велика, что вызывает ожидаемую погрешность в математических выражениях. А так как число близко к целой степени десятки, функция digits_before_point выдает 15 вместо ожидаемых 14. Что поделаешь, все имеет свою погрешность и свою область применимости. Наверное, и это можно исправить, путем усложнения функции digits_before_point. Стоит ли игра свеч, зависит от строгости требований.

В то же время, судя потому, как тебе пришлось извратиться для получения нужного литерала, попасть в такую ситуацию не так-то просто
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 11.09.2019 6:20 rg45 . Предыдущая версия . Еще …
Отредактировано 11.09.2019 6:17 rg45 . Предыдущая версия .
Отредактировано 11.09.2019 6:16 rg45 . Предыдущая версия .
Отредактировано 11.09.2019 6:07 rg45 . Предыдущая версия .
Отредактировано 11.09.2019 6:05 rg45 . Предыдущая версия .
Отредактировано 11.09.2019 6:05 rg45 . Предыдущая версия .
Отредактировано 11.09.2019 5:52 rg45 . Предыдущая версия .
Отредактировано 11.09.2019 5:49 rg45 . Предыдущая версия .
Re[2]: Простейшее форматирование плавающего в строку
От: rg45 СССР  
Дата: 11.09.19 05:27
Оценка:
Здравствуйте, c-smile, Вы писали:


CS>Как-то так (псевдо код)


CS>
CS>int n = int(f * 100);
CS> . . .
CS>


И что получится, например, при f = 1e+10 ?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Вариант №2 (постпроцессинг строк)
От: rg45 СССР  
Дата: 11.09.19 07:27
Оценка: 5 (1)
Здравствуйте, Went, Вы писали:

W>Доброго дня. Простите за ламерский вопрос, но все же

W>Мне нужно превратить стандартными средствами float в строку, чтобы соблюдались следующие условия:
W>1. Лишние нули не пишутся, лишняя точка тоже. 1.1000f -> "1.1", 4f -> "4"
W>2. Точность не больше двух знаков после запятой. 1.234 -> "1.23".
W>Все!
W>В С-шном формате есть %f и %g, но я нашел комбинации флагов, дающей нужного результата. Первый постоянно дописывает ненужные нули, даже если они не нужны, второе переводит в экспоненциальный формат, если число очень близко к нулю.
W>С++-ные варианты выдают подмножество С-шных, которые не устраивают.

Вот другой подход к решению задачи: сформировать строку страндартными средствами, затем отбросить лишние нули. Косяки с потерей точности тоже можно поймать, но это косяки уже не мои

https://ideone.com/2iDTT4

std::string format(double x, int precision = 2)
{
    std::ostringstream output;
    output << std::fixed << std::setprecision(precision) << x;
    std::string s = output.str();

    if (precision > 0)
    {
        const auto pos = s.find_last_not_of('0');
        assert(pos != std::string::npos);        

        if (pos != s.size() - 1)
        {
            const auto count = s[pos] != '.' ? pos + 1 : pos;
            return s.substr(0, count);
        }
    }
    return s;
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 11.09.2019 7:41 rg45 . Предыдущая версия .
Re: Простейшее форматирование плавающего в строку
От: Videoman Россия https://hts.tv/
Дата: 11.09.19 07:34
Оценка: 16 (3)
Здравствуйте, Went, Вы писали:

W>Мне нужно превратить стандартными средствами float в строку, чтобы соблюдались следующие условия:

W>1. Лишние нули не пишутся, лишняя точка тоже. 1.1000f -> "1.1", 4f -> "4"
W>2. Точность не больше двух знаков после запятой. 1.234 -> "1.23".
W>Все!

std::to_chars() c флагом std::chars_format::fixed делает то, что тебе нужно.
Кстати делает это исключительно быстро, т.к. ей плевать на локаль и подобную ересь
Отредактировано 11.09.2019 7:39 Videoman . Предыдущая версия .
Re[2]: Простейшее форматирование плавающего в строку
От: rg45 СССР  
Дата: 11.09.19 08:21
Оценка:
Здравствуйте, Videoman, Вы писали:

W>>Мне нужно превратить стандартными средствами float в строку, чтобы соблюдались следующие условия:

W>>1. Лишние нули не пишутся, лишняя точка тоже. 1.1000f -> "1.1", 4f -> "4"
W>>2. Точность не больше двух знаков после запятой. 1.234 -> "1.23".
W>>Все!

V>std::to_chars() c флагом std::chars_format::fixed делает то, что тебе нужно.


Я попытался адаптировать пример здесь под требования задачи, но компилер ругается на std::chars_format почему-то

Я усомнился, а действительно ли он отбрасывает лишние нули. Ну, допустим, отбрасывает, тогда вопрос, как добиться такого же результата, как при работе с потоками и манипулятором std::fixed — то есть, чтоб нули НЕ отбрасывались?
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 11.09.2019 8:22 rg45 . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.