浮点问题:C++ 到 C# 的迁移

Posted

技术标签:

【中文标题】浮点问题:C++ 到 C# 的迁移【英文标题】:Floating point issue: C++ to C# migration 【发布时间】:2014-01-22 04:30:18 【问题描述】:

我正在做一个 C++ 到 C# 的迁移项目。而且我遇到了一个涉及浮点运算的问题。 在 C++ 中,有一个函数

int doubleToInt(double d)

    return (int)(d >= 0.0 ? (d + 0.1) : (d - 0.1));

与我迁移到 C# 的函数相同(请注意,在 C++ 中,sizeof(int) 是 2 bytes。所以我使用 short 作为返回类型)

private static short doubleToInt(double d)

    return (short)(d >= 0.0 ? (d + 0.1) : (d - 0.1));

在这个转换之后,我正在做一些操作并生成一个二进制文件。与 C++ 相比,C# 生成的二进制文件是不同的。即使我在调试时(在写入文件之前)比较值,我也会得到不同的答案。

现在我需要向我的客户解释为什么它不同。

Can someone give me inputs on why it is different?

据我所知,在 C++ 中进行浮点算术运算时生成的临时值具有更高的精度。

Are there any other points? So that I can defend by telling "The way C++ handles the floating point is different from C# 或者我可以修改 C# 程序以匹配 C++ 输出吗?是否可以?此外,我无法修改 C++ 遗留代码。我需要在 C# 中获得相同的结果。有可能吗?

【问题讨论】:

为什么不隔离结果不同的特定输入,并在此处发布? 虽然它是特定于实现的,但 C++ ints 通常是 32 位的,而 C# shorts 是 16 位的。 为什么在 C# 版本上使用 short (16bit) 而不是 int (32bit)? @ನಿಶಿತ್ - 如果您使用非常罕见的目标平台,您应该明确说明...大多数平台的大多数现代 C++ 编译器使用 32 位 int 这两个函数是否都在使用/生成托管代码?如果是这样,您可以编译到 MSIL 并发布代码吗? 【参考方案1】:

事实:

在给定正常程序输入的情况下,此函数在 C++ 和 C# 中返回不同的输出,并且 在给定受控相同输入的情况下,此函数在 C++ 和 C# 中返回相同的输出

建议:

此函数的正常程序输入在 C++ 和 C# 中是不同的。

关于后者,OP 在评论中指出“我还用 C++ 和 C# 创建了一个示例测试应用程序,并对输入进行了硬编码。通过将输入硬编码到 doubleToInt 函数,我得到了相同的结果。”这表明,给定相同的输入,函数的 C++ 和 C# 版本返回相同的输出。由此我们可以推断出不同输出的原因是不同的输入。

OP 还声明“在调试时,为了比较结果,如果我看到 C++ 和 C# 的输出,对于同一组值来说是不同的。”但是,这还没有定论,因为调试器和打印语句用于调试通常不会打印浮点对象的完整、精确值。很多时候,它们四舍五入到六位有效数字。例如,一个简单的std::cout << x 将 10000.875 和 10000.9375 都显示为“10000.9”,但它们是不同的数字,在doubleToInt 中会产生不同的输出。

总之,问题可能是程序中的早期工作,在调用doubleToInt 之前,遇到浮点舍入或其他错误,并在C++ 和C# 版本中将不同的值传递给doubleToInt。要对此进行测试,请将 exact 输入打印到 doubleToInt 并查看它们在两个版本中是否不同。

可以通过以下方式准确打印输入:

如果您的实现支持,请使用%a 格式。 (这是一个以十六进制浮点表示法打印浮点值的 C 功能。使用 printf 时,一些 C++ 库支持它。) 将精度设置得非常高并打印,与std::cout.precision(100) 一样。一些 C++ 实现可能仍然无法打印准确的值(这是一个质量问题),但它们应该打印足够的数字以区分准确的值和相邻的 double 值。 打印值表示的字节(通过将指向浮点对象的指针转换为指向unsigned char 的指针并打印单个char 对象)。

根据提供的代码,问题不太可能是doubleToInt 中的浮点问题。语言定义允许浮点计算有一定的松懈,因此理论上有可能以超精度计算d+.1,而不是正常的double 精度,然后转换为intshort。但是,这只会在极少数情况下导致不同的结果,其中d+.1double 计算的精度向上舍入为整数,但d+.1 以超高的精度计算仍略低于整数。这要求大约 38 位(double 有效位中的 53 位减去整数部分中的 16 位加上一位用于舍入的位)具有特定值,因此我们预计它仅偶然发生 2750 亿次中的 1 次(假设均匀分布是一个合适的模型)。

