C ++ hexfloat编译时解析

时间:2015-09-28 18:28:46

标签: c++ c++11 visual-c++

C99语言具有直接指定二进制浮点文字的指数和尾数(因此称为“hexfloats”)的能力,例如, 0x1.0p01 * pow(2, 0),或1.0。 C ++ 11标准包括C99标准库,包括从字符串序列化和反序列化hexfloats的能力,但由于某些神秘的原因,不包括文字本身。

(1)为什么语言委员会不会添加这个对数字计算非常重要的非常简单的功能呢?

(2)如何在Visual Studio 2013支持的C ++ 11子集中实现编译时hexfloat解析? GCC允许在C ++中使用hexfloat文字,因此这在GNU世界中不是问题。

编辑:显然无法将hexfloats添加到C ++ 11中,因为它会与用户定义的文字“p”冲突。特别具有讽刺意味的是,由于没有人使用(UDL),因此无法实现实际有用的功能。

2 个答案:

答案 0 :(得分:8)

  

C ++ 11标准包括C99标准库,包括从字符串序列化和反序列化hexfloats的能力,但由于一些神秘的原因,不包括字面数据。

文字的Lexing和标记化不是标准库的一部分,因此仅仅参考C ++标准库中的C99标准库并不意味着C ++中也包含单个语言特性。

  

(1)为什么语言委员会不会添加这个对数字计算非常重要的非常简单的功能呢?

因为没有人建议将它包含在C ++中。事情并不只是神奇地出现在C ++标准中,因为它们属于C标准。有人必须提出它并争辩它。

答案 1 :(得分:3)

对于那些需要符合标准的constexpr十六进制浮点字符或等效功能的人(或者只是对编译时的一些解析感兴趣:)),这里是C ++ 11及更高版本的解决方案。它不是用户定义的文字形式,但非常接近。用法与HEX_LF_C(0x3.02ca7b893217bp-3456)类似0x3.02ca7b893217bp-3456

由于类名和宏,全局命名空间中唯一保留的名称是HEX_LF_C

对于C ++ 11更简单的版本,对C ++ 14更具可读性,但使用不太直接,请参阅version 6 of this answer

