Как увеличить наименьшую дробную часть десятичной дроби на единицу? - PullRequest
9 голосов
/ 10 февраля 2010

Я хочу увеличить наименьшую дробную часть десятичной дроби, чтобы, например,

decimal d = 0.01
d++
d == 0.02

или

decimal d = 0.000012349
d++
d == 0.000012350

Как мне это сделать?

Ответы [ 4 ]

12 голосов
/ 10 февраля 2010

Десятичный тип (.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);
}
1 голос
/ 10 февраля 2010

Я придумала новое решение, отличное от Joe, оно должно привести к незначительному увеличению производительности.

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 нс

0 голосов
/ 10 февраля 2010

Это бы сработало:

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;
0 голосов
/ 10 февраля 2010

Что по этому поводу:

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
    }
}

Я использовал здесь метод расширения; вы не можете переопределить оператор ++ для десятичного типа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...