Я работаю над проектом 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++ старый код. Мне нужно получить те же результаты на С#. Возможно ли это?
Факты, которые:
предлагает:
Что касается последнего, в комментарии 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 и усеченный. Таким образом, я подозреваю, что ошибки с плавающей запятой существуют раньше в программе. Возможно, они усугубляются в С#.
doubleToInt()
я бы ожидал 0,5 вместо 0,1. std::round()
- лучший.
Я думаю, ваша проблема связана с тем, как данные (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
Ваши функции будут 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
.
sizeof(int)
в вашей клиентской среде?
Здесь вы пытаетесь округлить числа, используя округление по умолчанию. C++ не задал направление округления, и он, вероятно, отличается от С#, учитывая разные результаты.
Я не полностью ввязался в это, так что, может быть, я могу ошибаться, но это могло бы иметь какое-то отношение к тому, что упоминалось здесь: в потоке о разнице на Float, Decimal и Double
Как он говорит: Double, который вы используете, в С# - тип плавающей двоичной точки. (10001.10010110011), возможно, Double в C++ больше похожи на десятичные числа в С#, тип плавающей десятичной точки. (12345.65789) И если вы сравниваете тип плавающей двоичной точки и тип с плавающей запятой, это не даст тот же результат.
decimal
), но это не используется здесь.
int
обычно 32-битные, а C #short
16-битные.