C # Проверить, имеет ли десятичное число более 3 десятичных знаков? - PullRequest
13 голосов
/ 23 мая 2011

У меня есть ситуация, которую я не могу изменить: одна таблица базы данных (таблица 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;

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

Ответы [ 12 ]

48 голосов
/ 29 мая 2012

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

if (Decimal.Round(valueDecimal, 3) != valueDecimal)
{
   //Too many decimals
}
14 голосов
/ 23 мая 2011

Это работает для 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 (вам нужно что-то вроде "большого целого числа" для полностью обобщенной функции).

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));
}
4 голосов
/ 01 марта 2013

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

decimal myDecimal = 1.000000021300010000001m;
byte decimals = (byte)((Decimal.GetBits(myDecimal)[3] >> 16) & 0x7F);
3 голосов
/ 23 мая 2011

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

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

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

bool hasMoreThan3decimals = number*1000 != (int) (number * 1000)
2 голосов
/ 13 октября 2012

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

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

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

2 голосов
/ 07 августа 2012

решению 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));
2 голосов
/ 23 мая 2011

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

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

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

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

If !(tableA.Qty * tableA.Unit).HasThisManyDecimalPlacesOrLess(3)) return;
1 голос
/ 23 мая 2011

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

  1. a = умножить на 1000
  2. b = усечь a
  3. если (b! = A), то есть дополнительная точность, которая была потеряна
1 голос
/ 23 мая 2011
    bool CountDecimalPlaces(decimal input)
    {
        return input*1000.0 == (int) (input*1000);
    }
...