Проблемы округления. Net Core 3.1 против. Net Core 2.0 /.Net Framework - PullRequest
0 голосов
/ 10 февраля 2020

У меня возникают проблемы с округлением между . Net Core 3.0 и . Net Framework /. Net Core 2.x .

Я некоторое время искал в Интернете, но не смог найти подходящий термин для поиска, поэтому я публикую его здесь.

Я написал следующий пример консольное приложение, чтобы проиллюстрировать мою проблему:

class Program
{
    static void Main(string[] args)
    {
        const double x = 123.4567890 / 3.14159265358979;
        Console.WriteLine(x);

        const double y = 98.76543210 / 3.14159265358979;
        Console.WriteLine(y);

        const double z = 11.2233445566778899 / 3.14159265358979;
        Console.WriteLine(z);

        Console.ReadKey();
    }
}

Я запустил эту программу на разных платформах и получил следующий вывод:

. Net Framework 4.7.2

  • 39,2975164552063
  • 31,4380134506439
  • 3,57250152843761

. Net Core 2.0:

  • 39,2975164552063
  • 31,4380134506439
  • 3,57250152843761

. Net Core 3.0:

  • 39,2975164552063
  • 31,438013450643936
  • 3,5725015284376096

Как вы можете видеть выход 3.0 отличается от первых двух и получил большую точность, начиная с 13-го числа после плавающей точки нт.

Я предполагаю, что точность. Net Core 3.0 более точна.

Но в моем случае я хочу перейти с . Net Framework на . Net Core 3.0 . Перед миграцией я написал тесты для библиотеки . Net Framework , чтобы убедиться, что вычисления приведут к тому же результату после перехода на . Net Core 3.0 . Для этого я просто написал тесты, такие как:

//Arrange
const double expectedValue = 0.1232342802302;

//Act
var result = Subject.Calculate();
//Assert
result.Should.Be(expectedValue);

Если я перенесу код и выполню тесты, которые я написал в . Net Framework , тесты не пройдут. У меня есть небольшие различия, такие как

Expected item[0] to be 0.4451391569556069, but found 0.44513915698437145.
Expected result to be -13.142142181869094, but found -13.142142181869062.

Мой вопрос здесь; как заставить округлить . Net Core 3.0 так же, как . Net Framework /.Net Core 2.0 , поэтому я не получу эти незначительные различия.

И кто-нибудь может объяснить эту разницу / описать изменения округления в . Net Core 3.1 против . Net Framework ?

Ответы [ 2 ]

0 голосов
/ 10 февраля 2020

Это задокументированное изменение, которое делает форматер и анализатор совместимыми с IEEE 754-2008. Из раздела IEEE с плавающей точкой в документе What's new in .NET 3.0:

API-интерфейсы с плавающей точкой обновляются в соответствии с ревизией IEEE 754-2008. Целью этих изменений является раскрытие всех необходимых операций и обеспечение их поведенческого соответствия спецификации IEEE spe c. Для получения дополнительной информации об улучшениях с плавающей запятой см. Улучшения синтаксического анализа и форматирования с плавающей запятой in. NET Core 3.0 в блоге.

Примеры в блоге на самом деле адрес того, что здесь произошло с Pi (выделено мной):

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

Примером, где это было проблематично c, было Math.PI.ToString (), где строка, которая была ранее возвращена (для ToString () и ToString ("G")), была 3.14159265358979; вместо этого он должен был вернуть 3.14159265358979 31 .

Предыдущий результат, когда анализировался, возвращал значение, которое было внутренне отключено на 7 ULP (единицы на последнем месте) от фактического значения Math.PI . Это означало, что пользователям было очень легко попасть в сценарий, в котором они случайно потеряли бы некоторую точность значения с плавающей запятой, когда это было необходимо для его сериализации / десериализации.

Фактические данные не сохранились т изменилось. Значения y и z do имеют большую точность, даже в дюймах. NET 4.7. Что изменилось, так это форматтер. До Core 3.x форматировщик использовал бы только 15 цифр, даже если значения имели большую точность.

В блоге объясняется, как получить старое поведение:

Для ToString ( ) и ToString ("G"), вы можете использовать G15 в качестве спецификатора формата, поскольку это то, что предыдущие логики c делали бы внутри.

Следующий код:

const double y = 98.76543210 / 3.14159265358979;
Console.WriteLine(y);
Console.WriteLine("{0:G15}",y);

Напечатает:

31.438013450643936
31.4380134506439
0 голосов
/ 10 февраля 2020

Это странно .... Я установил решение с 4 проектами

  • a. NET Framework 4.7.2 project
  • a. NET Core 2.0 проект
  • a. NET проект Core 3.1
  • a. NET проект Core 3.1 для запуска всех 3 проектов одновременно.

В каждом проекте I использовал константу Math.PI, чтобы увидеть, если что-то изменится, и это действительно произошло, но не так, как я ожидал.

Если я запускаю четвертый проект, тот, который вызывает все 3, я получаю этот результат

Значения всех трех проектов одинаковы. Но если я запускаю их отдельно, я получаю следующие результаты:

. NET Framework

. NET Core 2

. NET Core 3

Так что для некоторых причина, по которой я получаю разные результаты от вашего. NET Core использует Math.PI константы, и они одинаковы для версии 2 и 3.1. Однако я получаю тот же результат, что и у вас с. NET Framework, который отличается от двух. NET Core. Но, как мы видели выше, если вы запустите все 3 проекта из другого проекта, сделанного в. NET Core, вы получите те же результаты, то есть, возможно, это вызывающий проект, который определяет, какое округление следует использовать. К сожалению, я не могу найти точную причину, почему это происходит, но если я правильно помню, есть некоторые незначительные различия в том, как округление работает в Windows по сравнению с Unix системами. Поскольку. NET Core является кроссплатформенным, я думаю, что используется округление Unix, а не Windows, которое, вероятно, используется. NET Framework, что приводит к этим различиям.

РЕДАКТИРОВАТЬ: Это выходит за рамки науки сейчас ... Я использовал постоянное значение 3.14159265358979 вместо Math.PI, что в теории то же самое (в соответствии с Документация Microsoft ). Но при использовании этого значения результаты снова меняются! Если вы запустите тест, в котором запущены все 3 проекта, вы все равно получите те же результаты для всех 3, но они отличаются от предыдущего запуска

39,2975164552063
31,438013450643936
3,5725015284376096

При запуске. NET Framework Framework вы Получите те же результаты, что и раньше, при запуске. NET Core, вы получите вышеуказанные результаты. Таким образом, использование постоянного значения вместо Math.PI изменяет результаты еще раз. Но это на самом деле бессмысленно, поскольку под капотом Math.PI - это просто двойная константа со значением 3.14159265358979

РЕДАКТИРОВАТЬ 2: Я написал ту же программу с Python

def main():
    x = 123.4567890 / 3.14159265358979
    print(x)
    y = 98.76543210 / 3.14159265358979
    print(y)
    z = 11.2233445566778899 / 3.14159265358979
    print(z)


if __name__ == "__main__":
    main()

и результаты идентичны. NET Core

39.2975164552063
31.438013450643936
3.5725015284376096

Затем я попытался сделать то же самое, используя Go

package main

import "fmt"

func main() {
    x := 123.4567890 / 3.14159265358979
    fmt.Println(x)
    y := 98.76543210 / 3.14159265358979
    fmt.Println(y)
    z := 11.2233445566778899 / 3.14159265358979
    fmt.Println(z)
}

И в этом случае результаты

39.2975164552063
31.43801345064394
3.5725015284376096

y округлено до ..94, тогда как x и z совпадают с python и. NET Core.

В качестве финального теста я попытался сделать это с Javascript / Node.JS

let x = 123.456789 / 3.14159265358979;
console.log(x);
let y = 98.7654321 / 3.14159265358979;
console.log(y);
let z = 11.2233445566778899 / 3.14159265358979;
console.log(z);

Но здесь также результаты такие же, как python и. Net Core

39.2975164552063
31.438013450643936
3.5725015284376096

Поскольку Python, JS,. NET Core и GO (если вы не учитываете округление y), являются кроссплатформенными, я предполагаю, что что-то связано с Windows экосистема, на которую опирается. NET framework. Было бы интересно попробовать другие фреймворки / языки, связанные с Windows, но я не знаю ничего, кроме. NET Framework (может быть, Visual Basi c?)

...