Есть ли способ получить "значащие цифры" десятичной дроби? - PullRequest
17 голосов
/ 10 сентября 2010

Обновление

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

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 type , по-видимому, включает информацию о точности, тогда как double, насколько я знаю, нет.)

Ответы [ 3 ]

3 голосов
/ 10 сентября 2010

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

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

3 голосов
/ 10 сентября 2010

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

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

0 голосов
/ 02 февраля 2017

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

...