2015-09-28 19 views
6

Il linguaggio C99 offre la possibilità di specificare direttamente l'esponente e la mantissa di un valore letterale in virgola mobile binario (quindi denominato "hexfloats"), ad es. 0x1.0p0 è 1 * pow(2, 0) o 1,0. Lo standard C++ 11 include la libreria standard C99, inclusa la possibilità di serializzare e deserializzare hexfloats dalle stringhe, ma per qualche misterioso motivo non include gli stessi letterali.C++ hexfloat parsing in fase di compilazione

(1) Perché il comitato linguistico non ha aggiunto questa funzione molto semplice che è praticamente essenziale per il calcolo numerico?

(2) Come posso implementare l'analisi hexfloat in fase di compilazione nel sottoinsieme C++ 11 supportato da Visual Studio 2013? GCC consente letterali hexfloat in C++, quindi questo non è un problema nel mondo GNU.

EDIT: Apparentemente hexfloats non può essere aggiunto a C++ 11 perché sarebbe in conflitto con la "p" letterale definita dall'utente. Particolarmente ironico sul fatto che una funzionalità realmente utile non possa essere implementata a causa di qualcosa che nessuno usa (UDL).

+0

VS0213 non è totalmente C++ 11 denuncia. potresti voler provare VS2015. – NathanOliver

+0

@NathanOliver, lo so. Manca 'constexpr', il che renderà l'implementazione abbastanza difficile. Sfortunatamente, ho bisogno di supportare VS2013, dato che alcune librerie che sto usando non funzionano ancora con VS2015. – 68ejxfcj5669

+0

Per essere precisi, stai chiedendo qualcosa sulla falsariga di 'double pi = 0x1.921fb54442d18p + 1;'? –

risposta

3

Per coloro che necessitano di valori letterali esadecimali conformi allo standard constexpr o di funzionalità equivalenti (o sono solo interessati ad eseguire l'analisi in fase di compilazione :)), ecco una soluzione per C++ 11 e versioni successive. Tuttavia, non è nella forma di letterali definiti dall'utente, ma è molto vicino. L'utilizzo è come HEX_LF_C(0x3.02ca7b893217bp-3456) per creare un valore letterale di 0x3.02ca7b893217bp-3456.

L'unico nome nel namespace globale che viene riservato qui è HEX_LF_C a causa del nome della classe e della macro.

Per una versione molto più semplice per C++ 11 e ancor più leggibile per C++ 14, ma con un utilizzo meno semplice vedere version 6 of this answer.

Ora qui va il codice (e 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"; 
} 
7

Lo standard C++ 11 include la libreria standard C99, inclusa la possibilità di serializzare e deserializzare hexfloats dalle stringhe, ma per qualche misteriosa ragione non comprende i themslves letterali.

Lexing e tokenizzazione di letterali non fa parte della libreria standard, quindi basta riferendosi alla libreria standard C99 nella libreria standard C++, non significa che le singole caratteristiche del linguaggio sono inclusi anche in C++.

(1) Perché il comitato linguistico non ha aggiunto questa funzione molto semplice che è praticamente essenziale per il calcolo numerico?

Perché nessuno lo ha proposto per l'inclusione in C++. Le cose non appaiono solo magicamente nello standard C++ perché sono nello standard C. Qualcuno deve proporlo e discuterne a favore.

+0

Seriamente? Questa è una caratteristica che è stata ovviamente necessaria dal 1963, minima (per esempio, E. Lorenz, * Flusso nonperiodico deterministico *). È una funzionalità ampiamente utilizzata in molte implementazioni della libreria matematica C. La comunità C ha visto la necessità di questo a volte nel millennio precedente. Questo è un abietto fallimento del comitato C++. –

+3

@DavidHammen, dove ho detto che non era necessario? Ho detto che nessuno l'ha proposto per l'inclusione, quindi non è stato incluso. In quale parte di ciò non sei d'accordo o pensi che non sia serio? Penso che sia un vero peccato, vorrei averli in C++. –

+0

Qualcuno lo ha recentemente proposto per l'inclusione nello standard C++ e la commissione ha votato per includerlo. Come ho detto sopra, le cose non appaiono solo magicamente, ma se qualcuno le propone potrebbero diventare parte dello standard. È così che dovrebbe funzionare il processo. –

Problemi correlati