C++ hexfloat 编译时解析

Posted

技术标签:

【中文标题】C++ hexfloat 编译时解析【英文标题】:C++ hexfloat compile-time parsing 【发布时间】:2015-09-28 18:28:46 【问题描述】:

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)而无法实现一个真正有用的功能。

【问题讨论】:

VS0213 并不完全是 C++11 投诉。你可能想试试 VS2015。 @NathanOliver,我知道。它缺少constexpr,这将使实现变得相当困难。不幸的是,我需要支持 VS2013,因为我使用的一些库还不能与 VS2015 一起使用。 具有讽刺意味的是,由于没有人使用 UDL 的东西而无法实现一个真正有用的功能。答对了。这一点,以及 C++ 社区中的一些人明显不喜欢那些使用 C++ 来做基础科学和/或数值计算的人。 我在这里看不到与用户定义的文字有任何冲突。不以下划线开头的用户定义文字运算符保留用于实现,标准库中没有p用户定义文字运算符。 自 C++17 起支持 FYI 十六进制浮点数 en.cppreference.com/w/cpp/language/floating_literal 【参考方案1】:

C++11 标准包括 C99 标准库,包括从字符串序列化和反序列化 hexfloats 的能力,但由于某种神秘的原因不包括文字本身。

文字的词法和标记化不是标准库的一部分,因此仅在 C++ 标准库中引用 C99 标准库并不意味着 C++ 中也包含个别语言功能。

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

因为没有人提议将它包含在 C++ 中。事物不会神奇地出现在 C++ 标准中,因为它们在 C 标准中。必须有人提出并支持它。

【讨论】:

认真的吗?这是自 1963 年以来明显需要的一个特征,最低限度(例如,E. Lorenz,Deterministic nonperiodic flow)。这是在许多 C 数学库实现中广泛使用的功能。 C 社区在上个千年的某个时候看到了这种需求。这是 C++ 委员会的一次惨败。 @DavidHammen,我在哪里说过不需要?我说没有人提议将它包含在内,所以它没有被包含在内。你不同意或认为我不认真的哪一部分?我认为这是一个真正的遗憾,我希望我们在 C++ 中拥有它们。 最近有人提议将其纳入 C++ 标准,委员会投票决定将其纳入。就像我上面说的,事情不会神奇地出现,但如果有人提出它们,那么它们可能会成为标准的一部分。这就是该过程应该如何工作的方式。【参考方案2】:

对于那些需要符合标准的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";

【讨论】:

以上是关于C++ hexfloat 编译时解析的主要内容,如果未能解决你的问题,请参考以下文章

从 MS Visual C++ 编译器更改为英特尔 C++ 19.1 编译器时未解析的外部符号 __imp__fread

C++模板元编程深度解析:探索编译时计算的神奇之旅

编译期多态和运行时多态

是否可以在 C++ 运行时动态创建函数?

大括号之谜:C++的列表初始化语法解析

编译时间文本到数字转换 (atoi)