如何避免单元测试中的浮点舍入错误?

2024-03-16

我正在尝试为一些对单精度浮点数数组进行操作的简单向量数学函数编写单元测试。这些函数使用 SSE 内在函数,并且在 32 位系统上运行测试时出现误报(至少我认为)(测试在 64 位上通过)。当操作遍历数组时,我积累了越来越多的舍入误差。这是单元测试代码和输出的片段(我的实际问题如下):

测试设置:

static const int N = 1024;
static const float MSCALAR = 42.42f;

static void setup(void) {
    input = _mm_malloc(sizeof(*input) * N, 16);
    ainput = _mm_malloc(sizeof(*ainput) * N, 16);
    output = _mm_malloc(sizeof(*output) * N, 16);
    expected = _mm_malloc(sizeof(*expected) * N, 16);

    memset(output, 0, sizeof(*output) * N);

    for (int i = 0; i < N; i++) {
        input[i] = i * 0.4f;
        ainput[i] = i * 2.1f;
        expected[i] = (input[i] * MSCALAR) + ainput[i];
    }
}

然后,我的主要测试代码调用要测试的函数(该函数执行与生成expected数组)并检查其输出expected上面生成的数组。检查的是接近度(0.0001 以内)而不是相等性。

示例输出:

0.000000    0.000000    delta: 0.000000
44.419998   44.419998   delta: 0.000000
...snip 100 or so lines...
2043.319946 2043.319946 delta: 0.000000
2087.739746 2087.739990 delta: 0.000244
...snip 100 or so lines...
4086.639893 4086.639893 delta: 0.000000
4131.059570 4131.060059 delta: 0.000488
4175.479492 4175.479980 delta: 0.000488
...etc, etc...

我知道我有两个问题:

  1. 在 32 位机器上,387 和 SSE 浮点运算单元之间的差异。我相信 387 使用更多位作为中间值。
  2. 我用来生成预期值的 42.42 值的非精确表示。

所以我的问题是,写有意义的正确方法是什么and对浮点数据进行数学运算的便携式单元测试?

*我所说的“可移植”是指应该同时支持 32 位和 64 位架构。


根据评论,我们看到正在测试的功能本质上是:

for (int i = 0; i < N; ++i)
    D[i] = A[i] * b + C[i];

where A[i], b, C[i], and D[i]都有类型float。当引用单次迭代的数据时,我会使用a, c, and d for A[i], C[i], and D[i].

下面是我们在测试此函数时可用于容错的分析。不过,首先我想指出,我们可以设计测试以确保不出现错误。我们可以选择的值A[i], b, C[i], and D[i]以便所有结果,无论是最终结果还是中间结果,都可以精确表示,并且不存在舍入误差。显然,这不会测试浮点运算,但这不是目标。目标是测试函数的代码:它是否执行计算所需函数的指令?只需选择能够揭示使用正确数据、加法、乘法或存储到正确位置的任何失败的值就足以揭示函数中的错误。我们相信硬件能够正确执行浮点运算,并且不会对此进行测试;我们只是想测试函数是否正确编写。为了实现这一点,我们可以,例如,设置b为 2 的幂,A[i]到各种小整数,并且C[i]各种小整数乘以b。如果需要,我可以更精确地详细说明这些值的限制。那么所有的结果都会是准确的,并且任何允许比较的公差的需要都会消失。

除此之外,让我们继续进行错误分析。

目标是发现功能实现中的错误。为此,我们可以忽略浮点运算中的小错误,因为我们正在寻找的错误类型几乎总是会导致大错误:使用了错误的操作,使用了错误的数据,或者结果没有存储在浮点运算中。期望的位置,因此实际结果几乎总是与预期结果有很大不同。

现在的问题是我们应该容忍多少错误?因为bug通常会导致较大的错误,所以我们可以将容差设置得相当高。然而,在浮点数中,“高”仍然是相对的。与数万亿的值相比,一百万的误差很小,但当输入值是数万亿时,它太大而无法发现错误。所以我们至少应该做一些分析来决定水平。

The function being tested will use SSE intrinsics. This means it will, for each i in the loop above, either perform a floating-point multiply and a floating-point add or will perform a fused floating-point multiply-add. The potential errors in the latter are a subset of the former, so I will use the former. The floating-point operations for a*b+c do some rounding so that they calculate a result that is approximately a•b+c (interpreted as an exact mathematical expression, not floating-point). We can write the exact value calculated as (a•b•(1+e0)+c)•(1+e1) for some errors e0 and e1 with magnitudes at most 2-24, provided all the values are in the normal range of the floating-point format. (2-24 is the maximum relative error that can occur in any correctly rounded elementary floating-point operation in round-to-nearest mode in the IEEE-754 32-bit binary floating-point format. Rounding in round-to-nearest mode changes the mathematical value by at most half the value of the least significant bit in the significand, which is 23 bits below the most significant bit.)

接下来,我们考虑测试程序为其预期值产生什么值。它使用C代码d = a*b + c;。 (我已将问题中的长名称转换为较短的名称。)理想情况下,这还会计算 IEEE-754 32 位二进制浮点中的乘法和加法。如果确实如此,那么结果将与正在测试的功能相同,并且不需要在比较中允许任何容差。然而,C 标准允许实现在执行浮点运算时具有一定的灵活性,并且存在不符合标准的实现,它们比标准允许的自由度更大。

A common behavior is for an expression to be computed with more precision than its nominal type. Some compilers may calculate a*b + c using double or long double arithmetic. The C standard requires that results be converted to the nominal type in casts or assignments; extra precision must be discarded. If the C implementation is using extra precision, then the calculation proceeds: a*b is calculated with extra precision, yielding exactly a•b, because double and long double have enough precision to exactly represent the product of any two float values. A C implementation might then round this result to float. This is unlikely, but I allow for it anyway. However, I also dismiss it because it moves the expected result to be closer to the result of the function being tested, and we just need to know the maximum error that can occur. So I will continue, with the worse (more distant) case, that the result so far is a•b. Then c is added, yielding (a•b+c)•(1+e2) for some e2 with magnitude at most 2-53 (the maximum relative error of normal numbers in the 64-bit binary format). Finally, this value is converted to float for assignment to d, yielding (a•b+c)•(1+e2)•(1+e3) for some e3 with magnitude at most 2-24.

Now we have expressions for the exact result computed by a correctly operating function, (a•b•(1+e0)+c)•(1+e1), and for the exact result computed by the test code, (a•b+c)•(1+e2)•(1+e3), and we can calculate a bound on how much they can differ. Simple algebra tells us the exact difference is a•b•(e0+e1+e0•e1-e2-e3-e2•e3)+c•(e1-e2-e3-e2•e3). This is a simple function of e0, e1, e2, and e3, and we can see its extremes occur at endpoints of the potential values for e0, e1, e2, and e3. There are some complications due to interactions between possibilities for the signs of the values, but we can simply allow some extra error for the worst case. A bound on the maximum magnitude of the difference is |a•b|•(3•2-24+2-53+2-48)+|c|•(2•2-24+2-53+2-77).

Because we have plenty of room, we can simplify that, as long as we do it in the direction of making the values larger. E.g., it might be convenient to use |a•b|•3.001•2-24+|c|•2.001•2-24. This expression should suffice to allow for rounding in floating-point calculations while detecting nearly all implementation errors.

请注意,表达式与最终值不成比例,a*b+c,由正在测试的函数或测试程序计算得出。这意味着,一般来说,使用相对于被测试函数或测试程序计算的最终值的容差进行测试是错误的。测试的正确形式应该是这样的:

double tolerance = fabs(input[i] * MSCALAR) * 0x3.001p-24 + fabs(ainput[i]) * 0x2.001p-24;
double difference = fabs(output[i] - expected[i]);
if (! (difference < tolerance))
   // Report error here.

总之,这给我们提供了一个比浮点舍入导致的任何可能差异更大的容差,因此它永远不会给我们一个误报(报告测试函数被破坏,但实际上并没有被破坏)。然而,与我们想要检测的错误引起的错误相比,它非常小,因此它很少会给我们漏报(未能报告实际错误)。

(请注意,计算容差时也存在舍入误差,但它们小于我在系数中使用 0.001 时允许的斜率,因此我们可以忽略它们。)

(另请注意! (difference < tolerance)不等于difference >= tolerance。如果该函数由于错误而产生 NaN,则任何比较都会产生 false:两者difference < tolerance and difference >= tolerance产生错误,但是! (difference < tolerance)产生 true。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何避免单元测试中的浮点舍入错误? 的相关文章

  • Tensorflow 中的自定义资源

    由于某些原因 我需要为 Tensorflow 实现自定义资源 我试图从查找表实现中获得灵感 如果我理解得好的话 我需要实现3个TF操作 创建我的资源 资源的初始化 例如 在查找表的情况下填充哈希表 执行查找 查找 查询步骤 为了促进实施 我
  • MEX 文件中的断言导致 Matlab 崩溃

    我正在使用mxAssert 宏定义为matrix h在我的 C 代码中 mex 可以完美编译 当我调用的 mex 代码中违反断言时 该断言不会导致我的程序崩溃 而是导致 Matlab 本身崩溃 我错过了什么吗 这是有意的行为吗 当我查看 M
  • 在 C++ 中分割大文件

    我正在尝试编写一个程序 该程序接受一个大文件 任何类型 并将其分成许多较小的 块 我想我已经有了基本的想法 但由于某种原因我无法创建超过 12 kb 的块大小 我知道谷歌等上有一些解决方案 但我更感兴趣的是了解这个限制的根源是什么 然后实际
  • 如何进行带有偏差的浮点舍入(始终向上或向下舍入)?

    我想以偏置舍入浮动 要么总是向下 要么总是向上 代码中有一个特定的点 我需要这个 程序的其余部分应该像往常一样四舍五入到最接近的值 例如 我想四舍五入到最接近的 1 10 倍数 最接近 7 10 的浮点数约为 0 69999998807 但
  • 如果.Net Core可以在Windows上运行,为什么不能在.Net Framework中引用.Net Core DLL?

    我明白为什么 Net Framework 可能会在 Net Core IE 中导致问题 因为不存在特定于 Windows 平台的 API 但是为什么不能直接引用 Net Core 作为 Net Framework 中的库呢 如果 Net C
  • std::map 和二叉搜索树

    我读过 std map 是使用二叉搜索树数据结构实现的 BST 是一种顺序数据结构 类似于数组中的元素 它将元素存储在 BST 节点中并按其顺序维护元素 例如如果元素小于节点 则将其存储在节点的左侧 如果元素大于节点 则将其存储在节点的右侧
  • 如何在 VS 中键入时显示方法的完整文档?

    标题非常具有描述性 是否有任何扩展可以让我看到我正在输入的方法的完整文档 我想查看文档 因为我可以在对象浏览器中看到它 其中包含参数的描述和所有内容 而不仅仅是一些 摘要 当然可以选择查看所有覆盖 它可能是智能感知的一部分 或者我不知道它并
  • VS30063:您无权访问 https://dev.azure.com

    我正在尝试在 asp net core 2 1 mvc 应用程序中使用以下代码连接 Azure DevOps Uri orgUrl new Uri https dev azure com xxxxx String personalAcces
  • 转到 C# WPF 中的第一页

    我正在 WPF 中使用导航服务 为了导航到页面 我使用 this NavigationService Navigate new MyPage 为了返回我使用 this NavigationService GoBack 但是如何在不使用的情况
  • 单元测试失败,异常代码为 c0000005

    我正在尝试使用本机单元测试项目在 Visual Studios 2012 中创建单元测试 这是我的测试 TEST METHOD CalculationsRoundTests int result Calculations Round 1 0
  • 两组点之间的最佳匹配

    I ve got two lists of points let s call them L1 P1 x1 y1 Pn xn yn and L2 P 1 x 1 y 1 P n x n y n 我的任务是找到它们点之间的最佳匹配 以最小化它
  • 从匿名类型获取值

    我有一个方法如下 public void MyMethod object obj implement 我这样称呼它 MyMethod new myparam waoww 那么我该如何实施MyMethod 获取 myparam 值 Edit
  • Silverlight Datagrid:在对列进行排序时突出显示整个列

    我的 Silverlight 应用程序中有一个 DataGrid 我想在对该列进行排序时突出显示整个列 它在概念上与上一个问题类似 Silverlight DataGrid 突出显示整列 https stackoverflow com qu
  • 32位PPC rlwinm指令

    我在理解上有点困难rlwinmPPC 汇编指令 旋转左字立即然后与掩码 我正在尝试反转函数的这一部分 rlwinm r3 r3 0 28 28 我已经知道什么了r3 is r3在本例中是一个 4 字节整数 但我不确定这条指令到底是什么rlw
  • 是否有一个 C++ 库可以从 PDF 文件中提取文本,例如 PDFBox for Java? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 去年 我使用 PDFBox 在 Java 中创建了一个应用程序来获取某些 PDF 文件中的原始文本 现在
  • gdb查找行号的内存地址

    假设我已将 gdb 附加到一个进程 并且在其内存布局中有一个文件和行号 我想要其内存地址 如何获取文件x中第n行的内存地址 这是在 Linux x86 上 gdb info line test c 56 Line 56 of test c
  • 通过 Web 界面执行 python 单元测试

    是否可以通过 Web 界面执行单元测试 如果可以 如何执行 EDIT 现在我想要结果 对于测试 我希望它们是自动化的 可能每次我对代码进行更改时 抱歉我忘了说得更清楚 EDIT 这个答案此时已经过时了 Use Jenkins https j
  • 过度使用委托对性能来说是一个坏主意吗? [复制]

    这个问题在这里已经有答案了 考虑以下代码 if IsDebuggingEnabled instance Log GetDetailedDebugInfo GetDetailedDebugInfo 可能是一个昂贵的方法 因此我们只想在调试模式
  • 在基类集合上调用派生方法

    我有一个名为 A 的抽象类 以及实现 A 的其他类 B C D E 我的派生类持有不同类型的值 我还有一个 A 对象的列表 abstract class A class B class A public int val get privat
  • 如何创建向后兼容 Windows 7 的缩放和尺寸更改每显示器 DPI 感知应用程序?

    我是 WPF 和 DPI 感知 API 的新手 正在编写一个在 Windows 7 8 1 和 10 中运行的应用程序 我使用具有不同每个显示器 DPI 设置的多个显示器 并且有兴趣将我的应用程序制作为跨桌面配置尽可能兼容 我已经知道可以将

随机推荐

  • AutoMapper 地图子属性也定义了地图

    我有以下域对象 public class DomainClass public int Id get set public string A get set public string B get set 我有以下两个要映射到的对象 pub
  • send() 函数返回的字节数多于 C++ 所需的字节数

    我正在做一个套接字程序 在我的服务器与设备连接后 我试图向他发送一条消息 但 send 函数返回的字节数大于数组中存储的字节数 并且消息没有被发送 这是我的代码 StartSendingMessages int retorno CStrin
  • 是否有任何 jQuery 版本符合 Promise/A 规范?

    在阅读了几篇文章之后 我开始知道 jQuery 中存在 Promise 实现 但我不确定 jQuery 的任何版本是否兼容 Promise A 2015 更新 jQuery 3 0 与 Promises A 兼容 看这个问题在 GitHub
  • SocketCluster 中间件握手与承诺

    我正在构建一个同时服务 http 和 ws 的应用程序 用户首先通过 HTTP 登录 Laravel 服务器 这会返回一个 JWT 用于允许通过 WS 登录 Ihv 添加了一个 MIDDLEWARE HANDSHAKE 来获取令牌并向 La
  • Theano 中的 numpy.matmul

    TL DR我想复制的功能numpy matmul in theano 最好的方法是什么 过短 不明白看着theano tensor dot and theano tensor tensordot 我没有看到一种简单的方法来进行简单的批量矩阵
  • iframe src 在按钮单击时更改

    我想在单击按钮后更改 iframe 的 src 我找不到解决办法
  • 在 Windows 7 x86 上安装 Thin 时出现问题

    我在获取时遇到问题thin http code macournoyer com thin 在我的 Windows 7 机器上工作 我已经安装了 eventmachine v0 8 1 gt gem install Thin 忽略依赖项检查
  • 如何将流数据写入Kafka?

    我正在尝试对主题数据进行一些丰富 因此 使用 Spark 结构化流从 Kafka 接收器读回 Kafka val ds spark readStream format kafka option kafka bootstrap servers
  • 如何使用 Common Lisp 获得列表的所有可能排列?

    我正在尝试编写一个 Common Lisp 函数 该函数将给出列表的所有可能排列 每个元素仅使用一次 例如 列表 1 2 3 将给出输出 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 我已经写过一些有用的东西 但它
  • 如何使用 JNA 运行 chrome?

    我写了一些java代码 如何在 Windows 32 位 中使用 JNA 运行 chrome 然后我喜欢了解它的含义 如您所知 FindWindow 是一个简单的解决方案 但如果 chrome 不运行 它就不起作用 查找窗口示例 http
  • 通过一个报告用户运行所有 SSRS 报告,忽略自己的用户域

    我有以下代码 它从 SSRS 服务器返回报告 然后将路径存储到每个单独的列表 允许用户从应用程序内运行它们 下面的工作正常 NetworkCredential serviceCredentials new NetworkCredential
  • Destroy_with_password 始终返回 false

    以现有问题为基础演练如何需要密码才能删除用户帐户 https stackoverflow com questions 39373655 ruby on rails devise require password to delete acco
  • 如何在隐藏“display: none;”时渲染传单地图家长

    在我的页面上显示传单地图时 我遇到奇怪的行为 通常情况下 地图会按预期渲染并且运行良好 但是 我只想在我在 javascript 中检测到的表单中发生错误时才显示地图 所以如果我设置父级 div to display none 并根据需要稍
  • 无法将数据移出互斥体

    考虑下面的代码示例 我有一个向量JoinHandlers我需要它迭代以连接回主线程 但是 这样做后我收到错误error cannot move out of borrowed content let threads Arc new Mute
  • 使用 Internet Explorer 8 进行提示()

    我很难找到解决我的问题的方法 这是一个代码片段 var ans prompt Mot de passe if ans ans null doPostBack Page ans else window location Erreurs Not
  • 如何在 npm 中升级全局包的依赖项

    我已经全局安装了pouchdb server我收到了这条消息graceful fs npm install g pouchdb server npm WARN deprecated email protected cdn cgi l ema
  • 修改 NumPy 数组的特定行/列

    如何修改 NumPy 数组的特定行或列 例如 我有一个 NumPy 数组 如下所示 P array 1 2 3 4 5 6 如何更改第一行的元素 1 2 3 to 7 8 9 所以这样P会变成 P array 7 8 9 4 5 6 同样
  • Java SimpleDateFormat 解析时区,如 America/Los_Angeles

    我想用Java解析以下字符串并将其转换为日期 DTSTART TZID America Los Angeles 20140423T120000 我试过这个 SimpleDateFormat sdf new SimpleDateFormat
  • 用户登录后调用方法

    我想知道用户登录后是否可以调用函数 这是我要调用的代码 point this gt container gt get process points point gt ProcessPoints 1 this gt container 您可以
  • 如何避免单元测试中的浮点舍入错误?

    我正在尝试为一些对单精度浮点数数组进行操作的简单向量数学函数编写单元测试 这些函数使用 SSE 内在函数 并且在 32 位系统上运行测试时出现误报 至少我认为 测试在 64 位上通过 当操作遍历数组时 我积累了越来越多的舍入误差 这是单元测