如何解析签名零?

是否可以解析有符号的零? 我尝试了几种方法,但没有人给出正确的结果:

float test1 = Convert.ToSingle("-0.0"); float test2 = float.Parse("-0.0"); float test3; float.TryParse("-0.0", out test3); 

如果我使用直接初始化的值就可以了:

 float test4 = -0.0f; 

所以问题似乎在于c#的解析过程。 我希望有人可以判断是否有一些选项或解决方法。

只能通过转换为二进制来看到差异:

 var bin= BitConverter.GetBytes(test4); 

我认为没有办法强制float.Parse (或Convert.ToSingle )尊重负零。 它只是这样工作(在这种情况下忽略符号)。 所以你必须自己检查一下,例如:

 string target = "-0.0"; float result = float.Parse(target, CultureInfo.InvariantCulture); if (result == 0f && target.TrimStart().StartsWith("-")) result = -0f; 

如果我们查看coreclr的源代码 ,我们会看到(跳过所有不相关的部分):

 private static bool NumberBufferToDouble(ref NumberBuffer number, ref double value) { double d = NumberToDouble(ref number); uint e = DoubleHelper.Exponent(d); ulong m = DoubleHelper.Mantissa(d); if (e == 0x7FF) { return false; } if (e == 0 && m == 0) { d = 0; // < relevant part } value = d; return true; } 

如您所见,如果尾数和指数都为零,则将值显式指定为0 。 所以你无法改变它。

完整的.NET实现将NumberBufferToDouble作为InternalCall (在纯C \ C ++中实现),但我认为它做了类似的事情。

更新的结果

摘要

 Mode : Release Test Framework : .NET Framework 4.7.1 Benchmarks runs : 100 times (averaged/scale) Tests limited to 10 digits Name | Time | Range | StdDev | Cycles | Pass ----------------------------------------------------------------------- Mine Unchecked | 9.645 ms | 0.259 ms | 0.30 | 32,815,064 | Yes Mine Unchecked2 | 10.863 ms | 1.337 ms | 0.35 | 36,959,457 | Yes Mine Safe | 11.908 ms | 0.993 ms | 0.53 | 40,541,885 | Yes float.Parse | 26.973 ms | 0.525 ms | 1.40 | 91,755,742 | Yes Evk | 31.513 ms | 1.515 ms | 7.96 | 103,288,681 | Base Test Limited to 38 digits Name | Time | Range | StdDev | Cycles | Pass ----------------------------------------------------------------------- Mine Unchecked | 17.694 ms | 0.276 ms | 0.50 | 60,178,511 | No Mine Unchecked2 | 23.980 ms | 0.417 ms | 0.34 | 81,641,998 | Yes Mine Safe | 25.078 ms | 0.124 ms | 0.63 | 85,306,389 | Yes float.Parse | 36.985 ms | 0.052 ms | 1.60 | 125,929,286 | Yes Evk | 39.159 ms | 0.406 ms | 3.26 | 133,043,100 | Base Test Limited to 98 digits (way over the range of a float) Name | Time | Range | StdDev | Cycles | Pass ----------------------------------------------------------------------- Mine Unchecked2 | 46.780 ms | 0.580 ms | 0.57 | 159,272,055 | Yes Mine Safe | 48.048 ms | 0.566 ms | 0.63 | 163,601,133 | Yes Mine Unchecked | 48.528 ms | 1.056 ms | 0.58 | 165,238,857 | No float.Parse | 55.935 ms | 1.461 ms | 0.95 | 190,456,039 | Yes Evk | 56.636 ms | 0.429 ms | 1.75 | 192,531,045 | Base 

可validation的是, Mine Unchecked对于较小的数字是好的,但是当在计算结束时使用幂来做分数时,它对于较大的数字组合不起作用,也因为它的正常幂10只用于ai只是一个大的开关语句使它稍快一些。

背景

好吧,因为我得到的各种评论,以及我对此所做的工作。 我以为我会用最准确的基准来重写这篇文章。 以及他们背后的所有逻辑

因此,当第一个问题出现时,id已经编写了我自己的基准测试框架,并且通常就像为这些事情编写快速解析器并使用不安全的代码一样,9次中有10次我可以比相应的框架更快地获得这些东西。

起初这很容易,只需编写一个简单的逻辑来解析带有小数点位置的数字,而且我做得很好,但最初的结果并不像它们本来那样准确,因为我的测试数据只是使用’f ‘格式说明符,并将更大的精度数字转换为只有0的短格式。

最后,我无法编写可靠的解析来处理指数表示法1.2324234233E+23 。 我可以使数学BIGINTEGER的唯一方法是使用BIGINTEGER和许多黑客来强制将正确的精度转换为浮点值。 这变得超级慢。 我甚至去了浮动IEEE规范,并尝试用数学来构造它,这不是那么难,但是公式中有循环并且很难做到正确。 最后我不得不放弃指数表示法。

所以这就是我最终的结果

我的测试框架在输入数据上运行10000个flaots作为字符串的列表,它们在测试中共享并为每个测试运行生成。测试运行只是通过每个测试(记住每个测试的相同数据)并添加结果然后取平均值。 这几乎和它一样好。 我可以将运行增加到1000或更多因素,但它们并没有真正改变。 在这种情况下,因为我们正在测试一个基本上只接受一个变量的方法(浮点数的字符串表示),所以没有基于未设置的点进行缩放,但是我可以调整输入以适应不同长度的浮点数,即字符串为10,20直到98位数。 记住一个浮点数最多只能达到38。

为了检查我使用以下内容的结果,我之前已经编写了一个测试单元,涵盖了每个可以想到的浮点数,并且它们可以工作,除了我使用Powers计算数字的小数部分的变体。

注意,我的框架只测试1个结果集,而不是框架的一部分

 private bool Action(List floats, List list) { if (floats.Count != list.Count) return false; // sanity check for (int i = 0; i < list.Count; i++) { // nan is a special case as there is more than one possible bit value // for it if ( floats[i] != list[i] && !float.IsNaN(floats[i]) && !float.IsNaN(list[i])) return false; } return true; } 

在这种情况下,我再次测试3种类型的输入,如下所示

建立

 // numberDecimalDigits specifies how long the output will be private static NumberFormatInfo GetNumberFormatInfo(int numberDecimalDigits) { return new NumberFormatInfo { NumberDecimalSeparator = ".", NumberDecimalDigits = numberDecimalDigits }; } // generate a random float by create an int, and converting it to float in pointers private static unsafe string GetRadomFloatString(IFormatProvider formatInfo) { var val = Rand.Next(0, int.MaxValue); if (Rand.Next(0, 2) == 1) val *= -1; var f = *(float*)&val; return f.ToString("f", formatInfo); } 

测试数据1

 // limits the out put to 10 characters // also because of that it has to check for trunced vales and // regenerates them public static List GenerateInput10(int scale) { var result = new List(scale); while (result.Count < scale) { var val = GetRadomFloatString(GetNumberFormatInfo(10)); if (val != "0.0000000000") result.Add(val); } result.Insert(0, (-0f).ToString("f", CultureInfo.InvariantCulture)); result.Insert(0, "-0"); result.Insert(0, "0.00"); result.Insert(0, float.NegativeInfinity.ToString("f", CultureInfo.InvariantCulture)); result.Insert(0, float.PositiveInfinity.ToString("f", CultureInfo.InvariantCulture)); return result; } 

测试数据2

 // basically that max value for a float public static List GenerateInput38(int scale) { var result = Enumerable.Range(1, scale) .Select(x => GetRadomFloatString(GetNumberFormatInfo(38))) .ToList(); result.Insert(0, (-0f).ToString("f", CultureInfo.InvariantCulture)); result.Insert(0, "-0"); result.Insert(0, float.NegativeInfinity.ToString("f", CultureInfo.InvariantCulture)); result.Insert(0, float.PositiveInfinity.ToString("f", CultureInfo.InvariantCulture)); return result; } 

测试数据3

 // Lets take this to the limit public static List GenerateInput98(int scale) { var result = Enumerable.Range(1, scale) .Select(x => GetRadomFloatString(GetNumberFormatInfo(98))) .ToList(); result.Insert(0, (-0f).ToString("f", CultureInfo.InvariantCulture)); result.Insert(0, "-0"); result.Insert(0, float.NegativeInfinity.ToString("f", CultureInfo.InvariantCulture)); result.Insert(0, float.PositiveInfinity.ToString("f", CultureInfo.InvariantCulture)); return result; } 

这些是我用过的测试

EVK

 private float ParseMyFloat(string value) { var result = float.Parse(value, CultureInfo.InvariantCulture); if (result == 0f && value.TrimStart() .StartsWith("-")) { result = -0f; } return result; } 

我的安全

我把它称为安全,因为它试图检查无效的字符串

 [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe float ParseMyFloat(string value) { double result = 0, dec = 0; if (value[0] == 'N' && value == "NaN") return float.NaN; if (value[0] == 'I' && value == "Infinity")return float.PositiveInfinity; if (value[0] == '-' && value[1] == 'I' && value == "-Infinity")return float.NegativeInfinity; fixed (char* ptr = value) { char* l, e; char* start = ptr, length = ptr + value.Length; if (*ptr == '-') start++; for (l = start; *l >= '0' && *l <= '9' && l < length; l++) result = result * 10 + *l - 48; if (*l == '.') { char* r; for (r = length - 1; r > l && *r >= '0' && *r <= '9'; r--) dec = (dec + (*r - 48)) / 10; if (l != r) throw new FormatException($"Invalid float : {value}"); } else if (l != length) throw new FormatException($"Invalid float : {value}"); result += dec; return *ptr == '-' ? (float)result * -1 : (float)result; } } 

未选中

这对于较大的字符串会失败,但对于较小的字符串则可以

 [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe float ParseMyFloat(string value) { if (value[0] == 'N' && value == "NaN") return float.NaN; if (value[0] == 'I' && value == "Infinity") return float.PositiveInfinity; if (value[0] == '-' && value[1] == 'I' && value == "-Infinity") return float.NegativeInfinity; fixed (char* ptr = value) { var point = 0; double result = 0, dec = 0; char* c, start = ptr, length = ptr + value.Length; if (*ptr == '-') start++; for (c = start; c < length && *c != '.'; c++) result = result * 10 + *c - 48; if (*c == '.') { point = (int)(length - 1 - c); for (c++; c < length; c++) dec = dec * 10 + *c - 48; } // MyPow is just a massive switch statement if (dec > 0) result += dec / MyPow(point); return *ptr == '-' ? (float)result * -1 : (float)result; } } 

未经检查2

 [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe float ParseMyFloat(string value) { if (value[0] == 'N' && value == "NaN") return float.NaN; if (value[0] == 'I' && value == "Infinity") return float.PositiveInfinity; if (value[0] == '-' && value[1] == 'I' && value == "-Infinity") return float.NegativeInfinity; fixed (char* ptr = value) { double result = 0, dec = 0; char* c, start = ptr, length = ptr + value.Length; if (*ptr == '-') start++; for (c = start; c < length && *c != '.'; c++) result = result * 10 + *c - 48; // this division seems unsafe for a double, // however i have tested it with every float and it works if (*c == '.') for (var d = length - 1; d > c; d--) dec = (dec + (*d - 48)) / 10; result += dec; return *ptr == '-' ? (float)result * -1 : (float)result; } } 

Float.parse

 float.Parse(t, CultureInfo.InvariantCulture) 

原始答案

假设你不需要TryParse方法,我设法使用指针和自定义解析来实现我想你想要的。

基准测试使用1,000,000个随机浮点数列表,每个版本运行100次,所有版本使用相同的数据

 Test Framework : .NET Framework 4.7.1 Scale : 1000000 Name | Time | Delta | Deviation | Cycles ---------------------------------------------------------------------- Mine Unchecked2 | 45.585 ms | 1.283 ms | 1.70 | 155,051,452 Mine Unchecked | 46.388 ms | 1.812 ms | 1.17 | 157,751,710 Mine Safe | 46.694 ms | 2.651 ms | 1.07 | 158,697,413 float.Parse | 173.229 ms | 4.795 ms | 5.41 | 589,297,449 Evk | 287.931 ms | 7.447 ms | 11.96 | 979,598,364 

切碎为了简洁

注意 ,这两个版本都不能处理扩展格式, NaN+Infinity-Infinity 。 但是,以较小的开销实现起来并不困难。

我已经很好地检查了这一点,但我必须承认我没有编写任何unit testing,所以使用风险自负。

免责声明 ,我认为Evk的StartsWith版本可能会更加优化,但它仍然(最好)稍微慢于float.Parse

你可以试试这个:

 string target = "-0.0"; decimal result= (decimal.Parse(target, System.Globalization.NumberStyles.AllowParentheses | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowTrailingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowLeadingSign));