2010-02-12 21 views
18

Tôi thấy rằng, trong C#, làm tròn một decimal, theo mặc định, sử dụng MidpointRounding.ToEven. Điều này được mong đợi, và là những gì C# spec quyết định. Tuy nhiên, do sau:Tại sao .NET decimal.ToString (chuỗi) làm tròn từ số 0, dường như không nhất quán với thông số ngôn ngữ?

  • Một decimal dVal
  • Một định dạng string sFmt rằng, khi thông qua vào dVal.ToString(sFmt), sẽ dẫn đến một chuỗi chứa một phiên bản tròn của dVal

... nó là rõ ràng rằng decimal.ToString(string) trả về giá trị được làm tròn bằng cách sử dụng MidpointRounding.AwayFromZero. Điều này có vẻ là một mâu thuẫn trực tiếp của thông số C#.

Câu hỏi của tôi là: có lý do chính đáng trong trường hợp này không? Hay đây chỉ là sự mâu thuẫn trong ngôn ngữ?

Dưới đây, để tham khảo, tôi đã bao gồm một số mã viết để điều khiển một loại kết quả hoạt động làm tròn và kết quả hoạt động decimal.ToString(string), mỗi kết quả trên mọi giá trị trong một mảng giá trị decimal. Các đầu ra thực tế được nhúng. Sau đó, tôi đã bao gồm một đoạn có liên quan từ phần Đặc tả Ngôn ngữ C# trên loại decimal.

Đoạn mã ví dụ:

static void Main(string[] args) 
{ 
    decimal[] dArr = new decimal[] { 12.345m, 12.355m }; 

    OutputBaseValues(dArr); 
    // Base values: 
    // d[0] = 12.345 
    // d[1] = 12.355 

    OutputRoundedValues(dArr); 
    // Rounding with default MidpointRounding: 
    // Math.Round(12.345, 2) => 12.34 
    // Math.Round(12.355, 2) => 12.36 
    // decimal.Round(12.345, 2) => 12.34 
    // decimal.Round(12.355, 2) => 12.36 

    OutputRoundedValues(dArr, MidpointRounding.ToEven); 
    // Rounding with mr = MidpointRounding.ToEven: 
    // Math.Round(12.345, 2, mr) => 12.34 
    // Math.Round(12.355, 2, mr) => 12.36 
    // decimal.Round(12.345, 2, mr) => 12.34 
    // decimal.Round(12.355, 2, mr) => 12.36 

    OutputRoundedValues(dArr, MidpointRounding.AwayFromZero); 
    // Rounding with mr = MidpointRounding.AwayFromZero: 
    // Math.Round(12.345, 2, mr) => 12.35 
    // Math.Round(12.355, 2, mr) => 12.36 
    // decimal.Round(12.345, 2, mr) => 12.35 
    // decimal.Round(12.355, 2, mr) => 12.36 

    OutputToStringFormatted(dArr, "N2"); 
    // decimal.ToString("N2"): 
    // 12.345.ToString("N2") => 12.35 
    // 12.355.ToString("N2") => 12.36 

    OutputToStringFormatted(dArr, "F2"); 
    // decimal.ToString("F2"): 
    // 12.345.ToString("F2") => 12.35 
    // 12.355.ToString("F2") => 12.36 

    OutputToStringFormatted(dArr, "###.##"); 
    // decimal.ToString("###.##"): 
    // 12.345.ToString("###.##") => 12.35 
    // 12.355.ToString("###.##") => 12.36 

    Console.ReadKey(); 
} 

private static void OutputBaseValues(decimal[] dArr) 
{ 
    Console.WriteLine("Base values:"); 
    for (int i = 0; i < dArr.Length; i++) Console.WriteLine("d[{0}] = {1}", i, dArr[i]); 
    Console.WriteLine(); 
} 

private static void OutputRoundedValues(decimal[] dArr) 
{ 
    Console.WriteLine("Rounding with default MidpointRounding:"); 
    foreach (decimal d in dArr) Console.WriteLine("Math.Round({0}, 2) => {1}", d, Math.Round(d, 2)); 
    foreach (decimal d in dArr) Console.WriteLine("decimal.Round({0}, 2) => {1}", d, decimal.Round(d, 2)); 
    Console.WriteLine(); 
} 

private static void OutputRoundedValues(decimal[] dArr, MidpointRounding mr) 
{ 
    Console.WriteLine("Rounding with mr = MidpointRounding.{0}:", mr); 
    foreach (decimal d in dArr) Console.WriteLine("Math.Round({0}, 2, mr) => {1}", d, Math.Round(d, 2, mr)); 
    foreach (decimal d in dArr) Console.WriteLine("decimal.Round({0}, 2, mr) => {1}", d, decimal.Round(d, 2, mr)); 
    Console.WriteLine(); 
} 

private static void OutputToStringFormatted(decimal[] dArr, string format) 
{ 
    Console.WriteLine("decimal.ToString(\"{0}\"):", format); 
    foreach (decimal d in dArr) Console.WriteLine("{0}.ToString(\"{1}\") => {2}", d, format, d.ToString(format)); 
    Console.WriteLine(); 
} 


Đoạn từ phần 4.1.7 của C# Language Specification ("Các loại thập phân") (nhận được đầy đủ đặc tả here (.doc)):

