. net core 3 дает разные результаты с плавающей запятой из версии 2.2 - PullRequest
2 голосов
/ 17 января 2020

Вот пример кода с выводами из. net core 2.2 и 3.1. Он показывает различные результаты вычислений для базового c выражения с плавающей запятой a ^ b.

В этом примере мы вычисляем 1,9 с точностью до 3. Предыдущие. NET фреймворки дали правильный результат, но. net core 3.0 и 3.1 дают другой результат.

Является ли это предполагаемым изменением и как мы можем перенести код финансового расчета в новую версию с гарантией того, что численные расчеты все равно дадут те же результаты? (Было бы неплохо, если бы. NET тоже имела десятичную библиотеку Math).

    public static class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("--- Decimal ---------");
            ComputeWithDecimalType();
            Console.WriteLine("--- Double ----------");
            ComputeWithDoubleType();

            Console.ReadLine();
        }

        private static void ComputeWithDecimalType()
        {
            decimal a = 1.9M;
            decimal b = 3M;
            decimal c = a * a * a;
            decimal d = (decimal) Math.Pow((double) a, (double) b);

            Console.WriteLine($"a * a * a                        = {c}");
            Console.WriteLine($"Math.Pow((double) a, (double) b) = {d}");
        }

        private static void ComputeWithDoubleType()
        {
            double a = 1.9;
            double b = 3;
            double c = a * a * a;
            double d = Math.Pow(a, b);

            Console.WriteLine($"a * a * a      = {c}");
            Console.WriteLine($"Math.Pow(a, b) = {d}");
        }
    }

. NET Core 2.2

--- Decimal - --------

a * a * a                        = 6.859
Math.Pow((double) a, (double) b) = 6.859

--- Двухместный ----------

a * a * a      = 6.859
Math.Pow(a, b) = 6.859

. NET Core 3.1

--- десятичный ---------

a * a * a                        = 6.859
Math.Pow((double) a, (double) b) = 6.859

--- двойной ----------

a * a * a      = 6.858999999999999
Math.Pow(a, b) = 6.858999999999999

1 Ответ

1 голос
/ 17 января 2020

. NET В ядре появилось множество синтаксического анализа и форматирования с плавающей запятой улучшений в соответствии IEEE с плавающей запятой. Одним из них является соответствие форматированию IEEE 754-2008.

До. NET Core 3.0, ToString() внутренне ограниченная точность до "всего лишь" 15 мест, в результате чего получается строка, которая не может быть проанализирована обратно к оригиналу , Значения вопроса отличаются на один бит .

В обоих. NET 4.7 и. NET Core 3, фактические байты остаются теми же. В обоих случаях вызов

BitConverter.GetBytes(d*d*d)

производит

85, 14, 45, 178, 157, 111, 27, 64

С другой стороны, BitConverter.GetBytes(6.859) производит:

86, 14, 45, 178, 157, 111, 27, 64

Даже в. NET Core 3, синтаксический анализ "6.859" создает вторую последовательность байтов:

BitConverter.GetBytes(double.Parse("6.859"))

Это разница в один бит. Старое поведение приводило к строке, которая не могла быть проанализирована обратно к исходному значению

Разница объясняется этим изменением:

ToString (), ToString ("G") и ToString ("R") теперь будет возвращать самую короткую строку для переворачивания. Это гарантирует, что пользователи получат что-то, что просто работает по умолчанию.

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

Для спецификатора формата "G", который принимает точность (например, G3), спецификатор точности теперь всегда учитывается. Для double с точностью менее 15 (включительно) и для float с точностью менее 6 (включительно) это означает, что вы получите ту же строку, что и раньше. Для точности, превышающей это, вы получите до стольких значащих цифр

Использование ToString("G15") дает 6.859, тогда как ToString("G16") дает 6.858999999999999, который имеет 16 дробных цифр.

Это напоминание о том, что нам всегда нужно указывать точность при работе с числами с плавающей запятой, будь то сравнение или форматирование

...