iomanip — Значительные цифры в переполнении стека

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

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
double x = 0.12345678901234567890;
double y = 1.12345678901234567890;
cout << setprecision(15) << fixed << x << "\t" << y << "\n";
return 0;
}

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

0.123456789012346       1.123456789012346

Первое число имеет 15 сиг-фиг, а второе — 16. Что я могу сделать, чтобы решить эту проблему?

РЕДАКТИРОВАТЬ: меня специально попросили использовать setprecision, поэтому я не могу попробовать cout.precision.

2

Решение

Вы можете просто использовать научные (обратите внимание на 14 вместо 15):

std::cout << std::scientific << std::setprecision(14) << -0.123456789012345678 << std::endl;
std::cout << std::scientific << std::setprecision(14) << -1.234567890123456789 << std::endl;

-1.23456789012346e-01
-1.23456789012346e+00

или вы можете использовать функцию:

#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include <sstream>

enum vis_opt { scientific, decimal, decimal_relaxed };

std::string figures(double x, int nfig, vis_opt vo=decimal) {
std::stringstream str;

str << std::setprecision(nfig-1) << std::scientific << x;
std::string s = str.str();
if ( vo == scientific )
return s;
else {
std::stringstream out;
std::size_t pos;

int ileft = std::stoi(s,&pos);

std::string dec = s.substr(pos + 1, nfig - 1);
int e = std::stoi(s.substr(pos + nfig + 1));

if ( e < 0 ) {
std::string zeroes(-1-e,'0');
if ( ileft < 0 )
out << "-0." << zeroes << -ileft << dec;
else
out << "0." << zeroes << ileft << dec;
} else if ( e == 0) {
out << ileft << '.' << dec;
} else if ( e < ( nfig - 1) ) {
out << ileft << dec.substr(0,e) << '.' << dec.substr(e);
} else if ( e == ( nfig - 1) ) {
out << ileft << dec;
} else {
if ( vo == decimal_relaxed) {
out << s;
} else {
out << ileft << dec << std::string(e - nfig + 1,'0');
}
}

return out.str();
}

}

int main() {
std::vector<double> test_cases = {
-123456789012345,
-12.34567890123456789,
-0.1234567890123456789,
-0.0001234,
0,
0.0001234,
0.1234567890123456789,
12.34567890123456789,
1.234567890123456789,
12345678901234,
123456789012345,
1234567890123456789.0,
};for ( auto i : test_cases) {
std::cout << std::setw(22) << std::right << figures(i,15,scientific);
std::cout << std::setw(22) << std::right << figures(i,15) << std::endl;
}
return 0;
}

Мой вывод:

 -1.23456789012345e+14      -123456789012345
-1.23456789012346e+01     -12.3456789012346
-1.23456789012346e-01    -0.123456789012346
-1.23400000000000e-04 -0.000123400000000000
0.00000000000000e+00      0.00000000000000
1.23400000000000e-04  0.000123400000000000
1.23456789012346e-01     0.123456789012346
1.23456789012346e+01      12.3456789012346
1.23456789012346e+00      1.23456789012346
1.23456789012340e+13      12345678901234.0
1.23456789012345e+14       123456789012345
1.23456789012346e+18   1234567890123460000
3

Другие решения

Я нашел некоторый успех в простом вычислении целочисленных значащих цифр, а затем установил плавающие значащие цифры в X - <integer sig figs>:

редактировать

Чтобы ответить на комментарии Боба, я буду учитывать более крайние случаи. Я немного переработал код, чтобы настроить точность поля на основе начальных и конечных нулей. Я считаю, что для очень малых значений (например, std::numeric_limits<double>::epsilon:

int AdjustPrecision(int desiredPrecision, double _in)
{
// case of all zeros
if (_in == 0.0)
return desiredPrecision;// handle leading zeros before decimal place
size_t truncated = static_cast<size_t>(_in);

while(truncated != 0)
{
truncated /= 10;
--desiredPrecision;
}

// handle trailing zeros after decimal place
_in *= 10;
while(static_cast<size_t>(_in) == 0)
{
_in *= 10;
++desiredPrecision;
}

return desiredPrecision;
}

С большим количеством тестов:

double a = 0.000123456789012345;
double b = 123456789012345;
double x = 0.12345678901234567890;
double y = 1.12345678901234567890;
double z = 11.12345678901234567890;std::cout.setf( std::ios::fixed, std:: ios::floatfield);

std::cout << "a: " << std::setprecision(AdjustPrecision(15, a)) << a << std::endl;
std::cout << "b: " << std::setprecision(AdjustPrecision(15, b)) << b << std::endl;
std::cout << "x " << std::setprecision(AdjustPrecision(15, x))  << x << std::endl;
std::cout << "y " << std::setprecision(AdjustPrecision(15, y))  << y << std::endl;
std::cout << "z: " << std::setprecision(AdjustPrecision(15, z)) << z << std::endl;

Выход:

A: 0,000123456789012345
б: 123456789012345
х 0.123456789012346
у 1.12345678901235
z: 11.1234567890123

Live Demo

int GetIntegerSigFigs(double _in)
{
int toReturn = 0;
int truncated = static_cast<int>(_in);

while(truncated != 0)
{
truncated /= 10;
++toReturn;
}
return toReturn;
}

(Я уверен, что есть некоторые крайние случаи, которые я пропускаю)

А затем с помощью этого:

double x = 0.12345678901234567890;
double y = 1.12345678901234567890;
std::cout << td::setprecision(15-GetIntegerSigFigs(x))  << x
<< "\t" << std::setprecision(15-GetIntegerSigFigs(y)) << y << "\n";

Печать:

0.123456789012346 1.12345678901235

Live Demo

1

По вопросам рекламы [email protected]