Есть ли способ получить "значимые цифры" десятичного числа?
Update
ОК, после некоторого расследования, и в значительной степени благодаря полезным ответам Джона и Ганса, это то, что я смог собрать. Пока я думаю, что это работает хорошо. Разумеется, я не стал бы ставить свою жизнь на полную тотальность.
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 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
, насколько мне известно, не знает.)
Ответы
Ответ 1
Вы можете использовать Decimal.GetBits
, чтобы получить необработанные данные и выполнить это.
К сожалению, у меня нет времени писать пример кода на данный момент - и вы, вероятно, захотите использовать BigInteger
для некоторые из манипуляций, если вы используете .NET 4, но, надеюсь, это поможет вам. Просто выработка точности, а затем вызов Math.Round
по исходному результату вполне может быть хорошим началом.
Ответ 2
Да, в отличие от типов с плавающей запятой, System.Decimal отслеживает количество цифр в литерале. Это функция decimal.Parse(), выполняемая вашим кодом самостоятельно или компилятором при анализе литерала в вашей программе. Вы можете восстановить эту информацию, проверить код в моем ответе в этой теме.
Восстановление числа значимых цифр после того, как вы делаете математику по значению, меня поражает как длинный снимок. Не знаю, сохраняют ли операторы, сообщите нам, что вы узнали.
Ответ 3
Решение выше быстро падает с постели со значениями, такими как 1200, где число значащих цифр равно 2, но функция возвращает 4. Возможно, существует последовательный способ справиться с такой ситуацией, т.е. проверить, чтобы убедиться, что возвращаемое значение не включают конечные нули во всей числовой части без дробной части.