Tìm số vị trí thập phân trong giá trị thập phân bất kể nền văn hóa


91

Tôi tự hỏi liệu có cách nào ngắn gọn và chính xác để rút ra số vị trí thập phân trong một giá trị thập phân (dưới dạng số nguyên) mà sẽ an toàn để sử dụng trên các thông tin văn hóa khác nhau không?

Ví dụ:
19.0 nên trả về 1,
27.5999 nên trả về 4,
19.12 nên trả về 2,
v.v.

Tôi đã viết một truy vấn đã thực hiện tách chuỗi trên một dấu chấm để tìm các vị trí thập phân:

int priceDecimalPlaces = price.ToString().Split('.').Count() > 1 
                  ? price.ToString().Split('.').ToList().ElementAt(1).Length 
                  : 0;

Nhưng tôi thấy rằng điều này sẽ chỉ hoạt động ở những khu vực sử dụng '.' như một dấu phân cách thập phân và do đó rất dễ gãy trên các hệ thống khác nhau.


Một số thập phân theo tiêu đề câu hỏi
Jesse Carter

Làm thế nào về một số đối sánh mẫu trước khi Split ?. Về cơ bản \ d + (\ D) \ d + trong đó \ D trả về dấu phân cách (.,
V.v.

7
Đây không phải là một câu hỏi kết thúc vì nó có thể xuất hiện đỏ mặt lúc đầu. Yêu cầu 19.0trả lại 1là một chi tiết triển khai liên quan đến bộ nhớ trong của giá trị 19.0. Thực tế là chương trình hoàn toàn hợp pháp để lưu trữ điều này dưới dạng 190×10⁻¹hoặc 1900×10⁻²hoặc 19000×10⁻³. Tất cả những thứ đó đều bình đẳng. Thực tế là nó sử dụng đại diện đầu tiên khi được cung cấp giá trị 19.0Mvà điều này được hiển thị khi sử dụng ToStringmà không có định dạng định dạng chỉ là một sự trùng hợp ngẫu nhiên và một điều hạnh phúc. Ngoại trừ việc không vui khi mọi người dựa vào số mũ trong những trường hợp không nên.
ErikE

Nếu bạn muốn có một loại có thể mang theo "số chữ số thập phân sử dụng" khi nó được tạo ra, vì vậy mà bạn đáng tin cậy có thể phân biệt 19Mtừ 19.0Mtừ 19.00M, bạn sẽ cần phải tạo ra một lớp mới mà bó giá trị cơ bản như một thuộc tính và số lượng vị trí thập phân như một thuộc tính khác.
ErikE

1
Mặc dù lớp Thập phân có thể "phân biệt" 19m, từ 19,0m với 19,00m? Các chữ số quan trọng giống như một trong những trường hợp sử dụng chính của nó. 19.0m * 1.0m là gì? Có vẻ như đang nói 19.00m, có lẽ các nhà phát triển C # đang làm toán sai mặc dù: P? Một lần nữa các chữ số có nghĩa là một điều có thật. Nếu bạn không thích các chữ số có nghĩa, có lẽ bạn không nên sử dụng lớp Decimal.
Nicholi

Câu trả lời:


168

Tôi đã sử dụng cách của Joe để giải quyết vấn đề này :)

decimal argument = 123.456m;
int count = BitConverter.GetBytes(decimal.GetBits(argument)[3])[2];

6
Sau khi xem xét kỹ hơn điều này và thấy nó hoạt động, tôi đánh dấu nó là câu trả lời vì theo ý kiến ​​của tôi, đây là phương pháp ngắn gọn và thanh lịch nhất để trả về số thập phân mà tôi đã thấy ở đây. Would +1 lần nữa nếu tôi có thể: D
Jesse Carter

9
decimalgiữ chữ số đếm sau khi hôn mê, đó là lý do tại sao bạn tìm thấy "vấn đề" này, bạn phải chuyển thập phân thành kép và lại thập phân để khắc phục: BitConverter.GetBytes (decimal.GetBits ((decimal) (double)) [3]) [ 2];
burn_LEGION