事实上,添加 .1 向我表明,有人试图纠正他们期望为整数的结果中的浮点错误。如果有人试图将一个“自然”值转换为整数,通常的做法是四舍五入到最接近的值(如std::round),或者有时截断。添加 0.1 表明他们正在尝试计算他们期望为整数但由于浮点错误而得到的结果如 3.999 或 4.001,因此他们通过添加 .1 并截断来“纠正”它。因此,我怀疑程序早期存在浮点错误。也许它们在 C# 中被加剧了。

【讨论】:

+1 表示 "%a" 以及 .1 位和一般完整性。对于doubleToInt(),我预计是 0.5 而不是 0.1。 std::round() 最好。【参考方案2】:

您正在尝试使用默认四舍五入对此处的数字进行四舍五入。 C++ 没有规定舍入的方向,考虑到不同的结果,它可能与 C# 不同。

【讨论】:

我没有明白你在这里想说什么。与 C++ 相比,我在 C# 中使用相同的函数。在调试时,为了比较结果,如果我看到 C++ 和 C# 的输出,相同的一组值是不同的。我还用 C++ 和 C# 创建了一个示例测试应用程序,并对输入进行了硬编码。通过将输入硬编码到 doubleToInt 函数,我得到了相同的结果。 另外,C++ 中的 sizeof(int) 是 2 个字节。所以我在 C# 中使用short。 @ನಿಶಿತ್:不,不是。就标准而言,它可以是 1、4、17 或任何其他整数。【参考方案3】:

如果给定平台上的double 值超过sizeof(short/int),您的函数将technically 产生不同的结果。

当您从 double 截断(丢失精度)到 intshort 时,这两个函数都有可能丢失数据。假设您的目标是 MS 环境 sizeof(double) == 8sizeof(int) == 4sizeof(short) == 2;这对于 Windows 环境中的 C++ 和 C# 都是正确的(在 MS 构建中,字节序和位 (32/64) 与这些大小无关)。

您还需要提供有关在调用函数以生成二进制输出之后发生的情况的更多信息。从技术上讲,“二进制”文件输出只是无符号字符输出(即sizeof() == 1);这意味着您如何将函数的输出“写入”到文件中也会严重影响 C++ 和 C# 中的文件在输出数字类型方面 (double/int/short)。

您是在 C++ 中使用 fopen 调用并在文件中使用特定格式的输出,还是使用 std::fstream(或其他东西)?您如何将数据写入 C# 中的文件?您是在执行file.Write(doubleToInt(d)) 之类的操作(假设您使用的是System.IO.StreamWriter)还是使用System.IO.FileStream 并将doubleToInt 输出转换为byte[] 然后调用file.Write(dtiByteArr)

根据给出的信息,我最好的猜测是,您的 C# 函数返回 short 而不是 int,这会导致当传入函数的值大于 short.MaxValue .

【讨论】:

即使在写入文件之前,我与 C++ 比较时的值也不同。此外,我的环境中 C++ 中的 sizeof(int) 为 2 字节。所以我在 C# 中使用 short 您的客户环境中的sizeof(int) 是什么?【参考方案4】:

我认为您的问题与如何将数据 (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

【讨论】:

【参考方案5】:

我还没有完全投入其中,所以也许我错了,但这可能与这里提到的有关: In a thread about the difference on Float, Decimal and Double

正如他所说:您使用的 Double 在 C# 中是浮点二进制类型。 (10001.10010110011) 也许,C++ 中的 Double 更像 C# 中的小数点浮点类型。 (12345.65789) 如果你比较浮点型和浮点型,它不会给出相同的结果。

【讨论】:

不,C++ 没有十进制浮点类型。 C# 可以(称为decimal),但这里没有使用。 @Gabe C++ 是否指定浮点是用浮点二进制点类型完成的?我认为,就像在 C 中一样,FP 类型是依赖于实现的,而不是由语言指定的。假设语言指定了二进制文件,难道不是更普遍吗? @chux:不,它没有指定基数,但是除了 2 之外的任何东西都非常罕见。

以上是关于浮点问题:C++ 到 C# 的迁移的主要内容,如果未能解决你的问题,请参考以下文章

如何将结构从 C++ 迁移到 C#

将浮点数组从 C++ 获取到 C#

从 Visual C++ 6 迁移到 Visual C++ 2008 express

直接将 C++ 浮点数组成员编组到 C#,无需复制

为啥将 VB.NET 代码迁移到 C# 时,for 循环的行为会有所不同?

将 C++ 从 VS2003 迁移到 VS2005 需要哪些代码更改?