现在这里是代码(和here's its live demo):

class HEX_LF_C
{
    using size_t=decltype(sizeof(0)); // avoid including extra headers
    static constexpr const long double _0x1p256=1.15792089237316195424e77L; // 2^256
    struct BadDigit{};
    // Unportable, but will work for ANSI charset
    static constexpr int hexDigit(char c)
    {
        return '0'<=c&&c<='9' ? c-'0' :
               'a'<=c&&c<='f' ? c-'a'+0xa :
               'A'<=c&&c<='F' ? c-'A'+0xA : throw BadDigit{};
    }
    // lightweight constexpr analogue of std::strtoull
    template<typename Int>
    static constexpr Int getNumber(const char* array,
                                   int base,
                                   size_t begin,
                                   size_t end,
                                   Int accumulated=Int(0))
    {
        return begin==end ? accumulated :
               array[begin]=='-' ? -getNumber<Int>(array,base,begin+1,end) :
               array[begin]=='+' ? +getNumber<Int>(array,base,begin+1,end) :
               getNumber<Int>(array,base,begin+1,end,
                                    accumulated*base+hexDigit(array[begin]));
    }
    // lightweight constexpr version of std::scalbn
    static constexpr long double scalbn(long double value, int exponent)
    {
        // Trying hard to avoid hitting compiler recursion limit
        return exponent==0 ? value : exponent>0 ?
            (exponent>+255 ? scalbn(value*_0x1p256,exponent-256) : scalbn(value*2,exponent-1)) :
            (exponent<-255 ? scalbn(value/_0x1p256,exponent+256) : scalbn(value/2,exponent+1));
    }
    // constexpr version of std::strlen
    static constexpr size_t strlen(const char* array)
    { return *array ? 1+strlen(array+1) : 0; }
    static constexpr size_t findChar(const char* array,
                                     char charToFind,
                                     size_t begin,
                                     size_t end)
    {
        return begin==end ? end :
               array[begin]==charToFind ? begin :
               findChar(array,charToFind,begin+1,end);
    }
    static constexpr size_t mantissaEnd(const char* str)
    { return findChar(str,'p',0,strlen(str)); }

    static constexpr size_t pointPos(const char* str)
    { return findChar(str,'.',0,mantissaEnd(str)); }

    static constexpr int exponent(const char* str)
    {
        return mantissaEnd(str)==strlen(str) ? 0 :
                getNumber<int>(str,10,mantissaEnd(str)+1,strlen(str));
    }
    static constexpr bool isSign(char ch) { return ch=='+'||ch=='-'; }
    static constexpr size_t mantissaBegin(const char* str)
    {
        return isSign(*str)+
               2*(str[isSign(*str)]=='0' && str[isSign(*str)+1]=='x');
    }
    static constexpr unsigned long long beforePoint(const char* str)
    {
        return getNumber<unsigned long long>(str,
                                             16,
                                             mantissaBegin(str),
                                             pointPos(str));
    }
    static constexpr long double addDigits(const char* str,
                                           size_t begin,
                                           size_t end,
                                           long double currentValue,
                                           long double currentFactor)
    {
        return begin==end ? currentValue :
               addDigits(str,begin+1,end,
                         currentValue+currentFactor*hexDigit(str[begin]),
                         currentFactor/16);
    }
    // If you don't need to force compile-time evaluation, you can use this
    // directly (having made it public)
    template<size_t N>
    static constexpr long double get(const char (&str)[N])
    {
        return (str[0]=='-' ? -1 : 1)*
            addDigits(str,pointPos(str)+1,mantissaEnd(str),
                      scalbn(beforePoint(str),exponent(str)),
                      scalbn(1.L/16,exponent(str)));
    }
    struct UnsupportedLiteralLength{};
public:
    // This helps to convert string literal to a valid template parameter
    // It just packs the given chunk (8 chars) of the string into a ulonglong.
    // We rely here and in LF_Evaluator on the fact that 32 chars is enough
    // for any useful long double hex literal (on x87 arch).
    // Will need tweaking if support for wider long double types is required.
    template<size_t N>
    static constexpr unsigned long long string_in_ull(const char (&array)[N],
                                                      size_t start,
                                                      size_t end,
                                                      size_t numIndex)
    {
        // relying on CHAR_BIT==8 here
        return N>32 ? throw UnsupportedLiteralLength{} :
               start==end || start>=N ? 0 :
               string_in_ull(array,start+1,end,numIndex) |
                    ((array[start]&0xffull)<<(8*(start-numIndex)));
    }
    // This is to force compile-time evaluation of the hex constant
    template<unsigned long long A,
             unsigned long long B,
             unsigned long long C,
             unsigned long long D>
    struct LF_Evaluator
    {
        static constexpr char ch(unsigned long long X,
                                 int charIndex) { return X>>charIndex*8; }
        static constexpr const char string[32]={
            ch(A,0),ch(A,1),ch(A,2),ch(A,3),ch(A,4),ch(A,5),ch(A,6),ch(A,7),
            ch(B,0),ch(B,1),ch(B,2),ch(B,3),ch(B,4),ch(B,5),ch(B,6),ch(B,7),
            ch(C,0),ch(C,1),ch(C,2),ch(C,3),ch(C,4),ch(C,5),ch(C,6),ch(C,7),
            ch(D,0),ch(D,1),ch(D,2),ch(D,3),ch(D,4),ch(D,5),ch(D,6),ch(D,7)
            };
        static constexpr long double value=get(string);
    };
};

#define HEX_LF_C(num) HEX_LF_C::LF_Evaluator<                    \
                        HEX_LF_C::string_in_ull(#num,0,8,0),     \
                        HEX_LF_C::string_in_ull(#num,8,16,8),    \
                        HEX_LF_C::string_in_ull(#num,16,24,16),  \
                        HEX_LF_C::string_in_ull(#num,24,32,24)>::value

// Now some usage examples

#include <iostream>
#include <iomanip>

int main()
{
    enum { constexprTest=static_cast<int>(HEX_LF_C(0x2.34f7a87dp+19)) };
    std::cout << "constexpr test: " << constexprTest << "\n";
    std::cout << "value expected: " << 1157053 << "\n";

    std::cout << "need: 0x9.234f87ac95p+954\n";
    std::cout << "got : " << std::hexfloat << HEX_LF_C(0x9.234f87ac95p+954) << "\n";

    std::cout << "need: -0x9.00234f87ac95p-1954\n";
    std::cout << "got : " << std::hexfloat << HEX_LF_C(-0x9.00234f87ac95p-1954) << "\n";

#if defined __GNUG__ && !defined __clang__ // clang emits errors in pedantic mode
    static_assert(0x3.02ca7b893217bp-3456L==HEX_LF_C(0x3.02ca7b893217bp-3456),
                  "Something is broken");
    std::cout << std::boolalpha << "Works correctly as compared to GCC? "
              << (0x3.4d0a89f5c217bp-3456L==HEX_LF_C(0x3.4d0a89f5c217bp-3456)) << "\n";
#endif
    std::cout << "0x" << "9.2f3ca523p+73" << "\n";
    constexpr auto x=HEX_LF_C(9.2f3ca523p+73);
    std::cout << std::hexfloat << x << "\n";
}