2009-09-09 2 views
1

Я хотел бы вычислить число ненулевых и не повторяющихся (т.е. 1.999999999) справа от десятичной точки. Например:Определить количество ненулевых цифр справа от десятичной точки

x.xx = 2 
x.xxx = 3 
x.xxx000 = 3 

Я могу сделать это путем преобразования числа в строку, но мне было интересно, если есть более быстрый способ, используя математику. Есть идеи?

Спасибо.

EDIT: Многие люди, похоже, считают, что это безумное поручение из-за того, как номера представлены в компьютерах. Однако позвольте мне объяснить контекст проблемы. Предположим, вы должны написать метод случайного генерации числа с плавающей запятой. Количество, которое вы создаете, должно иметь определенную точность, поэтому вы должны округлять произвольно сгенерированное число до указанной точности. Например, если точность равна 2, ваше случайное число не может быть 0,012, оно должно быть округлено до 0,01. Проблема в том, что вам не предоставляется точность, вместо этого вам предоставляется приращение, которое в вышеупомянутом случае будет 0,01. В случае 0.01 или 0.002 или любого другого такого приращения, которое меньше 1, вы должны найти точность.

Редактировать: Удалено неправильное использование термина значимых цифр.

+2

В каком формате хранится ваш номер? Если это 'float' или' double', и вам нужно определить количество значительных цифр _decimal_, тогда задача бессмысленна, так как двоичные числа с плавающей запятой по определению не имеют значительных десятичных цифр. –

+0

anotherCoder не указывает, в каком формате хранятся числа. Я предполагаю, что это гипотетический вопрос, учитывая слово «интересно», используемое в вопросе. –

+0

Это другое определение «значащих цифр», чем я узнал в колледже. –

ответ

1

дешевки подход будет заключаться в следующем:

  1. преобразовать число в строку
  2. найти первое ненулевое число справа от строки путем оценки символов длины ап = строка - 1 вниз до 0.
  3. Возьмите номер, найденный на шаге 2, и вычтите из этого индекс десятичной точки в этой строке.
+0

Но это зависит от того, как печатается число (т.е. формат, который в настоящее время применяется к потоку). Таким образом, вы подсчитываете значащие цифры формата. Это легче найти в руководстве. –

+0

Обновлено, чтобы отразить значимые цифры –

+1

anotherCoder задал в своем вопросе, как он мог это сделать * без * использования строк ... – GRB

2

Одна техника & dagger; бы умножить число на 10, пока цифра для единиц не является 0 (которое можно различить с помощью%):

float x = /*...*/; 
int count = 0; 

while (true) 
{ 
    x *= 10; 

    if (static_cast<long>(x) % 10 == 0) 
     break; 

    ++count; 
} 

// at this point count has the number of digits after the decimal point. 

& кинжалом; Вот моя говядина с тем, что я написал:

  • Он никогда не может закончиться (теоретически).
  • В вышеприведенном алгоритме происходит много умножения и модуля, поэтому это довольно дорого.
  • Каждая итерация увеличивает вероятность попадания ошибок округления с плавающей запятой.
  • Это не удастся для x.x0x, где x представляет любое количество цифр. (спасибо Даниэлю Брюкнеру)

Таким образом, я бы склонился к серализации и поиску.

+0

+1 для допуска дефектов – GRB

+0

Это не удастся для x0.x и x.x0x, где x представляет любое количество цифр. –

+0

Я думаю, что вы правы во втором случае, но первый случай не должен быть проблемой, потому что число умножается первым, прежде чем оно будет проверено. Ваша точка остается в силе. – fbrereto

0

В C# существует функция округления до заданного числа десятичных цифр. Вы можете увеличить число десятичных цифр до тех пор, пока разница между числом и округленным числом не будет небольшим, а фиксированный порог - возможно, два или три раза (минимально возможное положительное значение - для двойного - 4.94065645841247e-324).

Double epsilon = 2 * Double.Epsilon; 
Int32 significantDigits = 0; 
while (Math.Abs(number - Math.Round(number, significantDigits)) > epsilon) 
{ 
    significantDigits++; 
} 

Это просто непроверенные предложение и алгоритмы такого рода дело с числами с плавающей точкой, как правило, ведет себя совершенно неожиданным - так это должно быть хорошо испытанная перед использованием.

0

Как насчет этого?

int sigfigs (double value) 
{ 
    int count = 0; 
    for (; value!=floor(value); value *= 10) 
      count++; 
    return count; 
} 

Это, кажется, работает 99% время (для некоторых очень странных случаев угловых за исключением, например, число 30,22054 будет возвращать правильное значение 5, в то время как 20.22054 возвращает 15)

+2

