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);
вероятно, есть более элегантный способ сделать это, но с моей головы я бы попробовал
- a = умножить на 1000
- b = усечь a
- if (b != a) тогда есть дополнительная точность, которая была потеряна
можете ли вы преобразовать его в строку и просто выполнить функцию 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;