Получить число цифр в unsigned long integer С#

Я пытаюсь определить количество цифр в числе С# ulong, я пытаюсь сделать это, используя некоторую математическую логику, а не используя ToString(). Length. Я не сравнивал два подхода, но видел другие сообщения об использовании System.Math.Floor(System.Math.Log10 (number)) + 1, чтобы определить количество цифр. Кажется, что он работает нормально, пока я не перейду с 999999999999997 до 999999999999998, и в этот момент я начну получать неверный счет.

Кто-нибудь сталкивался с этой проблемой раньше?

Я видел похожие записи с акцентом Java @Почему log (1000)/log (10) - это не то же самое, что и log10 (1000)?, а также post @Как получить отдельные цифры номера int?, который указывает, как я мог бы достичь того же, используя оператор%, но с гораздо большим количеством кода

Вот код, который я использовал для имитации этого

Action<ulong> displayInfo = number => 
 Console.WriteLine("{0,-20} {1,-20} {2,-20} {3,-20} {4,-20}", 
  number, 
  number.ToString().Length, 
  System.Math.Log10(number), 
  System.Math.Floor(System.Math.Log10(number)),
  System.Math.Floor(System.Math.Log10(number)) + 1);

Array.ForEach(new ulong[] {
 9U,
 99U,
 999U,
 9999U,
 99999U,
 999999U,
 9999999U,
 99999999U,
 999999999U,
 9999999999U,
 99999999999U,
 999999999999U,
 9999999999999U,
 99999999999999U,
 999999999999999U,
 9999999999999999U,
 99999999999999999U,
 999999999999999999U,
 9999999999999999999U}, displayInfo);

Array.ForEach(new ulong[] {
 1U,
 19U,
 199U,
 1999U,
 19999U,
 199999U,
 1999999U,
 19999999U,
 199999999U,
 1999999999U,
 19999999999U,
 199999999999U,
 1999999999999U,
 19999999999999U,
 199999999999999U,
 1999999999999999U,
 19999999999999999U,
 199999999999999999U,
 1999999999999999999U
}, displayInfo);

Заранее спасибо

Пат

Ответ 1

log10 будет включать преобразование с плавающей точкой - следовательно, ошибка округления. Ошибка довольно мала для двойника, но для крупного целого является большой суммой!

Исключая метод .ToString() и метод с плавающей запятой, да, я думаю, вам придется использовать итеративный метод, но я бы использовал целочисленное деление, а не по модулю.

Целое число делится на 10. Является ли результат > 0? Если так повторяться. Если нет, остановитесь. Количество цифр - это количество требуемых итераций.

Eg. 5 → 0; 1 итерация = 1 разряд.

1234 → 123 → 12 → 1 → 0; 4 итерации = 4 цифры.

Ответ 2

Я бы использовал ToString().Length, если вы не знаете, что это будет называться миллионы раз.

"преждевременная оптимизация - корень всего зла" - Дональд Кнут

Ответ 3

Из документации:

По умолчанию двойное значение содержит 15 десятичные цифры точности, хотя сохраняется максимум 17 цифр внутри.

Я подозреваю, что вы столкнулись с прецизионными ограничениями. Ваше значение 999,999,999,999,998, вероятно, находится на пределе точности. И поскольку ulong должен быть преобразован в double перед вызовом Math.Log10, вы увидите эту ошибку.

Ответ 4

Другие ответы опубликовали, почему это происходит.

Вот пример довольно быстрого определения "длины" целого числа (некоторые исключения исключены). Это само по себе не очень интересно, но я включаю его здесь, потому что использование этого метода в сочетании с Log10 может получить точность "идеально" для всего диапазона беззнакового длинного без необходимости второго вызова журнала.

// the lookup would only be generated once
// and could be a hard-coded array literal
ulong[] lookup = Enumerable.Range(0, 20)
    .Select((n) => (ulong)Math.Pow(10, n)).ToArray();
ulong x = 999;
int i = 0;
for (; i < lookup.Length; i++) {
    if (lookup[i] > x) {
        break;
    }
}
// i is length of x "in a base-10 string"
// does not work with "0" or negative numbers

Этот подход к таблице поиска может быть легко преобразован в любую базу. Этот метод должен быть более быстрым, чем итеративный подход по принципу "разделяйся", но профилирование остается как упражнение для читателя. (Прямая if-then ветвь, разбитая на "группы", скорее всего, еще быстрее, но слишком много повторяющегося набора текста для моих вкусов.)

Счастливое кодирование.