简体   繁体   English

C# 检查小数点是否超过 3 个小数位?

[英]C# Check if a decimal has more than 3 decimal places?

I have a situation that I cannot change: one database table (table A) accepts 6 decimal places, while a related column in a different table (table B) only has 3 decimal places.我有一种无法更改的情况:一个数据库表(表 A)接受 6 个小数位,而另一个表(表 B)中的相关列只有 3 个小数位。

I need to copy from A to B, but if A has more than 3 decimal places the extra data will be lost.我需要从 A 复制到 B,但如果 A 的小数位超过 3 位,额外的数据将丢失。 I cant change the table definition but I can add a workaround.我无法更改表定义,但可以添加解决方法。 So I'm trying to find out how to check if a decimal has more than 3 decimal places or not?所以我试图找出如何检查小数点是否超过 3 个小数位?

eg例如

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

Table B
Id, TotalQty(=3dp)

I want to be able to find out if Qty * Unit from Table A has more than 3 decimals (row 1 would fail, row 2 would pass):我希望能够找出表 A 中的 Qty * Unit 是否有超过 3 位小数(第 1 行会失败,第 2 行会通过):

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

How would I implement the CountDecimalPlaces(decimal value) {} function?我将如何实现CountDecimalPlaces(decimal value) {}函数?

You could compare the value of the number rounded to 3 decimal places with the original value.您可以将四舍五入到小数点后 3 位的数字的值与原始值进行比较。

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

This works for 3 decimal places, and it can be adapted for a generic solution:这适用于 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));
}

For a real generic solution, you'll need to "deconstruct" the decimal value in its parts - take a look at Decimal.GetBits for more information.对于真正的通用解决方案,您需要在其部分“解构”十进制值 - 查看Decimal.GetBits了解更多信息。

Update: this is a simple implementation of a generic solution which works for all decimals whose integer part is less than long.MaxValue (you'd need something like a "big integer" for a trully generic function).更新:这是通用解决方案的简单实现,适用于整数部分小于 long.MaxValue 的所有小数(对于真正的通用函数,您需要类似“大整数”的东西)。

static decimal CountDecimalPlaces(decimal dec)
{
    Console.Write("{0}: ", dec);
    int[] bits = Decimal.GetBits(dec);
    ulong lowInt = (uint)bits[0];
    ulong midInt = (uint)bits[1];
    int exponent = (bits[3] & 0x00FF0000) >> 16;
    int result = exponent;
    ulong lowDecimal = lowInt | (midInt << 32);
    while (result > 0 && (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));
}

This is a very simple one line code to get count of decimals in a Decimal:这是一个非常简单的一行代码,用于获取 Decimal 中的小数位数:

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

Multiplying a number with 3 decimal places by 10 to the power of 3 will give you a number with no decimal places.将一个有 3 个小数位的数字乘以 10 的 3 次方将得到一个没有小数位的数字。 It's a whole number when the modulus % 1 == 0 .当模数% 1 == 0时,它是一个整数。 So I came up with this...所以我想出了这个...

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

Returns true when n is less than (not equal) to the number of decimal places.n小于(不等于)小数位数时返回 true。

The basics is to know how to test if there are decimal places, this is done by comparing the value to its rounding基础知识是知道如何测试是否有小数位,这是通过将值与其舍入进行比较来完成的

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

Then, to count 3 decimal places, you just need to do the same for your number multiplied by 1000:然后,要计算 3 个小数位,您只需要对乘以 1000 的数字执行相同的操作:

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

All of the solutions proposed so far are not extensible ... fine if you are never going to check a value other than 3, but I prefer this because if the requirement changes the code to handle it is already written.到目前为止提出的所有解决方案都是不可扩展的......如果你永远不会检查 3 以外的值,那很好,但我更喜欢这个,因为如果需求改变了代码来处理它已经写好了。 Also this solution wont overflow.此解决方案也不会溢出。

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 solution will need to check for 0 otherwise "while ((lowDecimal % 10) == 0)" will produce an infinity loop when called with dec = 0 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));

One more option based on @RodH257's solution, but reworked as an extension method:另一个基于@RodH257 解决方案的选项,但作为扩展方法重新设计:

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

You can then call that as:然后,您可以将其称为:

If !(tableA.Qty * tableA.Unit).HasThisManyDecimalPlacesOrLess(3)) return;
    bool CountDecimalPlaces(decimal input)
    {
        return input*1000.0 == (int) (input*1000);
    }

There is probably a more elegant way to do this, but off the top of my head I would try可能有一种更优雅的方法来做到这一点,但我会尝试

  1. a = multiply by 1000 a = 乘以 1000
  2. b = truncate a b = 截断 a
  3. if (b != a) then there is additional precision that has been lost if (b != a) 那么额外的精度已经丢失
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

could you convert it to a string and just do a len function or would that not cover your situation?你能把它转换成一个字符串然后只做一个 len 函数还是不能涵盖你的情况?

follow up question: would 300.4 be ok?后续问题:300.4 可以吗?

Here is my version:这是我的版本:

public static int CountDecimalPlaces(decimal dec)
{
    var a = Math.Abs(dec);
    var x = a;
    var count = 1;
    while (x % 1 != 0)
    {
        x = a * new decimal(Math.Pow(10, count++));
    }

    var result = count - 1;

    return result;
}

I tried first @carlosfigueira/@Henrik Stenbæk , but their version does not work with 324000.00m我首先尝试了@carlosfigueira/@Henrik Stenbæk ,但他们的版本不适用于324000.00m

TEST:测试:

Console.WriteLine(CountDecimalPlaces(0m)); //0
Console.WriteLine(CountDecimalPlaces(0.5m)); //1
Console.WriteLine(CountDecimalPlaces(10.51m)); //2
Console.WriteLine(CountDecimalPlaces(10.5123456978563m)); //13
Console.WriteLine(CountDecimalPlaces(324000.0001m)); //4
Console.WriteLine(CountDecimalPlaces(324000.0000m)); //0

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM