C# проверьте, имеет ли десятичный знак более 3 знаков после запятой?

у меня есть ситуация, которую я не могу изменить: одна таблица базы данных (таблица A) принимает 6 знаков после запятой, в то время как связанный столбец в другой таблице (Таблица B) имеет только 3 знака после запятой.

мне нужно скопировать из A В B, но если A имеет более 3 знаков после запятой, дополнительные данные будут потеряны. Я не могу изменить определение таблицы, но я могу добавить обходной путь. Поэтому я пытаюсь выяснить, как проверить, имеет ли десятичный знак более 3 десятичных знаков или нет?

например

Table A
Id, Qty,  Unit(=6dp)
1,  1,     0.00025
2,  4000,  0.00025

Table B
Id, TotalQty(=3dp)

Я хочу иметь возможность узнать, имеет ли qty * единица из таблицы A более 3 десятичных знаков (строка 1 потерпит неудачу, строка 2 пройдет):

if (CountDecimalPlaces(tableA.Qty * tableA.Unit) > 3)
{
    return false;
}
tableB.TotalQty = tableA.Qty * tableA.Unit;

как бы я реализую

12 ответов


это работает для 3 десятичных знаков, и его можно адаптировать для общего решения:

static bool LessThan3DecimalPlaces(decimal dec)
{
    decimal value = dec * 1000;
    return value == Math.Floor(value);
}
static void Test()
{
    Console.WriteLine(LessThan3DecimalPlaces(1m * 0.00025m));
    Console.WriteLine(LessThan3DecimalPlaces(4000m * 0.00025m));
}

для реального универсального решения вам нужно "деконструировать" десятичное значение в его частях-взгляните на Decimal.GetBits для получения дополнительной информации.

Update: это простая реализация универсального решения, которое работает для всех десятичных знаков, целая часть которых меньше длины.MaxValue (вам понадобится что-то вроде "большого целого числа" для trully generic функция.)

static decimal CountDecimalPlaces(decimal dec)
{
    int[] bits = Decimal.GetBits(dec);
    int exponent = bits[3] >> 16;
    int result = exponent;
    long lowDecimal = bits[0] | (bits[1] >> 8);
    while ((lowDecimal % 10) == 0)
    {
        result--;
        lowDecimal /= 10;
    }

    return result;
}
static void Foo()
{
    Console.WriteLine(CountDecimalPlaces(1.6m));
    Console.WriteLine(CountDecimalPlaces(1.600m));
    Console.WriteLine(CountDecimalPlaces(decimal.MaxValue));
    Console.WriteLine(CountDecimalPlaces(1m * 0.00025m));
    Console.WriteLine(CountDecimalPlaces(4000m * 0.00025m));
}

вы можете сравнить значение числа, округленного до 3 десятичных знаков с исходным значением.

if (Decimal.Round(valueDecimal, 3) != valueDecimal)
{
   //Too many decimals
}

основы должны знать, как проверить, есть ли десятичные знаки, это делается путем сравнения значения с его округлением

double number;
bool hasDecimals = number == (int) number;

затем, чтобы сосчитать 3 десятичных знака, вам просто нужно сделать то же самое для вашего числа, умноженного на 1000:

bool hasMoreThan3decimals = number*1000 != (int) (number * 1000)

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

int GetDecimalCount(decimal val)
{
    if(val == val*10)
    {
        return int.MaxValue; // no decimal.Epsilon I don't use this type enough to know why... this will work
    }

    int decimalCount = 0;
    while(val != Math.Floor(val))
    {
        val = (val - Math.Floor(val)) * 10;
        decimalCount++;
    }
    return decimalCount;
}       

решение carlosfigueira необходимо будет проверить на 0 в противном случае" while (((lowDecimal % 10) == 0) " создаст бесконечный цикл при вызове с dec = 0

static decimal CountDecimalPlaces(decimal dec)
    {
        if (dec == 0)
            return 0;
        int[] bits = Decimal.GetBits(dec);
        int exponent = bits[3] >> 16;
        int result = exponent;
        long lowDecimal = bits[0] | (bits[1] >> 8);
        while ((lowDecimal % 10) == 0)
        {
            result--;
            lowDecimal /= 10;
        }
        return result;
    }

    Assert.AreEqual(0, DecimalHelper.CountDecimalPlaces(0m));      
    Assert.AreEqual(1, DecimalHelper.CountDecimalPlaces(0.5m));
    Assert.AreEqual(2, DecimalHelper.CountDecimalPlaces(10.51m));
    Assert.AreEqual(13, DecimalHelper.CountDecimalPlaces(10.5123456978563m));

умножение числа с 3 десятичными знаками на 10 в степени 3 даст вам число без десятичных знаков. Это целое число, когда модуль % 1 == 0. Поэтому я придумал это...

bool hasMoreThanNDecimals(decimal d, int n)
{
    return !(d * (decimal)Math.Pow(10, n) % 1 == 0);
}

возвращает true, если n меньше (не равно) количеству десятичных знаков.


Это очень простой код, чтобы получить количество знаков после запятой в десятичной:

decimal myDecimal = 1.000000021300010000001m;
byte decimals = (byte)((Decimal.GetBits(myDecimal)[3] >> 16) & 0x7F);

вероятно, есть более элегантный способ сделать это, но с моей головы я бы попробовал

  1. a = умножить на 1000
  2. b = усечь a
  3. if (b != a) тогда есть дополнительная точность, которая была потеряна

    bool CountDecimalPlaces(decimal input)
    {
        return input*1000.0 == (int) (input*1000);
    }

можете ли вы преобразовать его в строку и просто выполнить функцию len или это не будет охватывать вашу ситуацию?

следующий вопрос: бы 300.4 быть ОК?


Public Function getDecimalCount(decWork As Decimal) As Integer

    Dim intDecimalCount As Int32 = 0
    Dim strDecAbs As String = decWork.ToString.Trim("0")

    intDecimalCount = strDecAbs.Substring(strDecAbs.IndexOf(".")).Length -1

    Return intDecimalCount

End Function

еще один вариант, основанный на решении @RodH257, но переработанный как метод расширения:

public static bool HasThisManyDecimalPlacesOrLess(this decimal value, int noDecimalPlaces)
{
    return (Decimal.Round(value, noDecimalPlaces) == value);
}

затем вы можете назвать это как:

If !(tableA.Qty * tableA.Unit).HasThisManyDecimalPlacesOrLess(3)) return;