Есть ли способ получить значащие цифры десятичной дроби?

Обновлять

Хорошо, после некоторого расследования и во многом благодаря полезным ответам, предоставленным Джоном и Хансом, это то, что я смог собрать воедино. Пока я думаю, что это работает хорошо. Я, конечно, не ставлю свою жизнь на его полную правильность.

public static int GetSignificantDigitCount(this decimal value)
{
    /* So, the decimal type is basically represented as a fraction of two
     * integers: a numerator that can be anything, and a denominator that is 
     * some power of 10.
     * 
     * For example, the following numbers are represented by
     * the corresponding fractions:
     * 
     * VALUE    NUMERATOR   DENOMINATOR
     * 1        1           1
     * 1.0      10          10
     * 1.012    1012        1000
     * 0.04     4           100
     * 12.01    1201        100
     * 
     * So basically, if the magnitude is greater than or equal to one,
     * the number of digits is the number of digits in the numerator.
     * If it's less than one, the number of digits is the number of digits
     * in the denominator.
     */

    int[] bits = decimal.GetBits(value);

    if (value >= 1M || value <= -1M)
    {
        int highPart = bits[2];
        int middlePart = bits[1];
        int lowPart = bits[0];

        decimal num = new decimal(lowPart, middlePart, highPart, false, 0);

        int exponent = (int)Math.Ceiling(Math.Log10((double)num));

        return exponent;
    }
    else
    {
        int scalePart = bits[3];

        // Accoring to MSDN, the exponent is represented by
        // bits 16-23 (the 2nd word):
        // http://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx
        int exponent = (scalePart & 0x00FF0000) >> 16;

        return exponent + 1;
    }
}

Я не проверял все это так тщательно. Вот несколько примеров входных/выходных данных:

Value          Precision
0              1 digit(s).
0.000          4 digit(s).
1.23           3 digit(s).
12.324         5 digit(s).
1.2300         5 digit(s).
-5             1 digit(s).
-5.01          3 digit(s).
-0.012         4 digit(s).
-0.100         4 digit(s).
0.0            2 digit(s).
10443.31       7 digit(s).
-130.340       6 digit(s).
-80.8000       6 digit(s).

Используя этот код, я думаю, что достигну своей цели, сделав что-то вроде этого:

public static decimal DivideUsingLesserPrecision(decimal x, decimal y)
{
    int xDigitCount = x.GetSignificantDigitCount();
    int yDigitCount = y.GetSignificantDigitCount();

    int lesserPrecision = System.Math.Min(xDigitCount, yDigitCount);

    return System.Math.Round(x / y, lesserPrecision);
}

Я действительно не закончил работу над этим, хотя. Любой, кто хочет поделиться мыслями: это было бы очень признательно!


Оригинальный вопрос

Предположим, я написал этот код:

decimal a = 1.23M;
decimal b = 1.23000M;

Console.WriteLine(a);
Console.WriteLine(b);

Выше будет вывод:

1.23
1.23000

Я обнаружил, что это также работает, если я использую decimal.Parse("1.23") для a и decimal.Parse("1.23000") для b (это означает, что этот вопрос относится к случаям, когда программа получает пользовательский ввод).

Таким образом, очевидно, что значение decimal каким-то образом «осведомлено» о том, что я назову его точность. Однако я не вижу членов типа decimal, предоставляющих какой-либо способ доступа к этому, кроме самого ToString.

Предположим, я хочу перемножить два значения decimal и обрезать результат до точности менее точного аргумента. Другими словами:

decimal a = 123.4M;
decimal b = 5.6789M;

decimal x = a / b;

Console.WriteLine(x);

Вышеуказанные результаты:

21.729560302171195125816619416

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

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

также понимаю, что в большинстве случаев, когда вы имеете дело со значащими цифрами, вам, вероятно, не нужно использовать тип decimal. Но я спрашиваю, потому что, как я упоминал в начале , тип decimal похоже включает информацию о точности, тогда как double, насколько мне известно, нет.)


person Dan Tao    schedule 10.09.2010    source источник
comment
Я не знал, что хочу это знать, пока ты не спросил +1!   -  person msarchet    schedule 10.09.2010
comment
Ваша функция работает некорректно для некоторых из этих входных данных. Например, -0,012 — это только 2 значащие цифры, а не 4.   -  person James Jones    schedule 29.03.2011
comment
@JamesJones Ясно, что он имеет в виду другую концепцию значащих цифр, а не то, что можно было бы изучить на курсе математики. Возможно, название, использующее цифры, имело бы больше смысла.   -  person ErikE    schedule 22.08.2015


Ответы (3)


Вы можете использовать Decimal.GetBits для получения необработанных данных, и выработать это из этого.

К сожалению, сейчас у меня нет времени писать пример кода, и вы, вероятно, захотите использовать BigInteger для некоторых манипуляций, если вы используете .NET 4, но, надеюсь, это поможет вам. Простое определение точности и последующий вызов Math.Round для исходного результата может быть хорошим началом.

person Jon Skeet    schedule 10.09.2010
comment
Я принимаю этот ответ, хотя я еще не закончил разработку своего решения, потому что он определенно дает мне необходимую информацию. (К сожалению, я надеялся на что-то более доступное, чем GetBits, но, похоже, это лучшее, с чем нам приходилось работать!) - person Dan Tao; 10.09.2010

Да, в отличие от типов с плавающей запятой, System.Decimal отслеживает количество цифр в литерале. Это функция decimal.Parse(), независимо от того, выполняется ли она вашим кодом самостоятельно или компилятором, когда он анализирует литерал в вашей программе. Вы можете восстановить эту информацию, проверьте код в моем ответе в эта тема.

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

person Hans Passant    schedule 10.09.2010
comment
Да, я почти уверен, что эта информация не сохранилась. Что имеет смысл — я имею в виду, учитывая что-то вроде 1M / 4M, очевидно, должно получиться 0.25M, а не 0.3M. В любом случае, я немного разобрался с проблемой и придумал что-то, напоминающее готовящееся решение (в комплекте с тем же самым битом, который вы предложили в связанном ответе!)... мой код очень уродлив , хотя. Если у вас появится свободная минутка позже, я определенно буду признателен за ваши мысли о моем обновлении в верхней части вопроса. - person Dan Tao; 10.09.2010

Приведенное выше решение быстро падает со значениями, такими как 1200, где количество значащих цифр равно 2, но функция возвращает 4. Возможно, существует последовательный способ справиться с такой ситуацией, т.е. проверить, чтобы возвращаемое значение не включало конечные нули. в целой части числа без дробной части.

person Scott    schedule 02.02.2017