Я хочу увеличить самую маленькую дробную часть десятичного числа с той так, чтобы, например,
decimal d = 0.01
d++
d == 0.02
или
decimal d = 0.000012349
d++
d == 0.000012350
Как я делаю это?
Десятичный тип (.NET 2.0 и более поздние версии) сохраняет важные конечные нули, которые являются результатом вычисления или синтаксического анализа строки. Например. 1,2 * 0,5 = 0,60 (умножение двух чисел с точностью до одного десятичного знака дает результат с точностью до 2 десятичных знаков, даже если второй десятичный разряд равен нулю):
decimal result = 1.2M * 0.5M;
Console.WriteLine(result.ToString()); // outputs 0.60
Ниже предполагается, что вы хотите учитывать все значащие цифры в десятичном значении. , т.е.
decimal d = 1.2349M; // original 1.2349;
d = IncrementLastDigit(d); // result is 1.2350;
d = IncrementLastDigit(d); // result is 1.2351; (not 1.2360).
Однако, если вы хотите сначала удалить конечные нули, вы можете это сделать, например используя метод в этом ответе .
Для этого нет ничего встроенного. Вам придется сделать это самостоятельно, (а) определив, сколько цифр стоит после десятичной дроби, а затем (б) прибавив соответствующую сумму.
Чтобы определить, сколько цифр стоит после десятичной дроби, вы можете либо отформатировать как строку, а затем подсчитать их, либо, что более эффективно, вызвать decimal.GetBits (), результатом которого является массив из четырех целых чисел, содержащий коэффициент масштабирования в битах 16-23 четвертого целого числа.
Получив это, вы можете легко вычислить необходимое значение, которое нужно добавить к десятичному значению.
Вот реализация, использующая GetBits, которая «увеличивает» от нуля для отрицательных чисел. IncrementLastDigit (-1,234M) => -1,235M.
static decimal IncrementLastDigit(decimal value)
{
int[] bits1 = decimal.GetBits(value);
int saved = bits1[3];
bits1[3] = 0; // Set scaling to 0, remove sign
int[] bits2 = decimal.GetBits(new decimal(bits1) + 1);
bits2[3] = saved; // Restore original scaling and sign
return new decimal(bits2);
}
Или вот альтернатива (возможно, более элегантная):
static decimal GetScaledOne(decimal value)
{
int[] bits = decimal.GetBits(value);
// Generate a value +1, scaled using the same scaling factor as the input value
bits[0] = 1;
bits[1] = 0;
bits[2] = 0;
bits[3] = bits[3] & 0x00FF0000;
return new decimal(bits);
}
static decimal IncrementLastDigit(decimal value)
{
return value < 0 ? value - GetScaledOne(value) : value + GetScaledOne(value);
}
Я придумал новое решение, отличающееся от решения Джо, которое должно привести к небольшому повышению производительности.
public static decimal IncrementLowestDigit(this decimal value, int amount)
{
int[] bits = decimal.GetBits(value);
if (bits[0] < 0 && amount + bits[0] >= 0)
{
bits[1]++;
if (bits[1] == 0)
{
bits[2]++;
}
}
bits[0] += amount;
return new decimal(bits);
}
Тест
Я протестировал свои результаты методами Джо.
private static void Test(int l, int m, int h, int e, int times)
{
decimal a = new decimal(new[] { l, m, h, e });
decimal b = a.IncrementLowestDigit(times);
decimal c = IncrementLastDigit(a, times);
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine();
}
Test(0, 0, 0, 0x00000000, 1);
Test(0, 0, 0, 0x00000000, 2);
Test(0, 0, 0, 0x00010000, 1);
Test(0, 0, 0, 0x00010000, 2);
Test(0, 0, 0, 0x00020000, 1);
Test(0, 0, 0, 0x00020000, 2);
Test(-1, 0, 0, 0x00000000, 1);
Test(-1, 0, 0, 0x00000000, 2);
Test(-1, 0, 0, 0x00010000, 1);
Test(-1, 0, 0, 0x00010000, 2);
Test(-1, 0, 0, 0x00020000, 1);
Test(-1, 0, 0, 0x00020000, 2);
Test(-2, 0, 0, 0x00000000, 1);
Test(-2, 0, 0, 0x00000000, 2);
Test(-2, 0, 0, 0x00010000, 1);
Test(-2, 0, 0, 0x00010000, 2);
Test(-2, 0, 0, 0x00020000, 1);
Test(-2, 0, 0, 0x00020000, 2);
Test(-2, 0, 0, 0x00000000, 3);
Test(0, 1, 0, 0x00000000, 1);
Test(0, 1, 0, 0x00000000, 2);
Test(0, 1, 0, 0x00010000, 1);
Test(0, 1, 0, 0x00010000, 2);
Test(0, 1, 0, 0x00020000, 1);
Test(0, 1, 0, 0x00020000, 2);
Test(-1, 2, 0, 0x00000000, 1);
Test(-1, 2, 0, 0x00000000, 2);
Test(-1, 2, 0, 0x00010000, 1);
Test(-1, 2, 0, 0x00010000, 2);
Test(-1, 2, 0, 0x00020000, 1);
Test(-1, 2, 0, 0x00020000, 2);
Test(-2, 3, 0, 0x00000000, 1);
Test(-2, 3, 0, 0x00000000, 2);
Test(-2, 3, 0, 0x00010000, 1);
Test(-2, 3, 0, 0x00010000, 2);
Test(-2, 3, 0, 0x00020000, 1);
Test(-2, 3, 0, 0x00020000, 2);
Только для смеха
Я сделал тест производительности с 10 миллионами итераций на 3 ГГц. Чип Intel:
Мой: 11,6 нс
Джо: 32.1 ns
Это поможет:
decimal d = 0.01M;
int incr = 1;
int pos = d.ToString().IndexOf('.');
int len = d.ToString().Length - pos - 1;
if (pos > 0)
{
double val = Convert.ToDouble(d);
val = Math.Round(val * Math.Pow(10, len) + incr) / Math.Pow(10, len);
d = Convert.ToDecimal(val);
}
else
d += incr;
return d;
Что насчет этого:
static class DecimalExt {
public static decimal PlusPlus(this decimal value) {
decimal test = 1M;
while (0 != value % test){
test /= 10;
}
return value + test;
}
}
class Program {
public static void Main(params string[] args) {
decimal x = 3.14M;
x = x.PlusPlus(); // now is 3.15
}
}
Я использовал здесь метод расширения; вы не можете переопределить оператор ++ для десятичного типа.