3
Điều này không hiệu quả với tôi. Giá trị trả về từ SQL là 21,17 nó có 4 chữ số. Kiểu dữ liệu được định nghĩa là DECIMAL (12,4) nên có lẽ đó là nó (sử dụng Entity Framework).
PeterX

11
@Nicholi - Không, điều này đặc biệt tệ vì phương pháp này dựa vào vị trí của các bit cơ bản của số thập phân - một thứ có nhiều cách để biểu diễn cùng một số . Bạn sẽ không kiểm tra một lớp dựa trên trạng thái của các trường riêng của nó phải không?
m.edmondson

14
Không chắc những gì được cho là thanh lịch hoặc tốt đẹp về điều này. Điều này gần như khó hiểu khi nó xảy ra. Ai biết được liệu nó có hoạt động trong mọi trường hợp hay không. Không thể chắc chắn.
usr

24

Vì không có câu trả lời nào được cung cấp đủ tốt để số ma thuật "-0.01f" được chuyển đổi thành số thập phân .. tức là: GetDecimal((decimal)-0.01f);
Tôi chỉ có thể giả định rằng một loại vi-rút khổng lồ đã tấn công mọi người 3 năm trước :)
Đây là những gì có vẻ như đang hoạt động thực hiện cho vấn đề xấu xa và quái dị này, một bài toán rất phức tạp về đếm các chữ số thập phân sau dấu chấm - không có chuỗi, không có văn hóa, không cần đếm bit và không cần đọc các diễn đàn toán học .. chỉ cần toán lớp 3 đơn giản.

public static class MathDecimals
{
    public static int GetDecimalPlaces(decimal n)
    {
        n = Math.Abs(n); //make sure it is positive.
        n -= (int)n;     //remove the integer part of the number.
        var decimalPlaces = 0;
        while (n > 0)
        {
            decimalPlaces++;
            n *= 10;
            n -= (int)n;
        }
        return decimalPlaces;
    }
}

private static void Main(string[] args)
{
    Console.WriteLine(1/3m); //this is 0.3333333333333333333333333333
    Console.WriteLine(1/3f); //this is 0.3333333

    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.0m));                  //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(1/3m));                  //28
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)(1 / 3f)));     //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-1.123m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces(43.12345m));             //5
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0));                     //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.01m));                 //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-0.001m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.00000001f)); //8
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.0001234f));   //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.01f));        //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.01f));       //2
}

6
Giải pháp của bạn sẽ không thành công đối với một số trường hợp có chứa các số không ở cuối và các chữ số là SIGNIFICANT. 0,01m * 2,0m = 0,020m. Phải là 3 chữ số, phương thức của bạn trả về 2. Có vẻ như bạn đang hiểu sai điều gì sẽ xảy ra khi bạn truyền 0,01f thành Thập phân. Dấu chấm động vốn dĩ không chính xác, vì vậy giá trị nhị phân thực tế được lưu trữ cho 0,01f là không chính xác. Khi bạn truyền sang Thập phân (ký hiệu số rất có cấu trúc), bạn có thể không nhận được 0,01m (bạn thực sự nhận được 0,010m). Giải pháp GetBits thực sự chính xác khi lấy số chữ số từ một Hệ thập phân. Cách bạn chuyển đổi sang Decimal là chìa khóa.
Nicholi

2
@Nicholi 0,020m bằng 0,02m .. các số không ở cuối không có ý nghĩa. OP đang hỏi "bất kể nền văn hóa" trong tiêu đề và thậm chí giải thích cụ thể hơn ".. điều đó sẽ an toàn khi sử dụng trên các thông tin văn hóa khác nhau .." - do đó tôi nghĩ rằng câu trả lời của tôi thậm chí còn hợp lệ hơn những câu khác.
GY

6
OP đã nói cụ thể: "19.0 nên trả về 1". Mã này không thành công trong trường hợp đó.
daniloquio

9
có thể đây không phải là điều OP muốn, nhưng câu trả lời này phù hợp với nhu cầu của tôi hơn là câu trả lời hàng đầu của câu hỏi này
Arsen Zahray

2
Hai dòng đầu tiên nên được thay thế bằng n = n % 1; if (n < 0) n = -n;vì giá trị lớn hơn int.MaxValuesẽ gây ra OverflowException, ví dụ 2147483648.12345.
Loathing

23

Tôi có thể sử dụng giải pháp trong câu trả lời của @ fixagon .

Tuy nhiên, trong khi cấu trúc Decimal không có phương thức để lấy số thập phân, bạn có thể gọi Decimal.GetBits để trích xuất biểu diễn nhị phân, sau đó sử dụng giá trị số nguyên và tỷ lệ để tính số thập phân.

Điều này có thể sẽ nhanh hơn định dạng dưới dạng một chuỗi, mặc dù bạn phải xử lý rất nhiều số thập phân khủng khiếp để nhận thấy sự khác biệt.

Tôi sẽ để việc triển khai như một bài tập.


1
Cảm ơn @Joe, đó là một cách tiếp cận nó thực sự gọn gàng. Tùy thuộc vào cảm nhận của sếp về việc sử dụng giải pháp khác, tôi sẽ xem xét việc triển khai ý tưởng của bạn. Chắc chắn sẽ là một tập thể dục vui vẻ :)
Jesse Carter

17

Một trong những giải pháp tốt nhất để tìm số chữ số sau dấu thập phân được hiển thị trong bài đăng của burn_LEGION .

Ở đây tôi đang sử dụng các phần từ một bài viết trên diễn đàn STSdb: Số chữ số sau dấu thập phân .

Trong MSDN, chúng ta có thể đọc phần giải thích sau:

"Số thập phân là giá trị dấu phẩy động bao gồm dấu, giá trị số trong đó mỗi chữ số trong giá trị nằm trong khoảng từ 0 đến 9 và hệ số tỷ lệ cho biết vị trí của dấu phẩy động phân tách tích phân và phân số các phần của giá trị số. "

Và ngoài ra:

"Biểu diễn nhị phân của giá trị Thập phân bao gồm dấu 1 bit, số nguyên 96 bit và hệ số tỷ lệ được sử dụng để chia số nguyên 96 bit và chỉ định phần nào của nó là phần thập phân. Hệ số tỷ lệ là ngầm định là số 10, được nâng lên thành số mũ trong khoảng từ 0 đến 28. "

Ở cấp nội bộ, giá trị thập phân được biểu thị bằng bốn giá trị số nguyên.

Biểu diễn bên trong thập phân

Có một chức năng GetBits có sẵn công khai để lấy đại diện nội bộ. Hàm trả về một mảng int []:

[__DynamicallyInvokable] 
public static int[] GetBits(decimal d)
{
    return new int[] { d.lo, d.mid, d.hi, d.flags };
}

Phần tử thứ tư của mảng được trả về chứa hệ số tỷ lệ và dấu hiệu. Và như MSDN nói rằng hệ số tỷ lệ ngầm định là số 10, được nâng lên thành số mũ từ 0 đến 28. Đây chính là thứ chúng ta cần.

Do đó, dựa trên tất cả các điều tra ở trên, chúng ta có thể xây dựng phương pháp của mình:

private const int SIGN_MASK = ~Int32.MinValue;

public static int GetDigits4(decimal value)
{
    return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}

Ở đây, SIGN_MASK được sử dụng để bỏ qua dấu hiệu. Sau khi logic và chúng tôi cũng đã chuyển kết quả có 16 bit sang bên phải để nhận hệ số tỷ lệ thực tế. Giá trị này, cuối cùng, cho biết số chữ số sau dấu thập phân.

Lưu ý rằng ở đây MSDN cũng cho biết hệ số tỷ lệ cũng bảo toàn bất kỳ số không ở cuối trong số Thập phân. Các số không ở cuối không ảnh hưởng đến giá trị của một số Thập phân trong các phép toán số học hoặc so sánh. Tuy nhiên, các số không ở cuối có thể được tiết lộ bởi phương thức ToString nếu một chuỗi định dạng thích hợp được áp dụng.

Giải pháp này có vẻ là giải pháp tốt nhất, nhưng chờ đợi, còn nhiều hơn thế. Bằng cách truy cập các phương thức private trong C #, chúng ta có thể sử dụng các biểu thức để xây dựng quyền truy cập trực tiếp vào trường flags và tránh xây dựng mảng int:

public delegate int GetDigitsDelegate(ref Decimal value);

public class DecimalHelper
{
    public static readonly DecimalHelper Instance = new DecimalHelper();

    public readonly GetDigitsDelegate GetDigits;
    public readonly Expression<GetDigitsDelegate> GetDigitsLambda;

    public DecimalHelper()
    {
        GetDigitsLambda = CreateGetDigitsMethod();
        GetDigits = GetDigitsLambda.Compile();
    }

    private Expression<GetDigitsDelegate> CreateGetDigitsMethod()
    {
        var value = Expression.Parameter(typeof(Decimal).MakeByRefType(), "value");

        var digits = Expression.RightShift(
            Expression.And(Expression.Field(value, "flags"), Expression.Constant(~Int32.MinValue, typeof(int))), 
            Expression.Constant(16, typeof(int)));

        //return (value.flags & ~Int32.MinValue) >> 16

        return Expression.Lambda<GetDigitsDelegate>(digits, value);
    }
}

Mã đã biên dịch này được gán cho trường GetDigits. Lưu ý rằng hàm nhận giá trị thập phân dưới dạng ref, vì vậy không có quá trình sao chép thực tế nào được thực hiện - chỉ tham chiếu đến giá trị. Sử dụng hàm GetDigits từ DecimalHelper thật dễ dàng:

decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);

Đây là phương pháp nhanh nhất có thể để nhận số chữ số sau dấu thập phân cho các giá trị thập phân.


3
thập phân r = (thập phân) -0.01f; và giải pháp không thành công. (trên tất cả các câu trả lời tôi thấy trong trang này ...) :)
GY

5
LƯU Ý: Về toàn bộ (Decimal) 0.01f, bạn đang truyền một dấu phẩy động, vốn dĩ KHÔNG CHÍNH XÁC, thành một thứ gì đó rất có cấu trúc như Decimal. Hãy xem đầu ra của Console.WriteLine ((Decimal) 0.01f). Số thập phân được hình thành trong phép đúc THỰC TẾ có 3 chữ số, đó là lý do tại sao tất cả các giải pháp được cung cấp đều nói là 3 thay vì 2. Mọi thứ thực sự đang hoạt động như mong đợi, "vấn đề" là bạn đang mong đợi các giá trị dấu phẩy động là chính xác. Họ không phải.
Nicholi

@Nicholi điểm của bạn không thành công khi bạn nhận ra rằng 0.010.010là chính xác bằng số . Hơn nữa, ý tưởng rằng kiểu dữ liệu số có một số loại ngữ nghĩa "số chữ số được sử dụng" có thể dựa vào là hoàn toàn nhầm lẫn (không nên nhầm lẫn với "số chữ số cho phép". Đừng nhầm lẫn giữa cách trình bày (sự hiển thị của giá trị của một số trong một cơ số cụ thể, ví dụ: khai triển thập phân của giá trị được chỉ ra bởi khai triển nhị phân 111) với giá trị cơ bản! Để nhắc lại, các số không phải là chữ số, cũng không phải là chữ số .
ErikE 21/08

6
Chúng có giá trị tương đương nhau, nhưng không phải bằng chữ số có nghĩa. Đó là một trường hợp sử dụng lớn của lớp Decimal. Nếu tôi hỏi có bao nhiêu chữ số trong 0,010m chữ, bạn sẽ nói chỉ có 2? Mặc dù điểm số của giáo viên toán / khoa học trên toàn cầu sẽ cho bạn biết con số 0 cuối cùng có đáng kể không? Vấn đề chúng ta đang đề cập được biểu hiện bằng cách ép kiểu từ dấu phẩy động sang Hệ thập phân. Không phải bản thân việc sử dụng GetBits, nó đang hoạt động chính xác như được ghi lại. Nếu bạn không quan tâm đến các chữ số có nghĩa, thì bạn có vấn đề và có thể không nên sử dụng lớp Thập phân ngay từ đầu.
Nicholi

1
@theberserker Theo như tôi nhớ, không có cách nào bắt được - nó sẽ hoạt động theo cả hai cách.
Kristiyan Dimitrov

13

Dựa vào biểu diễn bên trong của số thập phân không phải là điều tuyệt vời.

Còn cái này thì sao:

    int CountDecimalDigits(decimal n)
    {
        return n.ToString(System.Globalization.CultureInfo.InvariantCulture)
                //.TrimEnd('0') uncomment if you don't want to count trailing zeroes
                .SkipWhile(c => c != '.')
                .Skip(1)
                .Count();
    }

11

bạn có thể sử dụng InvariantCulture

string priceSameInAllCultures = price.ToString(System.Globalization.CultureInfo.InvariantCulture);

một khả năng khác sẽ là làm điều gì đó như vậy:

private int GetDecimals(decimal d, int i = 0)
{
    decimal multiplied = (decimal)((double)d * Math.Pow(10, i));
    if (Math.Round(multiplied) == multiplied)
        return i;
    return GetDecimals(d, i+1);
}

Làm thế nào để tôi tìm được số chữ số thập phân trong số thập phân? Tôi không gặp vấn đề gì khi chuyển đổi số thập phân thành một chuỗi tốt trong bất kỳ nền văn hóa nào. Theo câu hỏi tôi đang cố gắng để tìm số chữ số thập phân đã có trên hai chữ số thập phân
Jesse Carter

@JesseCarter: Có nghĩa là bạn luôn có thể chia nhỏ ..
Austin Salonen

@AustinSalonen Thật không? Tôi đã không nhận thức được rằng việc sử dụng InvariantCulture sẽ thực thi việc sử dụng một thời gian để tách các số thập phân
Jesse Carter

như bạn đã làm trước đây, nó sẽ luôn đặt giá thành chuỗi với a. làm dấu phân cách thập phân. nhưng không phải là cách của mình nhất thanh lịch trong quan điểm của tôi ...
fixagon


8

Hầu hết mọi người ở đây dường như không biết rằng số thập phân coi các số 0 ở cuối là quan trọng cho việc lưu trữ và in ấn.

Vì vậy, 0,1m, 0,10m và 0,100m có thể được so sánh bằng nhau, chúng được lưu trữ khác nhau (như giá trị / tỷ lệ 1/1, 10/2 và 100/3, tương ứng) và sẽ được in tương ứng là 0,1, 0,10 và 0,100 , bởi ToString().

Như vậy, các giải pháp mà báo cáo "quá cao một độ chính xác" đang thực sự báo cáo đúng chính xác, về decimalthuật ngữ 's.

Ngoài ra, các giải pháp dựa trên toán học (như nhân với lũy thừa của 10) có thể sẽ rất chậm (số thập phân chậm hơn ~ 40 lần so với số học gấp đôi và bạn cũng không muốn kết hợp dấu phẩy động vì điều đó có thể dẫn đến không chính xác ). Tương tự như vậy, truyền đến inthoặc longnhư một phương tiện cắt ngắn dễ xảy ra lỗi ( decimalcó phạm vi lớn hơn nhiều so với một trong hai phạm vi đó - nó dựa trên số nguyên 96 bit).

Mặc dù không trang nhã như vậy, nhưng cách sau có thể sẽ là một trong những cách nhanh nhất để có được độ chính xác (khi được định nghĩa là "số thập phân không bao gồm số 0 ở cuối"):

public static int PrecisionOf(decimal d) {
  var text = d.ToString(System.Globalization.CultureInfo.InvariantCulture).TrimEnd('0');
  var decpoint = text.IndexOf('.');
  if (decpoint < 0)
    return 0;
  return text.Length - decpoint - 1;
}

Nền văn hóa bất biến đảm bảo một '.' dưới dạng dấu thập phân, các số 0 ở cuối được cắt bớt, và sau đó chỉ là vấn đề xem có bao nhiêu vị trí còn lại sau dấu thập phân (nếu thậm chí có một).

Chỉnh sửa: đã thay đổi kiểu trả về thành int


1
@mvmorten Không chắc tại sao bạn cảm thấy cần phải thay đổi kiểu trả về thành int; byte đại diện chính xác hơn giá trị trả về: không dấu và phạm vi nhỏ (0-29, trong thực tế).
Zastai

1
Tôi đồng ý rằng các giải pháp dựa trên tính toán và lặp đi lặp lại là chậm (ngoài việc không tính đến các số 0 ở cuối). Tuy nhiên, phân bổ một chuỗi cho chuỗi này và thay vào đó hoạt động trên chuỗi đó không phải là điều hiệu quả nhất nên làm, đặc biệt là trong các bối cảnh quan trọng về hiệu suất và với GC chậm. Truy cập thang đo thông qua logic con trỏ nhanh hơn và miễn phí phân bổ.
Martin Tilo Schmitz

Vâng, việc lấy tỷ lệ có thể được thực hiện hiệu quả hơn nhiều - nhưng điều đó sẽ bao gồm các số 0 ở cuối. Và việc loại bỏ chúng yêu cầu thực hiện số học trên phần nguyên.
Zastai

6

Và đây là một cách khác, sử dụng kiểu SqlDecimal có thuộc tính tỷ lệ với số lượng các chữ số bên phải của số thập phân. Truyền giá trị thập phân của bạn sang SqlDecimal và sau đó truy cập Quy mô.

((SqlDecimal)(decimal)yourValue).Scale

1
Nhìn vào mã tham chiếu của Microsoft , truyền sang SqlDecimal sử dụng nội bộ GetBytesđể phân bổ mảng Byte thay vì truy cập các byte trong ngữ cảnh không an toàn. Thậm chí còn có một ghi chú và nhận xét mã trong mã tham chiếu, nêu rõ điều đó và cách họ có thể làm điều đó thay thế. Tại sao họ không là một bí ẩn đối với tôi. Tôi muốn làm rõ điều này và truy cập trực tiếp vào các bit tỷ lệ thay vì ẩn GC Alloc trong dàn diễn viên này, vì nó không rõ ràng lắm về những gì nó làm.
Martin Tilo Schmitz

4

Cho đến nay, gần như tất cả các giải pháp được liệt kê đang cấp phát Bộ nhớ GC, đây là cách C # để thực hiện mọi việc nhưng không lý tưởng trong các môi trường quan trọng về hiệu suất. (Những cái không phân bổ sử dụng vòng lặp và cũng không xem xét các số không ở cuối.)

Vì vậy, để tránh GC Allocs, bạn chỉ có thể truy cập các bit tỷ lệ trong một ngữ cảnh không an toàn. Điều đó nghe có vẻ mong manh nhưng theo nguồn tham chiếu của Microsoft , bố cục cấu trúc của số thập phân là Tuần tự và thậm chí có chú thích trong đó, không thay đổi thứ tự của các trường:

    // NOTE: Do not change the order in which these fields are declared. The
    // native methods in this class rely on this particular order.
    private int flags;
    private int hi;
    private int lo;
    private int mid;

Như bạn có thể thấy, int đầu tiên ở đây là trường flags. Từ tài liệu và như đã đề cập trong các nhận xét khác ở đây, chúng ta biết rằng chỉ các bit từ 16-24 mã hóa thang đo và chúng ta cần tránh bit thứ 31 mã hóa dấu hiệu. Vì int có kích thước 4 byte, chúng ta có thể làm điều này một cách an toàn:

internal static class DecimalExtensions
{
  public static byte GetScale(this decimal value)
  {
    unsafe
    {
      byte* v = (byte*)&value;
      return v[2];
    }
  }
}

Đây phải là giải pháp hiệu quả nhất vì không có GC phân bổ mảng byte hoặc chuyển đổi ToString. Tôi đã thử nghiệm nó với .Net 4.x và .Net 3.5 trong Unity 2019.1. Nếu có bất kỳ phiên bản nào không thành công, vui lòng cho tôi biết.

Biên tập:

Cảm ơn @Zastai đã nhắc tôi về khả năng sử dụng bố cục cấu trúc rõ ràng để thực tế đạt được cùng một logic con trỏ bên ngoài mã không an toàn:

[StructLayout(LayoutKind.Explicit)]
public struct DecimalHelper
{
    const byte k_SignBit = 1 << 7;

    [FieldOffset(0)]
    public decimal Value;

    [FieldOffset(0)]
    public readonly uint Flags;
    [FieldOffset(0)]
    public readonly ushort Reserved;
    [FieldOffset(2)]
    byte m_Scale;
    public byte Scale
    {
        get
        {
            return m_Scale;
        }
        set
        {
            if(value > 28)
                throw new System.ArgumentOutOfRangeException("value", "Scale can't be bigger than 28!")
            m_Scale = value;
        }
    }
    [FieldOffset(3)]
    byte m_SignByte;
    public int Sign
    {
        get
        {
            return m_SignByte > 0 ? -1 : 1;
        }
    }
    public bool Positive
    {
        get
        {
            return (m_SignByte & k_SignBit) > 0 ;
        }
        set
        {
            m_SignByte = value ? (byte)0 : k_SignBit;
        }
    }
    [FieldOffset(4)]
    public uint Hi;
    [FieldOffset(8)]
    public uint Lo;
    [FieldOffset(12)]
    public uint Mid;

    public DecimalHelper(decimal value) : this()
    {
        Value = value;
    }

    public static implicit operator DecimalHelper(decimal value)
    {
        return new DecimalHelper(value);
    }

    public static implicit operator decimal(DecimalHelper value)
    {
        return value.Value;
    }
}

Để giải quyết vấn đề ban đầu, bạn có thể loại bỏ tất cả các trường bên cạnh ValueScalenhưng có thể hữu ích cho ai đó có tất cả chúng.


1
Bạn cũng có thể tránh mã không an toàn bằng cách mã hóa cấu trúc của riêng bạn với bố cục rõ ràng - đặt một số thập phân ở vị trí 0, sau đó đặt byte / int tại các vị trí thích hợp. Một cái gì đó như:[StructLayout(LayoutKind.Explicit)] public struct DecimalHelper { [FieldOffset(0)] public decimal Value; [FieldOffset(0)] public uint Flags; [FieldOffset(0)] public ushort Reserved; [FieldOffset(2)] public byte Scale; [FieldOffset(3)] public DecimalSign Sign; [FieldOffset(4)] public uint ValuePart1; [FieldOffset(8)] public ulong ValuePart2; }
Zastai

Cảm ơn @Zastai, điểm tốt. Tôi cũng đã kết hợp cách tiếp cận đó. :)
Martin Tilo Schmitz

