С# Проверьте, имеет ли десятичная запятая более трех знаков после запятой?

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

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

например,

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

Table B
Id, TotalQty(=3dp)

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

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

Как реализовать функцию CountDecimalPlaces(decimal value) {}?

Ответ 1

Это работает на 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 для получения дополнительной информации.

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

Ответ 4

Все предлагаемые решения пока не расширяемы... отлично, если вы никогда не будете проверять значение, отличное от 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;
}       

Ответ 5

Решение 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));

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

  • a = умножить на 1000
  • b = обрезать
  • if (b!= a), то есть дополнительная точность, потерянная

Ответ 9

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

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

Ответ 10

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

следующий вопрос: было бы 300.4 в порядке?

Ответ 11

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

Ответ 12

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

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

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

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