Kết quả của phép toán đối với các giá trị kiểu thập phân là kết quả tính toán kết quả chính xác (tỷ lệ bảo toàn, được xác định cho mỗi toán tử) và sau đó làm tròn để khớp với biểu diễn. Kết quả được làm tròn thành giá trị thể hiện gần nhất, và khi kết quả bằng nhau gần hai giá trị có thể biểu thị, với giá trị có số chẵn ở vị trí chữ số nhỏ nhất (điều này được gọi là "làm tròn ngân hàng"). Kết quả bằng không luôn có dấu 0 và tỷ lệ là 0.

Thật dễ dàng để thấy rằng chúng có thể không đang xem xét ToString(string) trong đoạn này, nhưng tôi có khuynh hướng nghĩ rằng nó phù hợp với mô tả này.

+2

Có thể bạn nên xem xét rằng C# không có phương thức 'ToString (string)'. Khuôn khổ .NET. Tôi không chắc chắn rằng .NET Framework bị ràng buộc tuân thủ các quy tắc của bất kỳ ngôn ngữ lập trình cụ thể nào. –

Trả lời

1

ToString() theo định dạng mặc định theo Culture, không theo khía cạnh tính toán của đặc điểm kỹ thuật. Rõ ràng là Culture cho ngôn ngữ của bạn (và hầu hết, từ ngoại hình của nó) mong đợi làm tròn đi từ số không.

Nếu bạn muốn hành vi khác nhau, bạn có thể vượt qua một IFormatProvider để ToString()

Tôi nghĩ trên, nhưng bạn là chính xác rằng nó luôn làm tròn đi từ zero không có vấn đề Culture. Kiểm tra các liên kết trong các ý kiến ​​để chứng minh.

+0

Tôi nghĩ rằng điều này gần đây là tốt, nhưng sau khi được nhắc tôi không thể thực sự tìm thấy bất cứ nơi nào trong CultureInfo hoặc NumberFormatInfo nơi làm tròn được xác định hoặc xác định. Bạn có thể chỉ ra nơi điều này thực sự xảy ra không? –

+0

Tôi không thể, và có vẻ như tôi có thể sai. * Giấy phép Cảnh báo: CLR nguồn sau khi liên kết * Có vẻ như http://www.koders.com/cpp/fid03737280F05F3996789AC863BDE66ACB337C1E9B.aspx?s=NumberToStringFormat#L1457 NumberToStringFormat cuộc gọi http://www.koders.com/cpp/fid03737280F05F3996789AC863BDE66ACB337C1E9B.aspx ? s = NumberToStringFormat # L838 RoundNumber luôn luôn làm tròn từ 0. –

+1

Vì câu trả lời tốt nhất có vẻ là nó không nhất quán trong ngôn ngữ, tôi sẽ đánh dấu câu trả lời này là câu trả lời. Cảm ơn! – stack

1

Rất có thể vì đây là cách xử lý tiền tệ tiêu chuẩn. Động lực cho việc tạo ra các số thập phân là điểm nổi không một công việc nghèo của việc đối phó với các giá trị tiền tệ, vì vậy bạn sẽ mong đợi đó là các quy tắc để phù hợp hơn với các tiêu chuẩn kế toán hơn so với tính chính xác toán học.

+0

Tôi không chắc tôi hiểu câu trả lời của bạn vì câu trả lời liên quan đến câu hỏi. – stack

6

Nếu bạn đọc kỹ thông số kỹ thuật, bạn sẽ thấy rằng không có sự mâu thuẫn ở đây.

Dưới đây là đoạn đó một lần nữa, với những phần quan trọng nhấn mạnh:

Kết quả của một hoạt động trên giá trị của loại thập phân là đó sẽ là kết quả của việc tính toán một kết quả chính xác (bảo quản quy mô, như đã định nghĩa cho mỗi toán tử) và sau đó làm tròn để khớp với biểu diễn. Các kết quả được làm tròn thành giá trị thể hiện gần nhất và, khi kết quả bằng nhau gần hai giá trị có thể biểu thị, với giá trị có số chẵn ở vị trí chữ số nhỏ nhất (điều này được gọi là "làm tròn ngân hàng"). Một kết quả không luôn luôn có một dấu hiệu của 0 và thang điểm 0.

Phần này của spec áp dụng cho phép tính số học trên decimal; chuỗi định dạng không phải là một trong số đó, và thậm chí nếu nó được, nó sẽ không quan trọng vì các ví dụ của bạn có độ chính xác thấp.

Để chứng minh các hành vi quy định tại spec, sử dụng đoạn mã sau:

Decimal d1 = 0.00000000000000000000000000090m; 
Decimal d2 = 0.00000000000000000000000000110m; 

// Prints: 0.0000000000000000000000000004 (rounds down) 
Console.WriteLine(d1/2); 

// Prints: 0.0000000000000000000000000006 (rounds up) 
Console.WriteLine(d2/2); 

Đó là tất cả spec đang nói về. Nếu kết quả của một số phép tính sẽ vượt quá giới hạn chính xác của loại decimal (29 chữ số), làm tròn của ngân hàng được sử dụng để xác định kết quả sẽ là gì.

+0

"Thật dễ dàng để thấy rằng họ có thể đã không xem xét ToString (chuỗi) trong đoạn này, nhưng tôi nghiêng để nghĩ rằng nó phù hợp trong mô tả này." – stack

Các vấn đề liên quan