1
Một điều cần lưu ý: đặt thang đo ngoài phạm vi 0-28 sẽ gây ra vỡ. ToString () có xu hướng hoạt động, nhưng số học không thành công.
Zastai

Cảm ơn một lần nữa @Zastai, tôi đã thêm một séc cho điều đó :)
Martin Tilo Schmitz

Một điều khác: một số người ở đây không muốn tính đến các số 0 thập phân theo sau. Nếu bạn xác định a const decimal Foo = 1.0000000000000000000000000000m;thì chia một số thập phân cho số đó sẽ xếp lại tỷ lệ đó cho tỷ lệ thấp nhất có thể (tức là không còn bao gồm các số 0 ở cuối thập phân nữa). Tuy nhiên, tôi chưa đánh giá điều này để xem liệu nó có nhanh hơn cách tiếp cận dựa trên chuỗi mà tôi đã đề xuất ở nơi khác hay không.
Zastai

2

Tôi đã viết một phương pháp nhỏ ngắn gọn ngày hôm qua cũng trả về số chữ số thập phân mà không cần phải dựa vào bất kỳ phân tách chuỗi hoặc văn hóa nào là lý tưởng:

public int GetDecimalPlaces(decimal decimalNumber) { // 
try {
    // PRESERVE:BEGIN
        int decimalPlaces = 1;
        decimal powers = 10.0m;
        if (decimalNumber > 0.0m) {
            while ((decimalNumber * powers) % 1 != 0.0m) {
                powers *= 10.0m;
                ++decimalPlaces;
            }
        }
return decimalPlaces;

@ sửa chữa-như-mã khác tương tự như câu trả lời thứ hai của bạn mặc dù đối với một cái gì đó như thế này tôi ủng hộ cách tiếp cận lặp đi lặp lại hơn là sử dụng đệ quy
Jesse Carter

Các bài bản gốc khẳng định rằng: 19.0 should return 1. Giải pháp này sẽ luôn giả định số lượng tối thiểu là 1 chữ số thập phân và bỏ qua các số không ở cuối. số thập phân có thể có những số đó vì nó sử dụng hệ số tỷ lệ. Yếu tố tỷ lệ có thể được truy cập như trong byte 16-24 của phần tử có chỉ số 3 trong mảng được lấy từ Decimal.GetBytes()hoặc bằng cách sử dụng logic con trỏ.
Martin Tilo Schmitz

2

Tôi đang sử dụng một cái gì đó rất giống với câu trả lời của Clement:

private int GetSignificantDecimalPlaces(decimal number, bool trimTrailingZeros = true)
{
  string stemp = Convert.ToString(number);

  if (trimTrailingZeros)
    stemp = stemp.TrimEnd('0');

  return stemp.Length - 1 - stemp.IndexOf(
         Application.CurrentCulture.NumberFormat.NumberDecimalSeparator);
}

Hãy nhớ sử dụng System.Windows.Forms để truy cập vào Application.CurrentCulture


1

Bạn co thể thử:

int priceDecimalPlaces =
        price.ToString(System.Globalization.CultureInfo.InvariantCulture)
              .Split('.')[1].Length;

7
Điều này sẽ không thất bại khi số thập phân là một số nguyên? [1]
Silvermind

1

Tôi sử dụng cơ chế sau trong mã của mình

  public static int GetDecimalLength(string tempValue)
    {
        int decimalLength = 0;
        if (tempValue.Contains('.') || tempValue.Contains(','))
        {
            char[] separator = new char[] { '.', ',' };
            string[] tempstring = tempValue.Split(separator);

            decimalLength = tempstring[1].Length;
        }
        return decimalLength;
    }

đầu vào thập phân = 3.376; var instring = input.ToString ();

gọi GetDecimalLength (cài đặt)


1
Điều này không phù hợp với tôi vì biểu diễn ToString () của giá trị thập phân thêm "00" vào cuối dữ liệu của tôi - Tôi đang sử dụng kiểu dữ liệu Decimal (12,4) từ SQL Server.
PeterX

Bạn có thể truyền dữ liệu của mình sang kiểu thập phân c # không và thử giải pháp. Đối với tôi khi tôi sử dụng Tostring () trên giá trị thập phân c #, tôi không bao giờ thấy "00".
Srikanth

1

Sử dụng đệ quy bạn có thể làm:

private int GetDecimals(decimal n, int decimals = 0)  
{  
    return n % 1 != 0 ? GetDecimals(n * 10, decimals + 1) : decimals;  
}

Các bài bản gốc khẳng định rằng: 19.0 should return 1. Giải pháp này sẽ bỏ qua các số không ở cuối. số thập phân có thể có những số đó vì nó sử dụng hệ số tỷ lệ. Hệ số tỷ lệ có thể được truy cập như trong byte 16-24 của phần tử có chỉ số 3 trong Decimal.GetBytes()mảng hoặc bằng cách sử dụng logic con trỏ.
Martin Tilo Schmitz

1
string number = "123.456789"; // Convert to string
int length = number.Substring(number.IndexOf(".") + 1).Length;  // 6

0

Tôi đề nghị sử dụng phương pháp này:

    public static int GetNumberOfDecimalPlaces(decimal value, int maxNumber)
    {
        if (maxNumber == 0)
            return 0;

        if (maxNumber > 28)
            maxNumber = 28;

        bool isEqual = false;
        int placeCount = maxNumber;
        while (placeCount > 0)
        {
            decimal vl = Math.Round(value, placeCount - 1);
            decimal vh = Math.Round(value, placeCount);
            isEqual = (vl == vh);

            if (isEqual == false)
                break;

            placeCount--;
        }
        return Math.Min(placeCount, maxNumber); 
    }

0

Là một phương pháp mở rộng thập phân có tính đến:

  • Các nền văn hóa khác nhau
  • Số nguyên
  • Số âm
  • Đặt theo sau các số không ở vị trí thập phân (ví dụ: 1.2300M sẽ trả về 2 chứ không phải 4)
public static class DecimalExtensions
{
    public static int GetNumberDecimalPlaces(this decimal source)
    {
        var parts = source.ToString(CultureInfo.InvariantCulture).Split('.');

        if (parts.Length < 2)
            return 0;

        return parts[1].TrimEnd('0').Length;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.