Можно ли разобрать нулевой знак? Я попробовал несколько подходов, но никто не дал правильный результат:
Если я использую значение, непосредственно инициализированное, это просто отлично:
Таким образом, проблема заключается в процедурах синтаксического анализа С#. Надеюсь, кто-то может сказать, есть ли какой-то вариант или обходной путь для этого.
Ответ 2
Обновленные результаты
Резюме
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
Разумеется, Mine Unchecked
хорош для меньших чисел, однако при использовании полномочий в конце вычисления, чтобы делать дробные числа, он не работает для более крупных комбинаций цифр, также потому, что его просто силы 10 он играет с ai просто большим оператором переключения, который делает его немного быстрее.
Фон
Хорошо из-за различных комментариев, которые я получил, и работы, которые я вложил в это. Я думал, что Id переписывает этот пост с самыми точными критериями, которые я мог бы получить. И вся логика, стоящая за ними
Поэтому, когда этот первый вопрос возник, id написал мою собственную базовую платформу и часто так же, как писать быстрый парсер для этих вещей и используя небезопасный код, 9 раз из 10 я могу получить этот материал быстрее, чем эквивалент структуры.
Сначала это было легко, просто напишите простую логику для разбора чисел с десятичными точками, и я сделал очень хорошо, однако исходные результаты были такими же точными, какими они могли быть, потому что мои тестовые данные просто использовали спецификатор формата f, и превратит более высокие числа точности в короткие форматы только с 0.
В конце концов, я просто не мог написать надежные анализы, чтобы иметь дело с показателем экспоненты. Ie 1.2324234233E+23
. Единственный способ, которым я мог заставить математику работать, - использовать BIGINTEGER
и множество хаков, чтобы заставить правильную точность в значение с плавающей запятой. Это оказалось очень медленным. Я даже пошел в спецификации float IEEE и попытался выполнить математику, чтобы построить его в битах, это было не так сложно, и, тем не менее, формула имеет циклы в нем и была сложной для правильного. В конце концов я должен был отказаться от нотации экспоненты.
Так вот что я в итоге
В моей тестовой структуре на входных данных запущен список из 10000 флепок в виде строк, которые разделяются между тестами и сгенерированы для каждого тестового прогона. Пробный тест проходит через каждый тест (запоминая его одни и те же данные для каждого теста) и добавляет то результаты затем усредняют их. Это примерно так же хорошо, как может. Я могу увеличить количество прогонов до 1000 или больше факторов, но они действительно не изменяются. В этом случае, поскольку мы тестируем метод, который принимает в основном одну переменную (строковое представление поплавка), нет никакого масштабирования этой точки, поскольку она не установлена на основе, однако я могу настроить ввод, чтобы обслуживать разные длины поплавков, т.е. строки, составляющие 10, 20, вплоть до 98 цифр. Помните, что поплавок доходит до 38 в любом случае.
Чтобы проверить результаты, я использовал следующее: ранее я написал тестовую единицу, которая охватывает все возможные поплавки, и они работают, за исключением вариации, когда я использую Powers для вычисления десятичной части числа.
Обратите внимание, что моя инфраструктура проверяет только 1 набор результатов, а его не входит в структуру
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;
}
В этом случае im test снова 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<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;
}
Данные испытаний 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;
}
Тестовые данные 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;
}
Это те тесты, которые я использовал
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
. Однако это было бы непросто реализовать при небольших затратах.
Я проверил это довольно хорошо, хотя я должен признать, что я не написал никаких модульных тестов, поэтому используйте их на свой страх и риск.
Отказ от ответственности, я думаю, что версия Evk StartsWith
возможно, была бы более оптимизирована, однако она все равно будет (в лучшем случае) немного медленнее, чем float.Parse