Проблема с плавающей точкой: миграция из C ++ в C #

0

Я работаю над проектом C++ в С#. И я застрял в проблеме, которая включает арифметику с плавающей запятой. В C++ существует функция

int doubleToInt(double d)
{
    return (int)(d >= 0.0 ? (d + 0.1) : (d - 0.1));
}

Та же самая функция, которую я переношу на С# как (обратите внимание, что в C++ размерof sizeof(int) равен 2 bytes. Поэтому я использую short как возвращаемый тип)

private static short doubleToInt(double d)
{
    return (short)(d >= 0.0 ? (d + 0.1) : (d - 0.1));
}

После этого преобразования я выполняю некоторую операцию и создаю двоичный файл. Двоичный файл, сгенерированный на С#, отличается от бинарного файла C++. Даже если я сравниваю значения во время отладки (перед записью в файл), я получаю разные ответы.

Теперь мне нужно объяснить моему клиенту, почему это другое.

Can someone give me inputs on why it is different?

То, что я знаю, временные данные, сгенерированные в C++ при выполнении арифметических операций с плавающей запятой, имеют более высокую точность.

Are there any other points? So that я can defend by telling "The way C++ handles the floating point is different from C# Are there any other points? So that я can defend by telling "The way C++ handles the floating point is different from C# или я могу изменить программу С# для соответствия C++ вывода? Возможно ли это? Кроме того, я не могу изменить C++ старый код. Мне нужно получить те же результаты на С#. Возможно ли это?

  • 2
    Почему бы не выделить конкретные материалы, по которым результаты отличаются, и опубликовать их здесь?
  • 2
    Хотя это зависит от реализации, C ++ int обычно 32-битные, а C # short 16-битные.
Показать ещё 7 комментариев
Теги:
floating-point
floating-point-precision

5 ответов

2
Лучший ответ

Факты, которые:

  • эта функция возвращает различный вывод в C++ по сравнению с С# при условии ввода обычной программы и
  • эта функция возвращает идентичный результат в C++ по сравнению с С# при заданном идентичном вводе

предлагает:

  • обычные программные входы для этой функции в C++ отличаются от С#.

Что касается последнего, в комментарии OP указывает: "Я также создал образец тестового приложения в C++ и С# и жестко закодировал вход. Твердое кодирование ввода для функции doubleToInt я получаю одинаковые результаты". Это говорит о том, что, учитывая идентичные входы, версии C++ и С# функции возвращают идентичные выходы. Из этого следует, что причиной разных выходов являются разные входы.

В OP также говорится: "При отладке, чтобы сравнить результаты, если я вижу вывод C++ и С#, он отличается для одного и того же набора значений". Однако это неубедительно, поскольку отладчики и операторы печати, используемые для отладки, часто не печатают полное точное значение объектов с плавающей запятой. Довольно часто они округляются до шести значащих цифр. Например, простой std::cout << x отображает как 10000.875, так и 10000.9375 как "10000.9", но они имеют разные значения и будут давать разные выходы в doubleToInt.

В заключение, проблема может заключаться в том, что предыдущая работа в программе, до doubleToInt, испытывает округление с плавающей запятой или другие ошибки и передает разные значения doubleToInt в версиях C++ и С#. Чтобы проверить это, напечатайте точные данные в doubleToInt и посмотрите, отличаются ли они в двух версиях.

Печать входов точно может быть выполнена с помощью:

  • Используйте формат %a, если ваша реализация поддерживает его. (Это функция C для печати значений с плавающей запятой в шестнадцатеричной системе с плавающей запятой. Некоторые библиотеки C++ поддерживают ее при использовании printf.)
  • Установите точность очень высоко и напечатайте, как и с std::cout.precision(100). Некоторые реализации C++ могут все еще не печатать точное значение (что является проблемой качества), но они должны печатать достаточно цифр, чтобы отличить точное значение от соседних double значений.
  • Распечатайте байты представления значения (путем преобразования указателя на объект с плавающей точкой на указатель на unsigned char и печать отдельных объектов char).

На основе представленного кода проблема вряд ли будет проблемой с плавающей запятой в doubleToInt. Определения языка допускают некоторое d+.1 оценки с плавающей точкой, поэтому теоретически возможно, что d+.1 оценивается с избыточной точностью вместо обычной double точности, а затем преобразуется в int или short. Однако это приведет к разным результатам только в очень редких случаях, когда d+.1 оценивается в double d+.1 до целого числа, но d+.1 оцененный с избыточной точностью, остается чуть ниже целого. Для этого требуется, чтобы около 38 бит (53 бит в double значении минус 16 бит в целочисленной части плюс один бит для округления) имели определенные значения, поэтому мы ожидаем, что это произойдет только около 1 из 275 миллиардов раз случайно (при условии, что единый распределение является подходящей моделью).

