本文介绍了为什么通过字符串进行往返转换是不安全的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 最近我不得不把一个双重序列化成文本,然后把它取回来。该值似乎不等同于: double d1 = 0.84551240822557006; string s = d1.ToString(R); double d2 = double.Parse(s); bool s1 = d1 == d2; // - > s1是False 但是根据 MSDN:标准数字格式字符串,R选项应保证往返安全。 为什么会这样?解决方案我发现了这个错误。 .NET在 clr\src\vm\comnumber.cpp / a>: DoubleToNumber(value,DOUBLE_PRECISION,& number); if(number.scale ==(int)SCALE_NAN){ gc.refRetVal = gc.numfmt-> sNaN; goto lExit; } if(number.scale == SCALE_INF){ gc.refRetVal =(number.sign?gc.numfmt-> sNegativeInfinity:gc.numfmt-> sPositiveInfinity); goto lExit; } NumberToDouble(& number,& dTest); if(dTest == value){ gc.refRetVal = NumberToString(& number,'G',DOUBLE_PRECISION,gc.numfmt); goto lExit; } DoubleToNumber(value,17,& number); DoubleToNumber 很简单 - 只需调用 _ecvt ,它位于C运行时: void DoubleToNumber(double value,int precision,NUMBER * number) { WRAPPER_CONTRACT _ASSERTE(number!= NULL); number-> precision = precision; if(((FPDOUBLE *)& value) - > exp == 0x7FF){ number-> scale =(((FPDOUBLE *)& value) - > mantLo || ((FPDOUBLE *)& value) - > mantHi)? SCALE_NAN:SCALE_INF; number-> sign =((FPDOUBLE *)& value) - > sign; number-> digits [0] = 0; } else { char * src = _ecvt(value,precision,& number-> scale& number-> sign); wchar * dst = number-> digits; if(* src!='0'){ while(* src)* dst ++ = * src ++; } * dst = 0; } } 事实证明, _ecvt 返回字符串 845512408225570 。 注意尾随零? 事实证明,这有所不同! 当零存在时,结果实际上解析回 0.84551240822557006 ,这是您的原始号码,所以它比较相等,因此只返回15位数字。 但是,如果我截断字符串在零到 84551240822557 ,然后我回到 0.84551240822556994 ,这是不你的原始号码,因此它将返回17位数。 证明:运行以下64位代码(大部分是从Microsoft共享源CLI 2.0中提取)您的调试器并在 main 的末尾检查 v : #include< stdlib.h> #include< string.h> #include< math.h> #define min(a,b)(((a)<(b))?(a):(b)) struct NUMBER { int精度; int scale; int sign; wchar_t digits [20 + 1]; NUMBER():precision(0),scale(0),sign(0){} }; #define I64(x)x ## LL static const unsigned long long rgval64Power10 [] = { //权力为10 / * 1 * / I64(0xa000000000000000), / * 2 * / I64(0xc800000000000000), / * 3 * / I64(0xfa00000000000000), / * 4 * / I64(0x9c40000000000000) / * 5 * / I64(0xc350000000000000), / * 6 * / I64(0xf424000000000000), / * 7 * / I64(0x9896800000000000), / * 8 * / I64(0xbebc200000000000), / * 9 * / I64(0xee6b280000000000), / * 10 * / I64(0x9502f90000000000), / * 11 * / I64(0xba43b74000000000) $ b / * 12 * / I64(0xe8d4a51000000000), / * 13 * / I64(0x9184e72a00000000), / * 14 * / I64(0xb5e620f480000000), / * 15 * / I64 (0xe35fa931a0000000), //功率为0.1 / * 1 * / I64(0xccccccccccccccd), / * 2 * / I64(0xa3d70a3d70a3d70b), / * 3 * / I64(0x83126e978d4fdf3c), / * 4 * / I64(0xd1b71758e219652e), / * 5 * / I64(0xa7c5ac471b478425), / * 6 * / I64(0x8637bd05af6c69b7) , / * 7 * / I64(0xd6bf94d5e57a42be), / * 8 * / I64(0xabcc77118461ceff), / * 9 * / I64(0x89705f4136b4a599), / * 10 * I64(0xdbe6fecebdedd5c2), / * 11 * / I64(0xafebff0bcb24ab02), / * 12 * / I64(0x8cbccc096f5088cf), / * 13 * / I64(0xe12e13424bb40e18) b / * 14 * / I64(0xb424dc35095cd813), / * 15 * / I64(0x901d7cf73ab0acdc),}; static const signed char rgexp64Power10 [] = { //对于两个权力为10和0.1的指数 / * 1 * / 4, / * 2 * / 7, / * 3 * / 10, / * 4 * / 14, / * 5 * / 17, / * 6 * / 20, / * 7 * / 24, / * 8 * / 27, / * 9 * / 30, / * 10 * / 34, / * 11 * 37, / * 12 * / 40, / * 13 * / 44, / * 14 * / 47, / * 15 * / 50, }; static const unsigned long long rgval64Power10By16 [] = { //功率为10 ^ 16 / * 1 * / I64(0x8e1bc9bf04000000), / * 2 * / I64(0x9dc5ada82b70b59e), / * 3 * / I64(0xaf298d050e4395d6), / * 4 * / I64(0xc2781f49ffcfa6d4), / * 5 * / I64(0xd7e77a8f87daf7fa) b $ b / * 6 * / I64(0xefb3ab16c59b14a0), / * 7 * / I64(0x850fadc09923329c), / * 8 * / I64(0x93ba47c980e98cde), / * 9 * I64(0xa402b9c5a8d3a6e6), / * 10 * / I64(0xb616a12b7fe617a8), / * 11 * / I64(0xca28a291859bbf90), / * 12 * / I64(0xe070f78d39275566), / * 13 * / I64(0xf92e0c3537826140), / * 14 * / I64(0x8a5296ffe33cc92c), / * 15 * / I64(0x9991a6f3d6bf1762), / * 16 * / I64 0xaa7eebfb9df9de8a), / * 17 * / I64(0xbd49d14aa79dbc7e), / * 18 * / I64(0xd226fc195c6a2f88), / * 19 * / I64(0xe950df20247c83f8), / * 20 * / I64(0x81842f29f2cce373), / * 21 * / I64(0x8fcac257558ee4e2), //功率为0.1 ^ 16 / * 1 * / I6 4(0xe69594bec44de160), / * 2 * / I64(0xcfb11ead453994c3), / * 3 * / I64(0xbb127c53b17ec165), / * 4 * / I64(0xa87fea27a539e9b3), / * 5 * / I64(0x97c560ba6b0919b5), / * 6 * / I64(0x88b402f7fd7553ab), / * 7 * / I64(0xf64335bcf065d3a0), / * 8 * / I64 / * 9 * / I64(0xc7caba6e7c5382ed), / * 10 * / I64(0xb3f4e093db73a0b7), / * 11 * / I64(0xa21727db38cb0053), / * 12 * / I64(0x91ff83775423cc29), / * 13 * / I64(0x8380dea93da4bc82), / * 14 * / I64(0xece53cec4a314f00), / * 15 * / I64(0xd5605fcdcf32e217) , / * 16 * / I64(0xc0314325637a1978), / * 17 * / I64(0xad1c8eab5ee43ba2), / * 18 * / I64(0x9becce62836ac5b0), / * 19 * / I64(0x8c71dcd9ba0b495c), / * 20 * / I64(0xfd00b89747823938), / * 21 * / I64(0xe3e27a444d8d991a),}; static const signed short rgexp64Power10By16 [] = { //对于10 ^ 16和0.1 ^ 16 / * 1 * / 54, / * 2 * / 107, / * 3 * / 160, / * 4 * / 213, / * 5 * / 266, / * 6 * / 319 , / * 7 * / 373, / * 8 * / 426, / * 9 * / 479, / * 10 * / 532, / * 11 * / 585, / * 12 * / 638, / * 13 * / 691, / * 14 * / 745, / * 15 * / 798, / * 16 * / 851, / * 17 * / 904, / * 18 * / 957, / * 19 * / 1010, / * 20 * / 1064, / * 21 * / 1117,}; static unsigned DigitsToInt(wchar_t * p,int count) { wchar_t * end = p + count; unsigned res = * p - '0'; (p = p + 1; p< end; p ++) { res = 10 * res + * p - '0' } return res; } #define Mul32x32To64(a,b)((unsigned long long)((unsigned long)(a))*(unsigned long long)((unsigned long)(b))) static unsigned long long Mul64Lossy(unsigned long long a,unsigned long long b,int * pexp) { //这里可以放弃一些精度 - Mul64将被称为 //在转换过程中最多两次,所以错误不会将 //传播到结果的53个有效位中的任何一个 unsigned long long val = Mul32x32To64(a>>> ; 32,b> 32)+ (Mul32x32To64(a>> 32,b)>> 32)+ (Mul32x32To64(a,b> > 32); // normalize if((val& I64(0x8000000000000000))== 0){val return val; } void NumberToDouble(NUMBER * number,double * value) { unsigned long long val; int exp; wchar_t * src = number-> digits; int剩余; int total; int count; int scale; int absscale; int index; total =(int)wcslen(src); remaining = total; //跳过前导零 while(* src =='0'){ remaining--; src ++; } if(remaining == 0){ * value = 0; goto done; } count = min(剩余,9); 剩余 - =计数; val = DigitsToInt(src,count); if(remaining> 0){ count = min(remaining,9); 剩余 - =计数; //获得10 的非规格化幂无符号long =(无符号长)(rgval64Power10 [count-1]>(64 - rgexp64Power10 [count-1])) ; val = Mul32x32To64(val,mult)+ DigitsToInt(src + 9,count); } scale = number-> scale - (total - remaining); absscale = abs(scale); if(absscale> = 22 * 16){ // overflow / underflow *(unsigned long long *)value =(scale> 0)? I64(0x7FF0000000000000):0; goto done; } exp = 64; //规范化mantisa if((val& I64(0xFFFFFFFF00000000))== 0){val if((val& I64(0xFFFF000000000000))== 0){val if((val& I64(0xFF00000000000000))== 0){val if((val& I64(0xF000000000000000))== 0){val<< = 4; exp - = 4; } if((val& I64(0xC000000000000000))== 0){val<< = 2; exp - = 2; } if((val& I64(0x8000000000000000))== 0){val< index = absscale& 15; if(index){ int multexp = rgexp64Power10 [index-1]; //指数在反向和常规表之间共享 exp + =(scale< 0)? (-multexp + 1):multexp; unsigned long long multval = rgval64Power10 [index +((scale< 0)?15:0) - 1]; val = Mul64Lossy(val,multval,& exp); } index = absscale>> 4; if(index){ int multexp = rgexp64Power10By16 [index-1]; //指数在反向和常规表之间共享 exp + =(scale< 0)? (-multexp + 1):multexp; unsigned long long multval = rgval64Power10By16 [index +((scale< 0)?21:0) - 1]; val = Mul64Lossy(val,multval,& exp); } // round&缩小 if((unsigned long)val&(1< 10)) { // IEEE round to even unsigned long long tmp = val + ((1 11)& 1); if(tmp< val){ // overflow tmp =(tmp>> 1)| I64(0x8000000000000000); exp + = 1; } val = tmp; } val>> = 11; exp + = 0x3FE; if(exp if(exp // underflow val = 0; } else { //非规范化 val>> =(-exp + 1); } } else if(exp> = 0x7FF){ // overflow val = I64(0x7FF0000000000000); } else { val =((unsigned long long)exp< 52)+(val& I64(0x000FFFFFFFFFFFF)); } *(unsigned long long *)value = val; done: if(number-> sign)*(unsigned long long *)value | = I64(0x8000000000000000); } int main() { NUMBER number; number.precision = 15; double v = 0.84551240822557006; char * src = _ecvt(v,number.precision,& number.scale,& number.sign); int truncate = 0; //如果要截断 if(truncate) { while(* src&& src [strlen(src) - 1] =='0') { src [strlen(src) - 1] = 0; } } wchar_t * dst = number.digits; if(* src!='0'){ while(* src)* dst ++ = * src ++; } * dst ++ = 0; NumberToDouble(& number,& v); return 0; } Recently I have had to serialize a double into text, and then get it back. The value seems to not be equivalent:double d1 = 0.84551240822557006;string s = d1.ToString("R");double d2 = double.Parse(s);bool s1 = d1 == d2;// -> s1 is FalseBut according to MSDN: Standard Numeric Format Strings, the "R" option is supposed to guarantee round-trip safety.Why did this happen? 解决方案 I found the bug..NET does the following in clr\src\vm\comnumber.cpp:DoubleToNumber(value, DOUBLE_PRECISION, &number);if (number.scale == (int) SCALE_NAN) { gc.refRetVal = gc.numfmt->sNaN; goto lExit;}if (number.scale == SCALE_INF) { gc.refRetVal = (number.sign? gc.numfmt->sNegativeInfinity: gc.numfmt->sPositiveInfinity); goto lExit;}NumberToDouble(&number, &dTest);if (dTest == value) { gc.refRetVal = NumberToString(&number, 'G', DOUBLE_PRECISION, gc.numfmt); goto lExit;}DoubleToNumber(value, 17, &number);DoubleToNumber is pretty simple -- it just calls _ecvt, which is in the C runtime:void DoubleToNumber(double value, int precision, NUMBER* number){ WRAPPER_CONTRACT _ASSERTE(number != NULL); number->precision = precision; if (((FPDOUBLE*)&value)->exp == 0x7FF) { number->scale = (((FPDOUBLE*)&value)->mantLo || ((FPDOUBLE*)&value)->mantHi) ? SCALE_NAN: SCALE_INF; number->sign = ((FPDOUBLE*)&value)->sign; number->digits[0] = 0; } else { char* src = _ecvt(value, precision, &number->scale, &number->sign); wchar* dst = number->digits; if (*src != '0') { while (*src) *dst++ = *src++; } *dst = 0; }}It turns out that _ecvt returns the string 845512408225570.Notice the trailing zero? It turns out that makes all the difference!When the zero is present, the result actually parses back to 0.84551240822557006, which is your original number -- so it compares equal, and hence only 15 digits are returned.However, if I truncate the string at that zero to 84551240822557, then I get back 0.84551240822556994, which is not your original number, and hence it would return 17 digits.Proof: run the following 64-bit code (most of which I extracted from the Microsoft Shared Source CLI 2.0) in your debugger and examine v at the end of main:#include <stdlib.h>#include <string.h>#include <math.h>#define min(a, b) (((a) < (b)) ? (a) : (b))struct NUMBER { int precision; int scale; int sign; wchar_t digits[20 + 1]; NUMBER() : precision(0), scale(0), sign(0) {}};#define I64(x) x##LLstatic const unsigned long long rgval64Power10[] = { // powers of 10 /*1*/ I64(0xa000000000000000), /*2*/ I64(0xc800000000000000), /*3*/ I64(0xfa00000000000000), /*4*/ I64(0x9c40000000000000), /*5*/ I64(0xc350000000000000), /*6*/ I64(0xf424000000000000), /*7*/ I64(0x9896800000000000), /*8*/ I64(0xbebc200000000000), /*9*/ I64(0xee6b280000000000), /*10*/ I64(0x9502f90000000000), /*11*/ I64(0xba43b74000000000), /*12*/ I64(0xe8d4a51000000000), /*13*/ I64(0x9184e72a00000000), /*14*/ I64(0xb5e620f480000000), /*15*/ I64(0xe35fa931a0000000), // powers of 0.1 /*1*/ I64(0xcccccccccccccccd), /*2*/ I64(0xa3d70a3d70a3d70b), /*3*/ I64(0x83126e978d4fdf3c), /*4*/ I64(0xd1b71758e219652e), /*5*/ I64(0xa7c5ac471b478425), /*6*/ I64(0x8637bd05af6c69b7), /*7*/ I64(0xd6bf94d5e57a42be), /*8*/ I64(0xabcc77118461ceff), /*9*/ I64(0x89705f4136b4a599), /*10*/ I64(0xdbe6fecebdedd5c2), /*11*/ I64(0xafebff0bcb24ab02), /*12*/ I64(0x8cbccc096f5088cf), /*13*/ I64(0xe12e13424bb40e18), /*14*/ I64(0xb424dc35095cd813), /*15*/ I64(0x901d7cf73ab0acdc),};static const signed char rgexp64Power10[] = { // exponents for both powers of 10 and 0.1 /*1*/ 4, /*2*/ 7, /*3*/ 10, /*4*/ 14, /*5*/ 17, /*6*/ 20, /*7*/ 24, /*8*/ 27, /*9*/ 30, /*10*/ 34, /*11*/ 37, /*12*/ 40, /*13*/ 44, /*14*/ 47, /*15*/ 50,};static const unsigned long long rgval64Power10By16[] = { // powers of 10^16 /*1*/ I64(0x8e1bc9bf04000000), /*2*/ I64(0x9dc5ada82b70b59e), /*3*/ I64(0xaf298d050e4395d6), /*4*/ I64(0xc2781f49ffcfa6d4), /*5*/ I64(0xd7e77a8f87daf7fa), /*6*/ I64(0xefb3ab16c59b14a0), /*7*/ I64(0x850fadc09923329c), /*8*/ I64(0x93ba47c980e98cde), /*9*/ I64(0xa402b9c5a8d3a6e6), /*10*/ I64(0xb616a12b7fe617a8), /*11*/ I64(0xca28a291859bbf90), /*12*/ I64(0xe070f78d39275566), /*13*/ I64(0xf92e0c3537826140), /*14*/ I64(0x8a5296ffe33cc92c), /*15*/ I64(0x9991a6f3d6bf1762), /*16*/ I64(0xaa7eebfb9df9de8a), /*17*/ I64(0xbd49d14aa79dbc7e), /*18*/ I64(0xd226fc195c6a2f88), /*19*/ I64(0xe950df20247c83f8), /*20*/ I64(0x81842f29f2cce373), /*21*/ I64(0x8fcac257558ee4e2), // powers of 0.1^16 /*1*/ I64(0xe69594bec44de160), /*2*/ I64(0xcfb11ead453994c3), /*3*/ I64(0xbb127c53b17ec165), /*4*/ I64(0xa87fea27a539e9b3), /*5*/ I64(0x97c560ba6b0919b5), /*6*/ I64(0x88b402f7fd7553ab), /*7*/ I64(0xf64335bcf065d3a0), /*8*/ I64(0xddd0467c64bce4c4), /*9*/ I64(0xc7caba6e7c5382ed), /*10*/ I64(0xb3f4e093db73a0b7), /*11*/ I64(0xa21727db38cb0053), /*12*/ I64(0x91ff83775423cc29), /*13*/ I64(0x8380dea93da4bc82), /*14*/ I64(0xece53cec4a314f00), /*15*/ I64(0xd5605fcdcf32e217), /*16*/ I64(0xc0314325637a1978), /*17*/ I64(0xad1c8eab5ee43ba2), /*18*/ I64(0x9becce62836ac5b0), /*19*/ I64(0x8c71dcd9ba0b495c), /*20*/ I64(0xfd00b89747823938), /*21*/ I64(0xe3e27a444d8d991a),};static const signed short rgexp64Power10By16[] = { // exponents for both powers of 10^16 and 0.1^16 /*1*/ 54, /*2*/ 107, /*3*/ 160, /*4*/ 213, /*5*/ 266, /*6*/ 319, /*7*/ 373, /*8*/ 426, /*9*/ 479, /*10*/ 532, /*11*/ 585, /*12*/ 638, /*13*/ 691, /*14*/ 745, /*15*/ 798, /*16*/ 851, /*17*/ 904, /*18*/ 957, /*19*/ 1010, /*20*/ 1064, /*21*/ 1117,};static unsigned DigitsToInt(wchar_t* p, int count){ wchar_t* end = p + count; unsigned res = *p - '0'; for ( p = p + 1; p < end; p++) { res = 10 * res + *p - '0'; } return res;}#define Mul32x32To64(a, b) ((unsigned long long)((unsigned long)(a)) * (unsigned long long)((unsigned long)(b)))static unsigned long long Mul64Lossy(unsigned long long a, unsigned long long b, int* pexp){ // it's ok to losse some precision here - Mul64 will be called // at most twice during the conversion, so the error won't propagate // to any of the 53 significant bits of the result unsigned long long val = Mul32x32To64(a >> 32, b >> 32) + (Mul32x32To64(a >> 32, b) >> 32) + (Mul32x32To64(a, b >> 32) >> 32); // normalize if ((val & I64(0x8000000000000000)) == 0) { val <<= 1; *pexp -= 1; } return val;}void NumberToDouble(NUMBER* number, double* value){ unsigned long long val; int exp; wchar_t* src = number->digits; int remaining; int total; int count; int scale; int absscale; int index; total = (int)wcslen(src); remaining = total; // skip the leading zeros while (*src == '0') { remaining--; src++; } if (remaining == 0) { *value = 0; goto done; } count = min(remaining, 9); remaining -= count; val = DigitsToInt(src, count); if (remaining > 0) { count = min(remaining, 9); remaining -= count; // get the denormalized power of 10 unsigned long mult = (unsigned long)(rgval64Power10[count-1] >> (64 - rgexp64Power10[count-1])); val = Mul32x32To64(val, mult) + DigitsToInt(src+9, count); } scale = number->scale - (total - remaining); absscale = abs(scale); if (absscale >= 22 * 16) { // overflow / underflow *(unsigned long long*)value = (scale > 0) ? I64(0x7FF0000000000000) : 0; goto done; } exp = 64; // normalize the mantisa if ((val & I64(0xFFFFFFFF00000000)) == 0) { val <<= 32; exp -= 32; } if ((val & I64(0xFFFF000000000000)) == 0) { val <<= 16; exp -= 16; } if ((val & I64(0xFF00000000000000)) == 0) { val <<= 8; exp -= 8; } if ((val & I64(0xF000000000000000)) == 0) { val <<= 4; exp -= 4; } if ((val & I64(0xC000000000000000)) == 0) { val <<= 2; exp -= 2; } if ((val & I64(0x8000000000000000)) == 0) { val <<= 1; exp -= 1; } index = absscale & 15; if (index) { int multexp = rgexp64Power10[index-1]; // the exponents are shared between the inverted and regular table exp += (scale < 0) ? (-multexp + 1) : multexp; unsigned long long multval = rgval64Power10[index + ((scale < 0) ? 15 : 0) - 1]; val = Mul64Lossy(val, multval, &exp); } index = absscale >> 4; if (index) { int multexp = rgexp64Power10By16[index-1]; // the exponents are shared between the inverted and regular table exp += (scale < 0) ? (-multexp + 1) : multexp; unsigned long long multval = rgval64Power10By16[index + ((scale < 0) ? 21 : 0) - 1]; val = Mul64Lossy(val, multval, &exp); } // round & scale down if ((unsigned long)val & (1 << 10)) { // IEEE round to even unsigned long long tmp = val + ((1 << 10) - 1) + (((unsigned long)val >> 11) & 1); if (tmp < val) { // overflow tmp = (tmp >> 1) | I64(0x8000000000000000); exp += 1; } val = tmp; } val >>= 11; exp += 0x3FE; if (exp <= 0) { if (exp <= -52) { // underflow val = 0; } else { // denormalized val >>= (-exp+1); } } else if (exp >= 0x7FF) { // overflow val = I64(0x7FF0000000000000); } else { val = ((unsigned long long)exp << 52) + (val & I64(0x000FFFFFFFFFFFFF)); } *(unsigned long long*)value = val;done: if (number->sign) *(unsigned long long*)value |= I64(0x8000000000000000);}int main(){ NUMBER number; number.precision = 15; double v = 0.84551240822557006; char *src = _ecvt(v, number.precision, &number.scale, &number.sign); int truncate = 0; // change to 1 if you want to truncate if (truncate) { while (*src && src[strlen(src) - 1] == '0') { src[strlen(src) - 1] = 0; } } wchar_t* dst = number.digits; if (*src != '0') { while (*src) *dst++ = *src++; } *dst++ = 0; NumberToDouble(&number, &v); return 0;} 这篇关于为什么通过字符串进行往返转换是不安全的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
09-15 04:41