Случаи, на которых вы спотыкаетесь, не являются странными угловыми шкафами. Это артефакт того факта, что представления с плавающей точкой не являются точными. Например, представление с плавающей запятой '20.22054' не равно действительному числу' 20.22054'. Представление с плавающей запятой действительного числа '20.22054' оказывается' 20.220539093017578125'. – jason

+0

Ну да, «странно» Я имел в виду «раздражающий». Я знал, что это из-за неточности с плавающей запятой, но неспособность узнать, когда вы попадете в число с таким искажением в памяти, затрудняет использование (честно говоря) любого из этих алгоритмов с уверенностью в их результатах. – GRB

6

Количества significant digits это не то, что вы вычисляете, глядя на номер. Вы вычисляете его на основе количества значимых цифр в других числах, которые вы использовали для вычисления числа, на которое вы смотрите, или точности инструмента, используемого для выполнения вашего измерения.

Незначительные цифры не отображаются и не используются, поэтому факт, что ваш номер x.xxx000 имеет шесть цифр справа от десятичной точки, означает, что все шесть из этих цифр значительны. Номер 2.3 не совпадает с номером 2.300.

Вопрос о том, следует ли считать нули значительными, вступает в игру, если у вас есть номер 2300, где число является целым числом, равным 10. Есть ли две значимые цифры или четыре? Запишите его в научной нотации: 2.3 × 10 или 2.300 × 10 .

Собственные числовые типы большинства языков, включая C++, не обрабатывают это понятие значимых цифр. В общем, они даже не обрабатывают цифры вообще; они обрабатывают биты. Если тип вашей реализации float - 32 бита, то все 32 из этих битов считаются значительными все время.

+1

Вы правы, я неправильно использовал термин siginificant digit. Прошу прощения за то, что я не ясно заявляю о своих намерениях и вопросах. – 2009-09-10 18:35:04

0

Как указывали другие, основная проблема заключается в том, что многие десятичные дроби не являются точно представимыми в двоичном формате, и поэтому простое хранение реального числа в качестве поплавка может добавить появление значительных цифр.

Однако справедливое приближение может быть сделано, если вы хотите получить неправильный ответ за число со многими значительными цифрами. Вот идея в C++. Я не утверждаю, что это полностью переносимо - он предполагает архитектуру с использованием, например, Платформы IEEE-759. Это должно охватывать 99,9% архитектур, в которые вы столкнетесь. Также возможно, что для этого требуется определенная работа для поддержки действительно, действительно малых/больших чисел.

#include <limits> 
#include <cmath> 

using std::numeric_limits; 
using std::abs; 
using std::floor; 

unsigned significant_digits(double target); 
unsigned significant_digits_right_of_decimal_point(double target) 
{ 
    target=abs(target); 
    return significant_digits(target-floor(target)); 
} 

unsigned significant_digits(double target) 
{ 
    // The number 0.0 is a special case. How many significant digits should 0 
    // have? 
    if (target==0.0) 
    { 
     return 0; 
    } 

    // make sure our number is positive -- won't change number 
    // of significant digits 
    target=abs(target); 

    // significant digits don't depend on position of decimal point. 
    // divide or multiply until we are between 0.1 and 1.0. 
    // FIXME: dividing by 10 may lose some precision 
    while (target<0.1) 
    { 
     target*=10; 
    } 
    while (target>=1.0) 
    { 
     target/=10; 
    } 

    // Multiply by 2 until we've got 1.0 or higher. This shouldn't change the 
    // mantissa. 
    unsigned exponent=0; 
    while (target<1.0) 
    { 
     ++exponent; 
     target*=2.0; 
    } 

    // ok, now we know the exponent. Figure out what the "round off" could be 
    double epsilon=numeric_limits<double>::epsilon(); 

    // return our number back to where it had been, correcting epsilon along 
    // the way. 
    while (exponent>0) 
    { 
     --exponent; 
     target/=2.0; 
     epsilon/=2.0; 
    } 

    // now that we have an error bound, we can calculate the number of 
    // significant digits. First, shave off any leading '0's 
    while (target<1.0) 
    { 
     target*=10; 
     epsilon*=10; 
    } 

    // now, extract digits until nothing significant is left 
    unsigned result=0; 
    do 
    { 
     ++result; 
     target=target-floor(target); 
     target*=10; 
     epsilon*=10; 
    } 
    while (target<=epsilon); 
    return result; 
} 
+0

К сожалению, вам необходимо научиться читать. Написал рутину для вычисления всех значимых цифр. Ну ладно, быстро исправить ... – Managu

1

найти десятичную точку. переместите десятичную дробь вправо от первой ненулевой цифры. подсчитайте количество мест от десятичной точки до конца номера. это будет показателем. удалить все нули

Смежные вопросы