Фактически, добавление.1 подсказывает мне, что кто-то пытался исправить ошибки с плавающей запятой в результате, который они ожидали быть целым числом. Если у кого-то было "естественное" значение, которое они пытались преобразовать в целое число, обычным способом сделать это было бы округление до ближайшего значения (как и для std::round) или, иногда, для усечения. Добавление.1 предполагает, что они пытались вычислить то, что они ожидали быть целым числом, но получали результаты, такие как 3.999 или 4.001, из-за ошибок с плавающей запятой, поэтому они "исправили" это, добавив.1 и усеченный. Таким образом, я подозреваю, что ошибки с плавающей запятой существуют раньше в программе. Возможно, они усугубляются в С#.

  • 0
    +1 для "% a" и .1 бита и общей полноты. Для doubleToInt() я бы ожидал 0,5 вместо 0,1. std::round() - лучший.
0

Я думаю, ваша проблема связана с тем, как данные (short) записываются/читаются в/из двоичного файла. Вам нужно рассмотреть Big-Endian/Small-Endian, поэтому файл данных согласован независимо от того, на какой платформе находится код.

Проверьте класс System.BitConverter. Поле BitConverter.IsLittleEndian может помочь в преобразовании. Код должен выглядеть примерно так:

  short value = 12348;
  byte[] bytes = BitConverter.GetBytes(value);
  Console.WriteLine(BitConverter.ToString(bytes));

  if (BitConverter.IsLittleEndian)
     Array.Reverse(bytes);

  Console.WriteLine(BitConverter.ToString(bytes)); // write to your file
0

Ваши функции будут technically производить разные результаты, если double значения превышают sizeof(short/int) на данной платформе.

Обе функции имеют возможность потерять данные по мере того, как вы усекаете (теряете точность) от double до int или short. Предполагая, что вы нацеливаете среду MS sizeof(double) == 8, sizeof(int) == 4 и sizeof(short) == 2; это справедливо как для C++, так и для С# в среде Windows (endian-ness и bit-ness (32/64) не имеют значения в этих размерах в сборке MS).

Вам также нужно предоставить дополнительную информацию о том, что происходит ПОСЛЕ вызова функций для генерации двоичного вывода. Технически говоря, "двоичный" выход файла имеет только выход без знакового символа (т.е. sizeof() == 1); что означает, что вы записываете вывод ваших функций в файл, также может серьезно повлиять на ваши файлы как в C++, так и на С# в отношении вывода числовых типов (double/int/short).

Используете ли вы вызов fopen в C++ с определенным форматированным выходом в файл или используете std::fstream (или что-то еще)? Как вы записываете данные в файл на С#? Вы делаете что-то вроде file.Write(doubleToInt(d)) (при условии, что вы используете System.IO.StreamWriter), или вы используете System.IO.FileStream и преобразовываете вывод doubleToInt в byte[] затем вызываете file.Write(dtiByteArr)?

Все, что было сказано, мое лучшее предположение, основанное на приведенной информации, будет заключаться в том, что ваша функция С# возвращает short а не int вызывающий проблемы, когда значения, переданные функции, больше, чем short.MaxValue.

  • 0
    Даже перед записью в файл значения при сравнении с C ++ отличаются. Кроме того, sizeof (int) в C ++ в моей среде составляет 2 байта. Поэтому я использую короткие в C #
  • 0
    Каков sizeof(int) в вашей клиентской среде?
0

Здесь вы пытаетесь округлить числа, используя округление по умолчанию. C++ не задал направление округления, и он, вероятно, отличается от С#, учитывая разные результаты.

  • 0
    Я не понял, что вы пытаетесь сказать здесь. Я использую ту же функцию в C # по сравнению с C ++. Во время отладки, для сравнения результатов, если я вижу вывод C ++ и C #, он отличается для одного и того же набора значений. Я также создал пример тестового приложения на C ++ и C # и жестко запрограммировал ввод. Путем жесткого кодирования ввода в функцию doubleToInt я получаю те же результаты.
  • 0
    Кроме того, sizeof (int) в C ++ составляет 2 байта. Поэтому я использую шорт в C #.
Показать ещё 1 комментарий
-1

Я не полностью ввязался в это, так что, может быть, я могу ошибаться, но это могло бы иметь какое-то отношение к тому, что упоминалось здесь: в потоке о разнице на Float, Decimal и Double

Как он говорит: Double, который вы используете, в С# - тип плавающей двоичной точки. (10001.10010110011), возможно, Double в C++ больше похожи на десятичные числа в С#, тип плавающей десятичной точки. (12345.65789) И если вы сравниваете тип плавающей двоичной точки и тип с плавающей запятой, это не даст тот же результат.

  • 1
    Нет, C ++ не имеет десятичного типа с плавающей точкой. C # делает (называется decimal ), но это не используется здесь.
  • 0
    @ Gabe Указывает ли C ++, что с плавающей запятой выполняется тип с плавающей двоичной точкой? Я думал, как и в C, что тип FP зависит от реализации и не определяется языком. Разве этот двоичный файл не является гораздо более распространенным, чем предполагается, что его определяет язык?
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню