为什么我得到 std::exp 的平台特定结果?
Why do I get platform-specific result for std::exp?
我有一个程序在 Android 和 Windows 下给出了截然不同的结果。当我根据包含预期结果的二进制文件验证输出数据时,即使差异很小(舍入问题)也很烦人,我必须找到一种方法来修复它。
这是一个示例程序:
#include <iostream>
#include <iomanip>
#include <bitset>
int main( int argc, char* argv[] )
{
// this value was identified as producing different result when used as parameter to std::exp function
unsigned char val[] = {158, 141, 250, 206, 70, 125, 31, 192};
double var = *((double*)val);
std::cout << std::setprecision(30);
std::cout << "var is " << var << std::endl;
double exp_var = std::exp(var);
std::cout << "std::exp(var) is " << exp_var << std::endl;
}
在 Windows 下,用 Visual 2015 编译,我得到输出:
var is -7.87234042553191493141184764681
std::exp(var) is 0.00038114128472300899284561093161
在Android/armv7下,用g++ NDK r11b编译,我得到输出:
var is -7.87234042553191493141184764681
std::exp(var) is 0.000381141284723008938635502307335
所以从 e-20 开始结果是不同的:
PC: 0.00038114128472300899284561093161
Android: 0.000381141284723008938635502307335
请注意,我的程序做了很多数学运算,我只注意到 std::exp
对相同的输入产生了不同的结果...而且只对某些特定的输入值产生了不同的结果(没有调查这些值是否具有一个相似的 属性),对于其中的大多数,结果是相同的。
- 这种行为是不是"expected",在某些情况下是否不能保证得到相同的结果?
- 是否有一些编译器标志可以解决这个问题?
- 或者我是否需要四舍五入我的结果以在两个平台上以相同的方式结束?那么四舍五入的好策略是什么?因为如果输入
var
非常小,在 e-20 处四舍五入会丢失太多信息?
编辑:我认为我的问题不是 Is floating point math broken? 的重复问题。我在两个平台上得到的结果完全相同,只有 std::exp
对于某些特定值会产生不同的结果。
标准没有定义 exp
函数(或任何其他数学库函数 1)应该如何实现,因此每个库实现可能使用不同的计算方法。
例如,Android C 库 (bionic) 通过区间 [0,0.34658] 上的特殊有理函数使用 exp(r) 的近似值并缩减结果。
可能 Microsoft 库使用了不同的计算方法(无法找到相关信息),从而导致不同的结果。
此外,库可以采用动态加载策略(即加载包含实际实现的 .dll
)以利用不同的硬件特定功能,即使使用相同的编译器。
为了在两个(所有)平台上获得相同的实现,您可以使用自己的 exp
函数实现,从而不依赖于不同库的不同实现。
考虑到处理器可能采用不同的舍入方法,这也会产生不同的结果。
我有一个程序在 Android 和 Windows 下给出了截然不同的结果。当我根据包含预期结果的二进制文件验证输出数据时,即使差异很小(舍入问题)也很烦人,我必须找到一种方法来修复它。
这是一个示例程序:
#include <iostream>
#include <iomanip>
#include <bitset>
int main( int argc, char* argv[] )
{
// this value was identified as producing different result when used as parameter to std::exp function
unsigned char val[] = {158, 141, 250, 206, 70, 125, 31, 192};
double var = *((double*)val);
std::cout << std::setprecision(30);
std::cout << "var is " << var << std::endl;
double exp_var = std::exp(var);
std::cout << "std::exp(var) is " << exp_var << std::endl;
}
在 Windows 下,用 Visual 2015 编译,我得到输出:
var is -7.87234042553191493141184764681
std::exp(var) is 0.00038114128472300899284561093161
在Android/armv7下,用g++ NDK r11b编译,我得到输出:
var is -7.87234042553191493141184764681
std::exp(var) is 0.000381141284723008938635502307335
所以从 e-20 开始结果是不同的:
PC: 0.00038114128472300899284561093161
Android: 0.000381141284723008938635502307335
请注意,我的程序做了很多数学运算,我只注意到 std::exp
对相同的输入产生了不同的结果...而且只对某些特定的输入值产生了不同的结果(没有调查这些值是否具有一个相似的 属性),对于其中的大多数,结果是相同的。
- 这种行为是不是"expected",在某些情况下是否不能保证得到相同的结果?
- 是否有一些编译器标志可以解决这个问题?
- 或者我是否需要四舍五入我的结果以在两个平台上以相同的方式结束?那么四舍五入的好策略是什么?因为如果输入
var
非常小,在 e-20 处四舍五入会丢失太多信息?
编辑:我认为我的问题不是 Is floating point math broken? 的重复问题。我在两个平台上得到的结果完全相同,只有 std::exp
对于某些特定值会产生不同的结果。
标准没有定义 exp
函数(或任何其他数学库函数 1)应该如何实现,因此每个库实现可能使用不同的计算方法。
例如,Android C 库 (bionic) 通过区间 [0,0.34658] 上的特殊有理函数使用 exp(r) 的近似值并缩减结果。
可能 Microsoft 库使用了不同的计算方法(无法找到相关信息),从而导致不同的结果。
此外,库可以采用动态加载策略(即加载包含实际实现的 .dll
)以利用不同的硬件特定功能,即使使用相同的编译器。
为了在两个(所有)平台上获得相同的实现,您可以使用自己的 exp
函数实现,从而不依赖于不同库的不同实现。
考虑到处理器可能采用不同的舍入方法,这也会产生不同的结果。