Подтвердить что ты не робот

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

Я заметил следующую несогласованность в С#/. 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.

4b9b3361

Ответ 1

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

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

Ответ 2

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

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".

Ответ 3

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

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

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

Ответ 4

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

Hm, после прокрутки рефлектора, возможно, нет:)