How to have all platform compiler output the same string for NaN?
Asked Answered
S

5

9

Consider this code snippet:

#include <iostream>
#include <string>
#include <limits>

int main()
{
    std::cout << std::numeric_limits<double>::quiet_NaN();
}

When compiled with Visual Studio 2010, output is 1.#QNAN. When compiled with g++, output is nan. Note that Visual Studio 2015 outputs "nan".

However, I need both to produce the same output. What's the most simple way to do that? I tried to override operator<< for double but I feel like that's not the right way to do. Can string to be used for NaN value be forced at stream level, or better, at global level (using std::locale stuff?...never used that...).

I found this squaring_num_put example. Interesting because it's a way to modify a number is redirected to the output. But I'm having a hard time trying to adapt it to my problem (Could not make do_put send either a number or a hard coded "NaN" string to the ostream...).

Stomach answered 8/2, 2016 at 13:28 Comment(3)
I need both to produce the same output. Sounds like an XY problem to me...Famagusta
Outputting of numbers ultimately calls std::num_put::do_put which basically uses std::printf to format the output. And as the std::printf reference pages says, the output for NaN should be nan or nan(char_sequence). (which one is implementation defined) This means that Visual Studio is wrong. It also means that even for compliant compilers and libraries, you have two alternative outputs to handleDonndonna
So the question may be how to force VS2010 to output "nan" rather than "1.#QNAN"....nota that VS2015 follows the standard and outputs "nan", which is OK.Stomach
U
5

You might use a stream manipulator or modify the underlying locale:

Manipulator:

#include <cmath>
#include <ostream>

template <typename T>
struct FloatFormat
{
    const T value;

    FloatFormat(const T& value)
    : value(value)
    {}

    void write(std::ostream& stream) const {
        if(std::isnan(value))
            stream << "Not a Number";
        else
            stream << value;
    }
};

template <typename T>
inline FloatFormat<T> float_format(const T& value) {
    return FloatFormat<T>(value);
}

template <typename T>
inline std::ostream& operator << (std::ostream& stream, const FloatFormat<T>& value) {
    value.write(stream);
    return stream;
}

int main() {
    std::cout << float_format(std::numeric_limits<double>::quiet_NaN()) << '\n';

}

Locale:

#include <cmath>
#include <locale>
#include <ostream>

template<typename Iterator = std::ostreambuf_iterator<char>>
class NumPut : public std::num_put<char, Iterator>
{
    private:
    using base_type = std::num_put<char, Iterator>;

    public:
    using char_type = typename base_type::char_type;
    using iter_type = typename base_type::iter_type;

    NumPut(std::size_t refs = 0)
    :   base_type(refs)
    {}

    protected:
    virtual iter_type do_put(iter_type out, std::ios_base& str, char_type fill, double v) const override {
        if(std::isnan(v))
            out = std::copy(std::begin(NotANumber), std::end(NotANumber), out);
        else
            out = base_type::do_put(out, str, fill, v);
        return out;
    }

    virtual iter_type do_put(iter_type out, std::ios_base& str, char_type fill, long double v) const override {
        if(std::isnan(v))
            out = std::copy(std::begin(NotANumber), std::end(NotANumber), out);
        else
            out = base_type::do_put(out, str, fill, v);
        return out;
    }

    private:
    static const char NotANumber[];
};

template<typename Iterator>
const char NumPut<Iterator>::NotANumber[] = "Not a Number";

#include <iostream>
#include <limits>

int main() {
    #if 1
    {
        const std::size_t NoDestroy = 1;
        NumPut<> num_put(NoDestroy);
        std::locale locale(std::cout.getloc(), &num_put);
        std::locale restore_locale = std::cin.getloc();
        std::cout.imbue(locale);
        std::cout << std::numeric_limits<double>::quiet_NaN() << '\n';
        // The num_put facet is going out of scope:
        std::cout.imbue(restore_locale);
    }
    #else
    {
        // Alternitvely use a reference counted facet and pass the ownership to the locales:
        auto num_put = new NumPut<>();
        std::locale locale(std::cout.getloc(), num_put);
        std::cout.imbue(locale);
        std::cout << std::numeric_limits<double>::quiet_NaN() << '\n';
    }
    #endif
    std::cout << std::numeric_limits<double>::quiet_NaN() << '\n';
}
Unshapen answered 8/2, 2016 at 14:28 Comment(1)
Thanks! That's exactly what I was looking for!Stomach
M
3

Just implement your own checking against the quiet_NaN value, and print based on that.

Mantissa answered 8/2, 2016 at 13:31 Comment(7)
But you can't just use == quiet_NaN for that, NaNs never compare equal to anything (even themselves). It's one of their more interesting properties.Corby
@MarkRansom: You can use isnanStomach
@Stomach I know that. I was prodding to get an improved answer.Corby
@MarkRansom: you know, you can edit my answers to make them better ;)Sympathize
@MarkRansom: or actually, you should've added that part of info to your Q in the first place ;)Sympathize
I know how to test if a number is nan, but, obviously, for huge project where numbers are redirecting to outputs and files in many places, that will be a pain to add the test everywhere. I'm looking for a global solution (either modifiying std::locale or currently used ostream attributes).Stomach
I think your ostream attributes are pretty much the way to go; after all, what you want to do is modifying how specific things are printed.Sympathize
E
2
  • Derive your YourNumPut from std::num_put.
  • Override what you need for example: virtual iter_type do_put( iter_type out, std::ios_base& str, char_type fill, double v ) const;
  • Create locale that uses it: std::locale yourLocale(std::locale(), new YourNumPut());
  • Set it global, imbue cout and cerr or where you need: std::locale::global(yourLocale); std::cout.imbue(yourLocale); std::cerr.imbue(yourLocale);

  • Test it

  • ...
  • Profit ;)
Exempt answered 8/2, 2016 at 14:26 Comment(2)
I tried that, but could not figure out how to send a string (like "nan") to the output stream within do_put overloading method....could you please post a compiling example?Stomach
Just std::copy to out parameter.Lookin
K
0

Use isnan() to portably test if a double is a NaN.

#include <cmath>

// ...

double d;

if (isnan(d))
   // ...
Kirovograd answered 8/2, 2016 at 13:34 Comment(1)
I know how to test if a number is nan, but, obviously, for huge project where numbers are redirecting to outputs and files in many places, that will be a pain to add the test everywhere. I'm looking for a global solution (either modifiying std::locale or currently used ostream attributes).Stomach
C
0

In C++20 you'll be able to use std::format to do this:

std::cout << std::format("{}", std::numeric_limits<double>::quiet_NaN());

This will print nan on all platforms that have quiet_NaN.

Disclaimer: I'm the author of C++20 std::format.

Crenshaw answered 16/12, 2020 at 16:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.