【问题标题】:Floating point format for std::ostreamstd::ostream 的浮点格式
【发布时间】:2012-08-16 14:28:20
【问题描述】:

如何使用 std::cout 执行以下操作?

double my_double = 42.0;
char str[12];
printf_s("%11.6lf", my_double); // Prints " 42.000000"

我正准备放弃并使用 sprintf_s。

更一般地说,我在哪里可以找到关于 std::ostream 格式的参考,它将所有内容都列在一个地方,而不是在长篇教程中全部展开?

编辑 2017 年 12 月 21 日 - 请参阅下面的答案。它使用了我在 2012 年问这个问题时不可用的功能。

【问题讨论】:

  • @pete - 你每天都会学到一些东西。我已经使用 %lf 28 年了。
  • @Pete @Jive: 使用%lf 没有错; l 根据标准被忽略。请参阅 pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html 或您最喜欢的 C 规范。
  • @pete 来自 PCC 的一切 ca。大约五分钟前从 1984 年到 VC++ 2010 年。
  • @PeteBecker:有一个“lf”格式说明符。根据 C99 标准,“[“l”修饰符] 对后面的 a、A、e、E、f、F、g 或 G 转换说明符没有影响。换句话说,“lf”与“f”的意思相同。
  • 存在 %lf 的原因是可以为 scanf 和 printf 使用相同的格式字符串。您必须告诉 scanf arg 是浮点数 (%f) 还是双精度数 (%lf)。弄错了,你的程序就会出错。

标签: c++ floating-point cout ostream


【解决方案1】:
std::cout << std::fixed << std::setw(11) << std::setprecision(6) << my_double;

你需要添加

#include <iomanip>

你需要stream manipulators

你可以用你想要的任何字符“填充”空白的地方。像这样:

std::cout << std::fixed << std::setw(11) << std::setprecision(6) 
          << std::setfill('0') << my_double;

【讨论】:

  • 哪些?我把 11 放在哪里,我把 6 放在哪里?
  • 为了匹配原始格式说明符,这还需要std::setw(11)。附带说明一下,对于精度为 6 的特定情况,无论在 C 还是 C++ 中,您都不需要说出来,因为它是默认值。
  • @JiveDadson - 抱歉,我错过了11。已编辑。
  • +1 用于包含 include 语句,太多人不这样做
  • 这是 OP 的一个很好的解决方案,但请注意这些是流操纵器;即他们修改流,你必须revert it 或每次你需要不同的格式时将其更改为其他内容。如果被操纵的流是一个“长期存在的流”,例如std::coutstd::cerr、日志流等,那就更成问题了。在这些情况下,使用建议的解决方案之一会更方便其他答案在这里。
【解决方案2】:
std::cout << boost::format("%11.6f") % my_double;

你必须#include &lt;boost\format.hpp&gt;

【讨论】:

  • 我记得我很久以前在一个很远很远的项目上使用过它。谢谢你提醒我。我希望我可以将 boost 用于我正在做的事情,但是唉......
【解决方案3】:

在 C++20 中你将能够做到

double my_double = 42.0;
char str[12];
std::format_to_n(str, sizeof(str), "{:11.6}", my_double); 

std::string s = std::format("{:11.6}", my_double); 

与此同时,您可以使用提供format_to_n 实现的the {fmt} library

免责声明:我是 {fmt} 和 C++20 std::format 的作者。

