Hoe druk ik een dubbele waarde met volledige precisie af met cout?

In mijn vroegere vraagwas ik een doublemet behulp van coutdie werd afgerond toen ik het niet verwachtte. Hoe kan ik couteen doublelaten afdrukken met volledige precisie?


Antwoord 1, autoriteit 100%

Je kunt de precisie rechtstreeks instellen op std::couten de std::fixedformaatspecificatie.

double d = 3.14159265358979;
cout.precision(17);
cout << "Pi: " << fixed << d << endl;

Je kunt #include <limits>gebruiken om de maximale precisie van een float of double te krijgen.

#include <limits>
typedef std::numeric_limits< double > dbl;
double d = 3.14159265358979;
cout.precision(dbl::max_digits10);
cout << "Pi: " << d << endl;

Antwoord 2, autoriteit 18%

Gebruik std::setprecision:

#include <iomanip>
std::cout << std::setprecision (15) << 3.14159265358979 << std::endl;

Antwoord 3, autoriteit 6%

Dit is wat ik zou gebruiken:

std::cout << std::setprecision (std::numeric_limits<double>::digits10 + 1)
          << 3.14159265358979
          << std::endl;

In principe heeft het limietenpakket eigenschappen voor alle ingebouwde typen.
Een van de eigenschappen voor getallen met drijvende komma (float/double/long double) is het digits10-attribuut. Dit definieert de nauwkeurigheid (ik ben de exacte terminologie vergeten) van een getal met drijvende komma in grondtal 10.

Zie: http://www.cplusplus.com/reference/std/ limieten/numeric_limits.html
Voor details over andere kenmerken.


Antwoord 4, autoriteit 3%

De manier van iostreams is nogal onhandig. Ik gebruik liever boost::lexical_castomdat het berekent voor mij de juiste precisie. En het is ook snel.

#include <string>
#include <boost/lexical_cast.hpp>
using boost::lexical_cast;
using std::string;
double d = 3.14159265358979;
cout << "Pi: " << lexical_cast<string>(d) << endl;

Uitvoer:

Pi: 3.14159265358979


Antwoord 5, autoriteit 2%

Hier leest u hoe u een dubbel met volledige precisie weergeeft:

double d = 100.0000000000005;
int precision = std::numeric_limits<double>::max_digits10;
std::cout << std::setprecision(precision) << d << std::endl;

Dit toont:

100.0000000000005

max_digits10 is het aantal cijfers dat nodig is om alle afzonderlijke dubbele waarden op unieke wijze weer te geven. max_digits10 staat voor het aantal cijfers voor en achter de komma.

Gebruik set_precision(max_digits10) niet met std::fixed.
Bij vaste notatie stelt set_precision() het aantal cijfers alleen nade komma in. Dit is onjuist omdat max_digits10 staat voor het aantal cijfers vooren nade komma.

double d = 100.0000000000005;
int precision = std::numeric_limits<double>::max_digits10;
std::cout << std::fixed << std::setprecision(precision) << d << std::endl;

Dit geeft een onjuist resultaat weer:

100.00000000000049738

Opmerking: headerbestanden vereist

#include <iomanip>
#include <limits>

6, Autoriteit 2%

In C++ 20 kun je std::formatom dit te doen:

std::cout << std::format("{}", M_PI);

Uitvoer (uitgaande van IEEE754 double):

3.141592653589793

De standaard drijvende-kommanotatie is de kortste decimale weergave met een retourgarantie. Het voordeel van deze methode ten opzichte van de setprecisionI/O-manipulator is dat er geen onnodige cijfers worden afgedrukt.

In de tussentijd kun je de {fmt} bibliotheek, std::formatis gebaseerd op. {fmt} biedt ook de functie printdie dit nog eenvoudiger en efficiënter maakt (godbolt):

fmt::print("{}", M_PI);

Disclaimer: ik ben de auteur van {fmt} en C++20 std::format.


Antwoord 7, autoriteit 2%

Met volledige precisie veronderstel ik voldoende precisie om de beste benadering van de beoogde waarde te tonen, maar er moet op worden gewezen dat doublewordt opgeslagen met basis 2 representatie en basis 2 niet kan vertegenwoordigen zoiets triviaals als 1.1precies. De enige manier om de volledige-precisie van de daadwerkelijke dubbele (zonder AFRONDENFOUT) te krijgen, is door de binaire bits (of hex nybbles) af te drukken.

Een manier om dat te doen is door een unionte gebruiken om de doublein te typen tot een geheel getal en vervolgens het gehele getal af te drukken, aangezien gehele getallen niet onderhevig zijn aan truncatie of afronding -uit problemen. (Dit soort woordspelingen wordt niet ondersteund door de C++-standaard, maar wel in C. De meeste C++-compilers zullen echter waarschijnlijk toch de juiste waarde afdrukken. Ik denk dat g++ dit ondersteunt.)

union {
    double d;
    uint64_t u64;
} x;
x.d = 1.1;
std::cout << std::hex << x.u64;

Dit geeft je de 100% nauwkeurige precisie van de dubbele… en is volkomen onleesbaar omdat mensen het IEEE-dubbelformaat niet kunnen lezen! Wikipediaheeft een goede beschrijving van hoe de binaire bits moeten worden geïnterpreteerd.

In nieuwere C++ kunt u dat doen

std::cout << std::hexfloat << 1.1;

Antwoord 8

IEEE 754drijvende-kommawaarden worden opgeslagen met basis 2-representatie. Elk getal met grondtal 2 kan met volledige precisie worden weergegeven als een decimaal (grondtal 10). Geen van de voorgestelde antwoorden echter. Ze allekappen de decimale waarde af.

Dit lijkt te wijten te zijn aan een verkeerde interpretatie van wat std::numeric_limits<T>::max_digits10staat voor:

De waarde van std::numeric_limits<T>::max_digits10is het aantal grondtal-10 cijfers dat nodig is om alle afzonderlijke waarden van het type T.

Met andere woorden: het is het (in het slechtste geval) aantal cijfers dat nodig is om uit te voeren als u van binair naar decimaal naar binair wilt gaan, zonder enige informatie te verliezen. Als u ten minste max_digits10decimalen uitvoert en een drijvende-kommawaarde reconstrueert, krijgt u gegarandeerd exact dezelfde binaire weergave waarmee u begon.

Wat belangrijk is: max_digits10levert in het algemeen niet de kortste decimaal op, en het is ook niet voldoende om de volledige precisie weer te geven. Ik ben niet op de hoogte van een constante in de C++ Standard Library die het maximale aantal decimale cijfers codeert dat nodig is om de volledige precisie van een drijvende-kommawaarde te bevatten. Ik geloof dat het ongeveer 767 is voor doubles1. Een manier om een drijvende-kommawaarde met volledige precisie uit te voeren, is door een voldoende grote waarde voor de precisie te gebruiken, zoals zo2, en laat de bibliotheek alle nullen verwijderen:

#include <iostream>
int main() {
    double d = 0.1;
    std::cout.precision(767);
    std::cout << "d = " << d << std::endl;
}

Dit levert de volgende uitvoer op, die de volledige precisie bevat:

d = 0.1000000000000000055511151231257827021181583404541015625

Merk op dat dit aanzienlijk meer decimalen heeft dan max_digits10zou suggereren.


Terwijl dat de vraag beantwoordt die werd gevraagd, zou een veel meer gemeenschappelijk doel zijn om het kortste decimale weergave van een bepaalde drijvende puntwaarde te krijgen, die alle informatie behoudt. Nogmaals, ik ben niet op de hoogte van een manier om de standaard I / O-bibliotheek te instrueren om die waarde uit te voeren. Beginnend met C++ 17 De mogelijkheid om die conversie te doen is eindelijk aangekomen in C++ in de vorm van std::to_chars. Standaard produceert het de kortste decimale weergave van een bepaalde drijvende puntwaarde die de volledige informatie behoudt.

De interface is een beetje clunky en u wilt waarschijnlijk dit in een functiesjabloon wikkelen die iets retourneert dat u kunt uitvoeren naar std::cout(zoals een std::string), bijv.

#include <charconv>
#include <array>
#include <string>
#include <system_error>
#include <iostream>
#include <cmath>
template<typename T>
std::string to_string(T value)
{
    // 24 characters is the longest decimal representation of any double value
    std::array<char, 24> buffer {};
    auto const res { std::to_chars(buffer.data(), buffer.data() + buffer.size(), value) };
    if (res.ec == std::errc {})
    {
        // Success
        return std::string(buffer.data(), res.ptr);
    }
    // Error
    return { "FAILED!" };
}
int main()
{
    auto value { 0.1f };
    std::cout << to_string(value) << std::endl;
    value = std::nextafter(value, INFINITY);
    std::cout << to_string(value) << std::endl;
    value = std::nextafter(value, INFINITY);
    std::cout << to_string(value) << std::endl;
}

Dit zou worden afgedrukt (met behulp van Microsoft’s C++ Standard Library):

0.1
0.10000001
0.10000002

1Uit de CppCon 2019-lezing van Stephan T. Lavavej getiteld Floating -Punt <charconv>: uw code 10x sneller maken met de eindbaas van C++17. (De hele talk is het bekijken waard.)

2Hiervoor zou ook een combinatie van scientificen fixednodig zijn, welke van beide korter is. Ik ben niet op de hoogte van een manier om deze modus in te stellen met behulp van de C++ Standard I/O-bibliotheek.


Antwoord 9

C++20 std::format

Deze geweldige nieuwe C++-bibliotheekfunctie heeft het voordeel dat het de status van std::coutniet beïnvloedt zoals std::setprecisiondoet:

#include <format>
#include <string>
int main() {
    std::cout << std::format("{:.2} {:.3}\n", 3.1415, 3.1415);
}

Verwachte uitvoer:

3.14 3.145

