Получение длины int

#rust

Вопрос:

Я пытаюсь получить длину (количество цифр при интерпретации в десятичном формате) int в rust. Я нашел способ сделать это, однако ищу метод, который исходит из самого примитива. Это то, что у меня есть:

 let num = 90.to_string();
println!("num: {}", num.chars().count())
// num: 2
 

Я смотрю на https://docs.rs/digits/0.3.3/digits/struct.Метод Digits.html#.длина. это хороший кандидат? Как мне его использовать? Или есть другие ящики, которые делают это за меня?

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

Комментарии:

1. Это 90.to_string().len() то, что тебе нужно?

2. мне нравится этот @hzqelf — идеальным случаем была бы функция, которая не меняет тип на строку

Ответ №1:

Вы можете сделать цикл и проверить, как часто вы можете делить число на 10, прежде чем оно превратится в одну цифру. Или в другом направлении (поскольку деление происходит медленнее, чем умножение), проверьте, как часто вы можете умножать 10*10*...*10 , пока не достигнете нужного числа:

 fn length(n: u32, base: u32) -> u32 {
    let mut power = base;
    let mut count = 1;
    while n >= power {
        count  = 1;
        if let Some(new_power) = power.checked_mul(base) {
            power = new_power;
        } else {
            break;
        }
    }
    count
}
 

С ночной ржавчиной (или в будущем, когда int_log функция стабилизируется) вы можете использовать:

 #![feature(int_log)]

n.checked_log10().unwrap_or(0)   1
 

Комментарии:

1. я больше искал что-то вроде одного лайнера. Я не могу избавиться от мысли, что такой один лайнер должен существовать :/ . тем не менее, отличный ответ

2. @AthulMuralidhar Мне любопытно, что вы находите полезным с одним лайнером в таком контексте.

3. Не нужно беспокоиться о том, что деление будет медленнее, чем умножение, если вы делите на константу. В любом случае оптимизатор будет представлять деление как умножение на обратное.

4. @пользователь На какую целочисленную константу вы можете умножить a u32 , чтобы разделить ее на 10?

5. Компиляторы иногда преобразуются более агрессивно, чем вы думаете. Оказывается, что моя length функция, вызываемая с константой времени компиляции 10, вообще ничего не умножает; она просто превращается в серию if операторов

Ответ №2:

Вот однострочный, который не требует строк или плавающей запятой:

 println!("num: {}", successors(Some(n), |amp;n| (n >= 10).then(|| n / 10)).count());
 

Он просто подсчитывает, сколько раз начальное число нужно разделить на 10, чтобы достичь 0.


РЕДАКТИРОВАТЬ: первая версия этого ответа использовалась iterate из (превосходного и настоятельно рекомендуемого) itertools ящика, но @trentcl указал, что successors из stdlib делает то же самое. Для справки, вот версия с использованием iterate :

 println!("num: {}", iterate(n, |amp;n| n / 10).take_while(|amp;n| n > 0).count().max(1));
 

Комментарии:

1. Вы могли бы сделать то же самое в std с. successors std -только версия: successors(Some(n), |amp;n| (n >= 10).then(|| n / 10)).count() (этот комментарий не является советом по программированию)

2. @trentcl Приятно, спасибо! На самом деле я искал что-то подобное successors() , но не смог найти и остановился на itertools::iterate() этом .

Ответ №3:

Вот (едва ли) однострочный текст, который быстрее, чем преобразование строк с использованием std::iter:

 let some_int = 9834;
let decimal_places = (0..).take_while(|i| 10u64.pow(*i) <= some_int).count();
 

Комментарии:

1. Аккуратно, но имеет ошибку переполнения для входных данных, превышающих 1 миллиард.

2. @Daniel спасибо, сменил базу на u64

3. Переполнение все еще существует, оно просто перемещено на входы, превышающие или равные 10 квинтиллионам. play.rust-lang.org/…

4. Верно, но если вход остается i/u32, все в порядке.

5. Или нет, это сделало some_int выводимым как u64 (потому что так и должно быть).. черт

Ответ №4:

Первый метод, приведенный ниже, основан на следующей формуле, где a и b являются логарифмическими основаниями.

 log<a>( x ) = log<b>( x ) / log<b>( a )

log<a>( x ) = log<2>( x ) / log<2>( a )  // Substituting 2 for `b`.
 

Следующая функция может быть применена для определения количества цифр для оснований, которые имеют степень 2. Этот подход очень быстрый.

 fn num_digits_base_pow2(n: u64, b: u32) -> u32
{
    (63 - n.leading_zeros()) / (31 - b.leading_zeros())   1
}
 

Биты подсчитываются как n для (числа, которое мы хотим представить), так и b для (базы), чтобы найти их значения уровня log2. Затем скорректированное соотношение этих значений дает значение журнала потолка в нужной базе.

Для подхода общего назначения к нахождению количества цифр для произвольных оснований должно быть достаточно следующего.

 fn num_digits(n: u64, b: u32) -> u32
{
    (n as f64).log(b as f64).ceil() as u32
}
 

Комментарии:

1. С точки зрения производительности, с плавающей запятой log -это одна инструкция HW, которая будет быстрее, чем цикл SW. Однако вы можете заменить свои while петли с помощью leading_zeros : n_floor_log2 = 32-x.leading_zeros()

Ответ №5:

Хорошее свойство чисел, которое всегда полезно иметь в виду, состоит в том, что количество цифр, необходимых для записи числа $x$ в базе $n$, на самом деле равно $lceil log_n(x 1) rceil$.

Поэтому можно просто написать следующую функцию (обратите внимание на приведение от u32 к f32 , так как целые числа не имеют функции журнала).

 fn length(n: u32, base: u32) -> u32 {
    let n = (n 1) as f32;
    n.log(base as f32).ceil() as u32
}
 

Вы можете легко адаптировать его для отрицательных чисел. Для чисел с плавающей запятой это может быть немного (т. Е. Намного) сложнее.

Чтобы принять во внимание комментарий Даниэля о патологических случаях, возникающих при использовании f32, обратите внимание, что в случае ночной ржавчины целые числа имеют метод логарифма. (Обратите внимание, что, имо, это детали реализации, и вам следует больше сосредоточиться на понимании алгоритма, чем на реализации.):

 #![feature(int_log)]

fn length(n: u32, base: u32) -> u32 {
    n.log(base)   1
}
 

Комментарии:

1. Не все u32 вписывается в f32 ошибку без округления, поэтому в некоторых случаях ваше решение возвращает неправильный результат, например, длина(0x8000047f, 2) = 31, но длина(0x80000480, 2) = 32. Кроме того, у вас есть ошибка переполнения, (n 1) сбой для n=0xffffffff

2. @Daniel Я технически согласен с вами (и я соответствующим образом обновил ответ). Тем не менее, ОП, возможно, захочет взглянуть на восторг Хакера за хорошие математические трюки, подобные этому : en.wikipedia.org/wiki/Hacker’s_Delight

3. Проблема с этими «простыми» подходами заключается в том, что они часто просто не работают. Например, это возвращает 5 как для 9999, так и для 10000.

4. @user4815162342 Я с вами не согласен. Ошибка, о которой вы упомянули, связана только с тем, что я быстро написал пример (и его легко было исправить), а не с фактом глубокой ошибки в рассуждениях. Как правило, подходы, основанные на логарифмировании, потерпят неудачу в случае, если n = 0 . Да, это известно, и это не важно в ответе . Цель ответов на вопросы состоит в том, чтобы дать подход, а затем OP может интегрировать его в более надежный контекст. Я бы сказал, что логарифмический подход лучше, чем создание строки и подсчет или наличие цикла (за исключением, возможно, в случае базы 2).

5. Я прочитал весь пост, спасибо, и нет, добавление второго варианта без ошибок (который работает только для людей по ночам, которые готовы полагаться на нестабильные функции) не устраняет проблемы в первом фрагменте. Если вы пишете код с ошибками, решение состоит в том, чтобы исправить ошибки, а не писать больше кода.