【讨论】:

    【解决方案4】:

    一般来说,您希望避免在 输出点。那是物理标记,你需要逻辑标记; 例如pressure,或volume。这样,您可以在一个地方定义 压力或音量是如何格式化的,如果格式化发生变化, 您无需搜索整个程序即可找到更改位置 格式(并意外更改其他格式)。在 C++,你通过定义一个操纵器来做到这一点,它设置各种 格式化选项,最好在完整的结尾恢复它们 表达。所以你最终会写出这样的东西:

    std::cout << pressure << my_double;
    

    虽然我绝对不会在生产代码中使用它,但我发现 以下FFmt 格式化程序对快速工作很有用:

    class FFmt : public StateSavingManip
    {
    public:
        explicit            FFmt(
                                int                 width,
                                int                 prec = 6,
                                std::ios::fmtflags  additionalFlags 
                                        = static_cast<std::ios::fmtflags>(),
                                char                fill = ' ' );
    
    protected:
        virtual void        setState( std::ios& targetStream ) const;
    
    private:
        int                 myWidth;
        int                 myPrec;
        std::ios::fmtflags  myFlags;
        char                myFill;
    };
    
    FFmt::FFmt(
        int                 width,
        int                 prec,
        std::ios::fmtflags  additionalFlags,
        char                fill )
        :   myWidth( width )
        ,   myPrec( prec )
        ,   myFlags( additionalFlags )
        ,   myFill( fill )
    {
        myFlags &= ~ std::ios::floatfield
        myFlags |= std::ios::fixed
        if ( isdigit( static_cast< unsigned char >( fill ) )
                 && (myFlags & std::ios::adjustfield) == 0 ) {
            myFlags |= std::ios::internal
        }
    }
    
    void
    FFmt::setState( 
        std::ios&           targetStream ) const
    {
        targetStream.flags( myFlags )
        targetStream.width( myWidth )
        targetStream.precision( myPrec )
        targetStream.fill( myFill )
    }
    

    这允许编写如下内容:

    std::cout << FFmt( 11, 6 ) << my_double;
    

    并记录在案:

    class StateSavingManip
    {
    public:
        StateSavingManip( 
                StateSavingManip const& other );
        virtual             ~StateSavingManip();
        void                operator()( std::ios& stream ) const;
    
    protected:
        StateSavingManip();
    
    private:
        virtual void        setState( std::ios& stream ) const = 0;
    
    private:
        StateSavingManip&   operator=( StateSavingManip const& );
    
    private:
        mutable std::ios*   myStream;
        mutable std::ios::fmtflags
                            mySavedFlags;
        mutable int         mySavedPrec;
        mutable char        mySavedFill;
    };
    
    inline std::ostream&
    operator<<(
        std::ostream&       out,
        StateSavingManip const&
                            manip )
    {
        manip( out );
        return out;
    }
    
    inline std::istream&
    operator>>(
        std::istream&       in,
        StateSavingManip const&
                            manip )
    {
        manip( in );
        return in;
    }
    

    StateSavingManip.cc:

    namespace {
    
    //      We maintain the value returned by ios::xalloc() + 1, and not
    //      the value itself.  The actual value may be zero, and we need
    //      to be able to distinguish it from the 0 resulting from 0
    //      initialization.  The function getXAlloc() returns this value
    //      -1, so we add one in the initialization.
    int                 getXAlloc();
    int                 ourXAlloc = getXAlloc() + 1;
    
    int
    getXAlloc()
    {
        if ( ourXAlloc == 0 ) {
            ourXAlloc = std::ios::xalloc() + 1;
            assert( ourXAlloc != 0 );
        }
        return ourXAlloc - 1;
    }
    }
    
    StateSavingManip::StateSavingManip()
        :   myStream( NULL )
    {
    }
    
    StateSavingManip::StateSavingManip(
        StateSavingManip const&
                            other )
    {
        assert( other.myStream == NULL );
    }
    
    StateSavingManip::~StateSavingManip()
    {
        if ( myStream != NULL ) {
            myStream->flags( mySavedFlags );
            myStream->precision( mySavedPrec );
            myStream->fill( mySavedFill );
            myStream->pword( getXAlloc() ) = NULL;
        }
    }
    
    void
    StateSavingManip::operator()( 
        std::ios&           stream ) const
    {
        void*&              backptr = stream.pword( getXAlloc() );
        if ( backptr == NULL ) {
            backptr      = const_cast< StateSavingManip* >( this );
            myStream     = &stream;
            mySavedFlags = stream.flags();
            mySavedPrec  = stream.precision();
            mySavedFill  = stream.fill();
        }
        setState( stream );
    }
    

    【讨论】:

    • 那是一些非常好的东西,我谢谢你。对大家说:我要离开一段时间。感谢所有的帮助。似乎没有完美的答案,但我学到了很多,甚至还记得一些我忘记的东西。当我回来时,我会为在哪里放置绿色复选标记而苦恼。
    【解决方案5】:
    #include <iostream>
    #include <iomanip>
    
    int main() {
        double my_double = 42.0;
        std::cout << std::fixed << std::setw(11)
            << std::setprecision(6) << my_double << std::endl;
        return 0;
    }
    

    【讨论】:

    • 不是我需要的。也许我问错了问题。我希望它总是打印 11 个字符,不多也不少。当我给出 0.1 时,它会打印出只有 8 个字符的“0.100000”。
    • @JiveDadson - 适合我。当我给出 0.1 时,它会打印“0.100000”,即 11 个字符。 (请注意,实际输出中有三个前导空格;它们似乎已被自动换行所吞噬。
    • 请注意,setw(11) 不会将输出设置为正好 11 个字符。它将字段宽度设置为至少 11 个字符。例如,使用这种格式打印 42e5 会产生 14 个字符。
    • 这让我发疯了。使用 printf 很简单,而且很短。正如安德烈建议的那样,我可能只需要撕掉提升/格式。从引入 iostream 的第一天起,我就鄙视它们。这场战斗我打了很多次,但我总是忘记那些可怕的细节。
    • @JiveDadson - 是的,它很冗长。如果您要经常做同样的事情(即不止一次),请编写一个可以执行此操作的操纵器。当然,仍然不像printf那么简单。
    【解决方案6】:

    对于喜欢使用 std::ostream 的实际 printf 样式格式规范的未来访问者,这里是另一个变体,基于 Martin York 在另一个 SO 问题中的出色帖子:https://stackoverflow.com/a/535636:

    #include <iostream>
    #include <iomanip>
    #include <stdio.h> //snprintf
    
    class FMT
    {
    public:
        explicit FMT(const char* fmt): m_fmt(fmt) {}
    private:
        class fmter //actual worker class
        {
        public:
            explicit fmter(std::ostream& strm, const FMT& fmt): m_strm(strm), m_fmt(fmt.m_fmt) {}
    //output next object (any type) to stream:
            template<typename TYPE>
            std::ostream& operator<<(const TYPE& value)
            {
    //            return m_strm << "FMT(" << m_fmt << "," << value << ")";
                char buf[40]; //enlarge as needed
                snprintf(buf, sizeof(buf), m_fmt, value);
                return m_strm << buf;
            }
        private:
            std::ostream& m_strm;
            const char* m_fmt;
        };
        const char* m_fmt; //save fmt string for inner class
    //kludge: return derived stream to allow operator overloading:
        friend FMT::fmter operator<<(std::ostream& strm, const FMT& fmt)
        {
            return FMT::fmter(strm, fmt);
        }
    };
    

    用法示例:

    double my_double = 42.0;
    cout << FMT("%11.6f") << my_double << "more stuff\n";
    

    甚至:

    int val = 42;
    cout << val << " in hex is " << FMT(" 0x%x") << val << "\n";
    

    【讨论】:

      【解决方案7】:

      是我,OP,Jive Dadson - 五年过去了。 C++17 正在成为现实。

      具有完美转发的可变参数模板参数的出现让生活变得如此简单。 ostream

      #include <iostream>
      #include <string.h>
      #include <stdio.h>
      #include <string_view>
      
      namespace dj {
      
          template<class Out, class... Args>
          Out& oprintf(Out &out, const std::string_view &fmt, Args&&... args) {
              const int sz = 512;
              char buffer[sz];
              int cx = snprintf(buffer, sz, fmt.data(), std::forward<Args>(args)...);
      
              if (cx >= 0 && cx < sz) { 
                  return out.write(buffer, cx);
              } else if (cx > 0) {
                  // Big output
                  std::string buff2;
                  buff2.resize(cx + 1);
                  snprintf(buff2.data(), cx, fmt.data(), std::forward<Args>(args)...);
                  return out.write(buff2.data(), cx);
              } else {
                  // Throw?
                  return out;
              }
          }
      }
      
      int main() {
          const double my_double = 42.0;
          dj::oprintf(std::cout, "%s %11.6lf\n", "My double ", my_double);
          return 0;
      }
      

      【讨论】:

      • 使用 string_view 作为 C 风格函数的输入是危险的,因为它不一定是空终止的。
      【解决方案8】:

      已经有一些很好的答案;向他们致敬!

      这是基于其中的一些。我为 POD 类型添加了类型断言,因为它们是唯一可用于 printf() 的安全类型。

      #include <iostream>
      #include <stdio.h>
      #include <type_traits>
      
      namespace fmt {
      namespace detail {
      
      template<typename T>
      struct printf_impl
      {
          const char* fmt;
          const T v;
      
          printf_impl(const char* fmt, const T& v) : fmt(fmt), v(v) {}
      };
      
      template<typename T>
      inline typename std::enable_if<std::is_pod<T>::value, std::ostream& >::type
      operator<<(std::ostream& os, const printf_impl<T>& p)
      {
          char buf[40];
          ::snprintf(buf, sizeof(buf), p.fmt, p.v, 40);
          return os << buf;
      }
      
      } // namespace detail
      
      template<typename T>
      inline typename std::enable_if<std::is_pod<T>::value, detail::printf_impl<T> >::type
      printf(const char* fmt, const T& v)
      {
          return detail::printf_impl<T>(fmt, v);
      }
      
      } // namespace fmt
      

      示例用法如下。

      std::cout << fmt::printf("%11.6f", my_double);
      

      Give it a try on Coliru.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-28
        相关资源
        最近更新 更多