Zoals vermeld op https://stackoverflow.com/a/65329803/895245als u de precisie drukt het expliciet de kortste decimale weergave af met een retourgarantie. TODO begrijpt in meer detail hoe het zich verhoudt tot: dbl::max_digits10zoals getoond op https://stackoverflow. com/a/554134/895245met {:.{}}:

#include <format>
#include <limits>
#include <string>
int main() {
    std::cout << std::format("{:.{}}\n",
        3.1415926535897932384626433, dbl::max_digits10);
}

Zie ook:


Antwoord 10

printf("%.12f", M_PI);

%.12f betekent drijvende komma, met een precisie van 12 cijfers.


Antwoord 11

Meeste draagbaar…

#include <limits>
using std::numeric_limits;
    ...
    cout.precision(numeric_limits<double>::digits10 + 1);
    cout << d;

Antwoord 12

In deze vraagstaat een beschrijving hoe je een double zonder verlies omzet in string (in Octave, maar het kan gemakkelijk te reproduceren in C++). Het idee is om een korte, voor mensen leesbare beschrijving van de vlotter te hebben en een verliesloze beschrijving in hexa-vorm, bijvoorbeeld: pi -> 3.14{54442d18400921fb}.


Antwoord 13

Hier is een functie die werkt voor elk type met drijvende komma, niet alleen double, en de stream ook terugzet zoals deze later werd gevonden. Helaas zal het niet goed communiceren met threads, maar dat is de aard van iostreams. U hebt deze nodig aan het begin van uw bestand:

#include <limits>
#include <iostream>

Hier is de functie, je zou hem in een header-bestand kunnen zetten als je hem veel gebruikt:

template <class T>
void printVal(std::ostream& os, T val)
{
    auto oldFlags = os.flags();
    auto oldPrecision = os.precision();
    os.flags(oldFlags & ~std::ios_base::floatfield);
    os.precision(std::numeric_limits<T>::digits10);
    os << val;
    os.flags(oldFlags);
    os.precision(oldPrecision);
}

Gebruik het als volgt:

double d = foo();
float f = bar();
printVal(std::cout, d);
printVal(std::cout, f);

Als u de normale invoegoperator <<wilt gebruiken, kunt u deze extra wrappercode gebruiken:

template <class T>
struct PrintValWrapper { T val; };
template <class T>
std::ostream& operator<<(std::ostream& os, PrintValWrapper<T> pvw) {
    printVal(os, pvw.val);
    return os;
}
template <class T>
PrintValWrapper<T> printIt(T val) {
    return PrintValWrapper<T>{val};
}

Nu kun je het als volgt gebruiken:

double d = foo();
float f = bar();
std::cout << "The values are: " << printIt(d) << ", " << printIt(f) << '\n';

14

Hiermee wordt de waarde weergegeven tot twee decimalen na de punt.

#include <iostream>
#include <iomanip>
double d = 2.0;
int n = 2;
cout << fixed << setprecision(n) << d;

Zie hier: opgelost -point notatie

STD :: Fixed

Gebruik vaste drijvende notatie Stelt de vlag van de floatfield-indeling voor
de str-stroom opgelost.

Wanneer floatfield op vaste, zijn drijvende-puntwaarden geschreven
met behulp van een notatie van vaste punt: de waarde is vertegenwoordigd met precies zoals
Veel cijfers in het decimale deel zoals gespecificeerd door het precisieveld
(precisie) en zonder exponent onderdeel.

STD :: SetPrecision

Set Decimal Precision Stelt de decimale precisie in die moet worden gebruikt om te formatteren
drijvende-puntwaarden op uitvoeroperaties.

Als u bekend bent met de IEEE-standaard voor het weergeven van de drijvende punten, zou u weten dat het onmogelijk is om drijvende punten te tonen met volledige precisie uit de reikwijdte van de standaard , Dat wil zeggen, het zal altijd resulteren in een afronding van de echte waarde.

U moet eerst controleren of de waarde binnen de scope , indien ja, gebruikt:

cout << defaultfloat << d ;

STD :: DefaultFloat

Gebruik standaard drijvende notatie Stelt de vlag van het Floatfield-formaat in
voor de str-stroom naar defaultFloat.

Wanneer floatfield is ingesteld op de standaardfloat, zijn drijvende-puntwaarden
Geschreven met behulp van de standaard notatie: de weergave gebruikt zoveel mogelijk
zinvolle cijfers zoals nodig is tot de decimale precisie van de stroom
(Precisie), zowel de cijfers voor als na de decimale tellen
punt (indien aanwezig).

Dat is ook het standaardgedrag van cout, wat betekent dat u het niet expliciet gebruikt.


15

Met Ostream :: Precision (INT)

cout.precision( numeric_limits<double>::digits10 + 1);
cout << M_PI << ", " << M_E << endl;

levert

3.141592653589793, 2.718281828459045

Waarom u “+1” moet zeggen, ik heb geen aanwijzing, maar het extra cijfer dat u eruit haalt, is correct.

Other episodes