toint32 - string to long c#
¿Cómo analizar el cero firmado? (3)
¿Es posible analizar el cero firmado? He intentado varios enfoques pero nadie da el resultado adecuado:
float test1 = Convert.ToSingle("-0.0");
float test2 = float.Parse("-0.0");
float test3;
float.TryParse("-0.0", out test3);
Si uso el valor directamente inicializado, está bien:
float test4 = -0.0f;
Entonces, el problema parece estar en los procedimientos de análisis de c #. Espero que alguien pueda decir si hay alguna opción o solución para eso.
La diferencia solo se podía ver al convertir a binario:
var bin= BitConverter.GetBytes(test4);
Resultados actualizados
Resumen
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
Verificadamente, Mine Unchecked
es bueno para números más pequeños, sin embargo, cuando se usan potencias al final del cálculo para hacer números fraccionarios, no funciona para combinaciones de dígitos más grandes, también porque sus potencias de 10 solo juegan con ai solo una gran instrucción de cambio que lo hace un poco más rápido.
Fondo
Ok, debido a los diversos comentarios que recibí, y el trabajo que puse en esto. Pensé que reescribiría esta publicación con los puntos de referencia más precisos que pudiera obtener. Y toda la lógica detrás de ellos.
Entonces, cuando surgió esta primera pregunta, id había escrito mi propio marco de referencia y, a menudo, al igual que escribir un analizador rápido para estas cosas y usar código inseguro, 9 de cada 10 puedo obtener estas cosas más rápido que el equivalente del marco.
Al principio esto fue fácil, solo escriba una lógica simple para analizar los números con puntos decimales, y lo hice bastante bien, sin embargo, los resultados iniciales no fueron tan precisos como podrían haber sido, porque mis datos de prueba solo usaban el ''f ''especificador de formato, y convertiría números de precisión más grandes en formatos cortos con solo 0''s.
Al final, simplemente no pude escribir un análisis confiable para tratar con la notación del exponente Ie 1.2324234233E+23
. La única forma en que podía hacer que funcionaran las matemáticas era usando BIGINTEGER
y muchos hacks para forzar la precisión correcta en un valor de punto flotante. Esto se volvió súper lento. Incluso fui a las especificaciones de IEEE flotantes y traté de hacer los cálculos para construirlos en bits, esto no fue tan difícil, y sin embargo la fórmula tiene bucles y fue complicado hacerlo bien. Al final tuve que renunciar a la notación exponencial.
Así que esto es lo que terminé con
Mi marco de prueba ejecuta en datos de entrada una lista de 10000 puntos como cadenas, que se comparte entre las pruebas y se genera para cada ejecución de prueba. Una ejecución de prueba solo se realiza a través de cada prueba (recordando que son los mismos datos para cada prueba) y agrega Los resultados los promedian luego. Esto es lo mejor que puede conseguir. Puedo aumentar las carreras a 1000 o más, sin embargo, realmente no cambian. En este caso, debido a que estamos probando un método que toma básicamente una variable (una representación de cadena de un flotador), no hay un punto de escalamiento de este, ya que no está configurado, sin embargo, puedo modificar la entrada para atender diferentes longitudes de flotadores, es decir, cadenas que son 10, 20 hasta 98 dígitos. Recordar un flotador solo sube a 38 de todos modos.
Para verificar los resultados, utilicé lo siguiente, previamente escribí una unidad de prueba que cubre todos los flotantes concebibles, y funcionan, excepto por una variación en la que utilizo Poderes para calcular la parte decimal del número.
Tenga en cuenta que mi marco solo prueba 1 conjunto de resultados y no forma parte del marco.
private bool Action(List<float> floats, List<float> 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;
}
En este caso, estoy probando nuevamente 3 tipos de entrada como se muestra a continuación
Preparar
// 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);
}
Datos de prueba 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<string> GenerateInput10(int scale)
{
var result = new List<string>(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;
}
Datos de prueba 2
// basically that max value for a float
public static List<string> 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;
}
Datos de prueba 3
// Lets take this to the limit
public static List<string> 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;
}
Estas son las pruebas que utilicé.
Evk
private float ParseMyFloat(string value)
{
var result = float.Parse(value, CultureInfo.InvariantCulture);
if (result == 0f && value.TrimStart()
.StartsWith("-"))
{
result = -0f;
}
return result;
}
Seguro de minas
Lo llamo seguro ya que trata de verificar si hay cadenas inválidas
[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;
}
}
Desenfrenado
Esto falla para cadenas más grandes, pero está bien para las más pequeñas
[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 sin marcar
[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)
Respuesta original
Suponiendo que no necesita un método TryParse
, me las arreglé para usar punteros y análisis personalizados para lograr lo que creo que quiere.
El punto de referencia usa una lista de 1,000,000 flotadores aleatorios y ejecuta cada versión 100 veces, todas las versiones usan los mismos datos
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
Picado por brevedad
Tenga en cuenta que estas dos versiones no pueden lidiar con el formato extendido, NaN
, +Infinity
o -Infinity
. Sin embargo, no sería difícil de implementar con poca sobrecarga.
He comprobado esto bastante bien, aunque debo admitir que no he escrito ninguna prueba de unidad, así que utilícelo bajo su propio riesgo.
Descargo de responsabilidad , creo que la versión de Evk''s StartsWith
probablemente podría estar más optimizada, sin embargo, seguirá siendo (en el mejor de los casos) un poco más lenta que float.Parse
Creo que no hay manera de forzar que float.Parse
(o Convert.ToSingle
) respete el cero negativo. Simplemente funciona así (ignora el signo en este caso). Así que tienes que comprobarlo tú mismo, por ejemplo:
string target = "-0.0";
float result = float.Parse(target, CultureInfo.InvariantCulture);
if (result == 0f && target.TrimStart().StartsWith("-"))
result = -0f;
Si miramos el código fuente de coreclr, veremos (omitiendo todas las partes irrelevantes):
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;
}
Como puede ver, si la mantisa y el exponente son ambos cero, el valor se asigna explícitamente a 0
. Así que no hay forma de cambiar eso.
La implementación completa de .NET tiene NumberBufferToDouble
como InternalCall
(implementada en C / C ++ puro), pero asumo que hace algo similar.
Puedes probar esto:
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));