C99语言具有直接指定二进制浮点文字(因此称为“hexfloats”)的指数和尾数的能力,例如,0x1.0p0是1 * 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),因此无法实现实际有用的功能.
解决方法
对于那些需要符合标准的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,end) : getNumber<Int>(array,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 end) { return begin==end ? end : array[begin]==charToFind ? begin : findChar(array,charToFind,end); } static constexpr size_t mantisSAEnd(const char* str) { return findChar(str,'p',strlen(str)); } static constexpr size_t pointPos(const char* str) { return findChar(str,'.',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,long double currentValue,long double currentFactor) { return begin==end ? currentValue : addDigits(str,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 numIndex) { // relying on CHAR_BIT==8 here return N>32 ? throw UnsupportedLiteralLength{} : start==end || start>=N ? 0 : string_in_ull(array,start+1,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),2),3),4),5),6),7),ch(B,ch(C,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,8,\ HEX_LF_C::string_in_ull(#num,8),24,16),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"; }