C++中的另一个双重类型技巧
Another double type trick in C++?
首先,我知道C++中的double类型已经讨论了很多时间,但搜索后我无法回答我的问题。任何帮助或想法都将不胜感激。
我的问题的简化版本是:当我用三种不同的方法和相同的值b
、c
和d
计算a=b-c+d
时,我得到了三个不同的结果(a=-0.926909
、a=-0.926947
和a=-0.926862
),我不知道该信任哪一个。
我的问题的详细版本是:
我最近正在编写一个程序(在Ubuntu 10.10上使用C++)来处理一些数据。其中一个功能如下:
void calc() {
double a, b;
...
a = b - c + d; // c, d are global variables of double
...
}
当我使用GDB调试上述代码时,在调用calc()的过程中,我在语句a = b - c + d
之前记录了b
、c
和d
的值,如下所示:
b = 54.7231
c = 55.4051
d = -0.244947
在执行语句a = b - c + d
之后,我发现a=-0.926909
而不是计算器计算的-0.926947
。到目前为止,这还不太令人困惑,因为我想这可能只是一个精度问题。后来由于某种原因,我重新实现了calc()
的另一个版本。让我们把这个新版本称为calc_new()
。calc_new()
与calc()
几乎相同,不同之处在于b
、c
和d
的计算方式和位置:
void calc_new() {
double a, b;
...
a = b - c + d; // c, d are global variables of double
...
}
这次调试时,语句a = b - c + d
之前的b
、c
和d
的值与调试calc()
时的值相同:b = 54.7231
、c = 55.4051
、d = -0.244947
。然而,这一次在执行语句a = b - c + d
之后,我得到了a=-0.926862
。也就是说,当我用相同的值b
、c
和d
计算a = b - c + d
时,我得到了三个不同的a
。我认为a=-0.926862
、a=-0.926909
和a=-0.926947
之间的差异不小,但我无法找出原因。哪一个是正确的?
非常感谢,Tom
如果您希望答案在小数点后第5位和第6位是准确的,那么您需要确切地知道这些位置的计算输入是什么。您看到的输入只有4位小数,您还需要显示它们的第5位和第6位。然后我想你会看到一个可以理解的情况,将你的计算器匹配到小数点后6位。Double对这项工作有足够的精度,如果你取两个非常相似的数字的差(你不是),这里只会有精度问题。
编辑:不出所料,增加显示精度也会向您显示calc()和calc_new()为计算提供了不同的输入。感谢Mike Seymour和Dietmar Kuhl在评论中第一个看到你的实际问题。
让我试着回答我怀疑你想问的问题。如果我误解了你的意图,那么你可以无视答案。
假设我的数字u=500.1和v=5.001,各精确到小数点后四位。什么是w=u+v?答案是,w=505.101,,但小数点后四位为w=505.1。
现在考虑x=w-u=5.000,,它应该等于v, 然而,如果我只改变运算的顺序,我可以使x精确地等于v,而不是通过x=w-u或通过x=(u+v)-u,而是通过1x=v+(u-u) 这微不足道吗?是的,在我的例子中,它是;但同样的原理也适用于您的示例,只是它们不是真正的小数点,而是精度。 一般来说,为了保持精度,如果有一些浮点数要求和,应该先把小的加在一起,然后再把大的加在和中。
我们在这里讨论的是烟雾。如果环境中没有任何变化,则表达式如下:
a = b + c + d
如果输入未更改,则必须始终返回相同的值。
无舍入误差。没有深奥的实用主义者,什么都没有。
如果你今天和明天检查你的银行账户(当时没有任何变化),我怀疑如果你看到不同的东西,你会疯掉的。我们谈论的是程序,而不是随机数生成器!!!
正确的数值是-0.926947。
你看到的差异对于舍入误差来说太大了(即使是单精度),因为你可以在这个编码器中检查。使用编码器时,您需要这样输入它们:-55.926909(以说明先前提交的答案中很好地描述的运算符交换性效应的潜在影响。)此外,仅最后一个有效位的差异很可能是由于舍入效应,但您的值不会有任何差异。
使用该工具时,64位格式(Binary64)对应于实现的双重类型。
有理数在给定的基数中并不总是有终止展开。1/3不能用以十为基数的有限位数表示。在基数2中,分母为2的幂的有理数将具有终止展开。其余的不会。因此,1/2、1/4、3/8、7/16……任何看起来像x/(2^n)的数字都可以精确地表示。这是有理数的无穷级数的一个相当稀疏的子集。其他所有东西都会受到试图在有限容器中表示无限多个二进制数字所带来的错误的影响。
但是加法是可交换的,对吧?对但当你开始引入舍入误差时,情况会发生一些变化。以a=b+c+d为例,假设d不能用有限数量的二进制数字表示。c也不能。所以把它们加在一起会给我们一些不准确的值,而这些值本身也可能无法用有限数量的二进制数字表示。所以错误在错误之上。然后我们把这个值加到b上,这可能也不是二进制的终止展开。因此,取一个不准确的结果,再加上另一个不精确的数字,就会得到另一个错误的数字。因为我们在每一步都放弃了精度,我们可能在每一步骤都打破了交换性的对称性。
我写了一篇帖子:(与Perl相关,但这是一个普遍的主题)Re:令人震惊的不精确(PerlMonks),当然还有经典的《每个计算机科学家都应该知道的关于浮点数学的知识》,这两篇文章都讨论了这个主题。后者要详细得多。
- 检查一个类型是否直接派生自"enable if"上下文中的另一个类型(是其子类型)
- C++将一个指针分配给另一个指针时执行的类型检查
- 如何包装一个函数以适应另一个函数的所需类型
- 在 C++ 中将一个模板类型的对象类型转换为另一个模板类型
- 我正在尝试将表的地址传递给要在另一个函数中使用的指针,但得到不兼容的指针类型
- 如何构造一个类型特征,可以判断一个类型的私有方法是否可以在另一个类型的构造函数中调用?
- 缺少类型说明符和另一个问题
- 如果一个变量在它之前释放了另一个(相同的数据类型)变量,如何将其分配给内存?
- 如何在类 A 中传递类型名 T 以在另一个类 B 中使用
- 将积分类型的数组作为另一个不相关的积分类型的阵列进行访问的安全且符合标准的方法
- c ++ 我是否需要手动删除指向另一个具体类型的 void* 指针?
- 如何在另一个类中调用类型类的向量?
- 使用 SFINAE 测试是否可以将一个指针类型static_cast到另一个指针类型
- 检查数值类型是否是另一个数值类型的子集
- 无法从派生类型的作用域访问另一个实例的受保护成员
- 另一个:从"常量类型*"到"类型*"的转换无效
- 为什么直接传递"this"指针来存档是一个错误,而另一个相同类型的指针是可以的?
- C++:在另一个类中作为类型类
- 将 std::variant 转换为另一个具有类型子集的 std::variant
- 类的成员是否可以与其类型(另一个类)命名相同的名称