Почему .Net использует алгоритм округления в String.Format, который не согласуется с алгоритмом Math.Round () по умолчанию? - PullRequest
26 голосов
/ 09 февраля 2010

Я заметил следующее несоответствие в C # /. NET. Мне было интересно, почему это так.

Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.04, Math.Round(1.04, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.05, Math.Round(1.05, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.06, Math.Round(1.06, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.14, Math.Round(1.14, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.15, Math.Round(1.15, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.16, Math.Round(1.16, 1));
Console.WriteLine();
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.04, Math.Round(1.04, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.05, Math.Round(1.05, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.06, Math.Round(1.06, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.14, Math.Round(1.14, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.15, Math.Round(1.15, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.16, Math.Round(1.16, 1, MidpointRounding.AwayFromZero));

Выход:

1.0  | 1.0
1.1  | 1.0
1.1  | 1.1
1.1  | 1.1
1.2  | 1.2
1.2  | 1.2

1.0  | 1.0
1.1  | 1.1
1.1  | 1.1
1.1  | 1.1
1.2  | 1.2
1.2  | 1.2

Похоже, что по умолчанию форматирование строк выполняется для округления с использованием MidpointRounding.AwayFromZero, а не по умолчанию Math.Round () MidpointRounding.ToEven.

Ответы [ 4 ]

12 голосов
/ 09 февраля 2010

Как историческая справка, первоначальная реализация формата $ в Visual Basic также не соответствовала округлению до четности, то есть округлению Банкира. Оригинальный код Format $ был написан Тимом Патерсоном. Возможно, вы помните, что Тим был автором маленькой программы под названием QDOS (позже известной как MS-DOS), которая некоторое время была довольно хорошим продавцом.

Возможно, это еще один случай 25-летней обратной совместимости.

3 голосов
/ 21 января 2015

Кажется, эта проблема хуже, чем "простая" несогласованность:

double dd = 0.034999999999999996;

Math.Round(dd, 2); // 0.03
Math.Round(dd, 2, MidpointRounding.AwayFromZero); // 0.03
Math.Round(dd, 2, MidpointRounding.ToEven); // 0.03

string.Format("{0:N2}", dd); // "0.04"

Это абсолютно бананы. Кто знает, где, черт возьми, он получает "0,04".

2 голосов
/ 09 февраля 2010

Пожалуйста, посмотрите здесь: Возможная ошибка: математика. Раунд возвращает противоречивые результаты

WriteLine () просто вызывает Object.ToString (), что, в свою очередь, приводит к вызову Number.FormatDouble (this, null , NumberFormatInfo.CurrentInfo). Как видите, параметр для строки формата равен нулю. Если вы хотите получить реальную вещь от ToString (), вы должны использовать System.Diagnostics.Debug.WriteLine (n.ToString ("R")).

"Когда значение Single или Double форматируется с использованием этого спецификатора, оно сначала тестируется с использованием общего формата, с 15 цифрами точности для Double и 7 цифрами точности для Single. Если значение успешно проанализировано обратно в то же числовое значение, оно отформатировано с использованием общего спецификатора формата. Если значение не было успешно проанализировано обратно к тому же числовому значению, оно форматируется с использованием 17 цифр точности для двойного и 9 цифр точности для одиночного. " Стандартные строки числового формата

0 голосов
/ 09 февраля 2010

WriteLine(string, params object[]) вызывает string.Format и передает текущее CultureInfo, поэтому будет использовать ваш локализованный NumberFormatInfo, чтобы определить, как записать число. Math.Round не учитывает культуру, так как вы указываете, как именно вы хотите, чтобы она округлялась.

Хм, после того, как ковыряться в отражателе, может и нет :)

...