Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ hexfloat compile-time parsing

The C99 language features the ability to directly specify the exponent and mantissa of a binary floating point literal (hence referred to as "hexfloats"), e.g. 0x1.0p0 is 1 * pow(2, 0), or 1.0. The C++11 standard includes the C99 standard library, including the ability to serialize and deserialize hexfloats from strings, but for some mysterious reason does not include the literals themselves.

(1) Why did the language committee not add this very simple feature that is pretty much essential for numeric computing?

(2) How can I implement compile-time hexfloat parsing in the C++11 subset supported by Visual Studio 2013? GCC allows hexfloat literals in C++, so this is not a problem in the GNU world.

EDIT: Apparently hexfloats couldn't be added to C++11 because it would conflict with the user-defined literal "p". Especially ironic that an actually useful feature couldn't be implemented because of something that nobody uses (UDLs).

like image 545
68ejxfcj5669 Avatar asked Sep 28 '15 18:09

68ejxfcj5669


2 Answers

The C++11 standard includes the C99 standard library, including the ability to serialize and deserialize hexfloats from strings, but for some mysterious reason does not include the literals themslves.

Lexing and tokenization of literals is not part of the standard library, so just referring to the C99 standard library in the C++ standard library doesn't mean that individual language features are also included in C++.

(1) Why did the language committee not add this very simple feature that is pretty much essential for numeric computing?

Because nobody proposed it for inclusion in C++. Things don't just magically appear in the C++ standard because they are in the C standard. Someone has to propose it and argue in favour of it.

like image 84
Jonathan Wakely Avatar answered Oct 22 '22 14:10

Jonathan Wakely


For those who need standard-compliant constexpr hexadecimal float literals or equivalent functionality (or just are interested in some parsing at compile time :) ), here is a solution for C++11 and higher. It's not in the form of user-defined literals though, but is very close. The usage is like HEX_LF_C(0x3.02ca7b893217bp-3456) to make a literal of 0x3.02ca7b893217bp-3456.

The only name in global namespace which becomes reserved here is HEX_LF_C due to class name and the macro.

For a much simpler version for C++11 and even more readable for C++14, but with less straightforward usage see version 6 of this answer.

Now here goes the code (and 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";
}
like image 42
Ruslan Avatar answered Oct 22 '22 14:10

Ruslan