将 uint64_t 转换为双倍后出现意外结果

Unexpected result after converting uint64_t to double

提问人:Luc Bloom 提问时间:11/16/2017 最后编辑:Luc Bloom 更新时间:11/16/2017 访问量:3022

问:

在以下代码中:

#include <iostream>

...

uint64_t t1 = 1510763846;
uint64_t t2 = 1510763847;
double d1 = (double)t1;
double d2 = (double)t2;
// d1 == t2 => evaluates to true somehow?
// t1 == d2 => evaluates to true somehow?
// d1 == d2 => evaluates to true somehow?
// t1 == t2 => evaluates to false, of course.
std::cout << std::fixed << 
        "uint64_t: " << t1 << ", " << t2 << ", " <<
        "double: " << d1 << ", " << d2 << ", " << (d2+1) << std::endl;

我得到这个输出:

uint64_t: 1510763846, 1510763847, double: 1510763904.000000, 1510763904.000000, 1510763905.000000

我不明白为什么。这个答案:可以存储在双精度中的最大整数表示可以在不损失精度的情况下存储在 a 中最多 2^53 (9007199254740992) 的整数。double

实际上,当我开始使用双精度进行计算时,我会遇到错误,所以这不仅仅是打印问题。(例如,1510763846 和 1510763847 都给出1510763904)

同样很奇怪的是,双精度可以加到然后正确 (d2+1 == 1510763905.000000)

基本原理:我将这些数字转换为双精度,因为我需要在 Lua 中使用它们,它只支持浮点数。我确定我正在编译 Lua 库作为类型,而不是 .doublelua_Numberfloat

std::cout << sizeof(t1) << ", " << sizeof(d2) << std::endl;

输出

8, 8

我正在将 VS 2012 与目标 MachineX86、工具包v110_xp一起使用。浮点模型“Precise (/fp:precise)”

补遗

在回复的人的帮助下,以及这篇文章为什么在特定的 Visual Studio 2008 项目中错误地添加了双精度值?,我已经能够查明问题所在。库正在使用 _set_controlfp、_control87、_controlfp 或 __control87_2 等函数将可执行文件的精度更改为“single”。这就是为什么uint64_t转换为双精度时的行为就像浮点数一样。

在对上述函数名称和用于精确控制的“MCW_PC”进行文件搜索时,我发现以下库可能已经设置了它:

  • Android NDK
  • boost::数学
  • boost::数值
  • DirectX(我们使用的是 2010 年 6 月)
  • FMod(非 EX)
  • Pyro 粒子引擎

现在我想重新表述我的问题:

如何确保每次都能正确地从 a 转换为 a,而无需:uint64_tdouble

  1. 每次可能的转换发生时都必须调用 _fpreset()(考虑函数参数)
  2. 不得不担心库的线程会改变我的 _fpreset() 和转换之间的浮点精度吗?

朴素代码是这样的:

double toDouble(uint64_t i)
{
    double d;
    do {
        _fpreset();
        d = i;
        _fpreset();
    } while (d != i);
    return d;
}

double toDouble(int64_t i)
{
    double d;
    do {
        _fpreset();
        d = i;
        _fpreset();
    } while (d != i);
    return d;
}

此解决方案假设线程两次扰乱浮点精度的几率是天文数字。问题是,我正在使用的值是代表现实世界价值的计时器。所以我不应该冒险。这个问题有灵丹妙药吗?

C++ 精度 UInt64

评论

1赞 pm100 11/16/2017
您在 YR COUT 中有 D2 两次。为什么当你说“HIT!”时 t1 == t2 肯定是错误的
5赞 Sergey Kalinichenko 11/16/2017
在修复代码中的拼写错误(演示)后,这在 ideone 上正常工作。
1赞 Daniel H 11/16/2017
你为什么平台编译?您使用的是什么编译器?哪些编译器标志?
1赞 Aki Suihkonen 11/16/2017
我的意思是检查 sizeof float == sizeof double。double 必须至少是浮点的大小,但不一定更大。
3赞 Keith Thompson 11/16/2017
您已经添加了(您需要的)和(您不使用的)指令。您还需要 和 的标头。我建议您更新您的问题以显示一个完整的独立程序,我们可以复制和粘贴并在我们自己的系统上不加修改地运行。最小可重复示例#include<iostream><time.h>#include<cassert><cstdint>

答:

0赞 Aki Suihkonen 11/16/2017 #1

从 ieee754 浮点转换来看,您的 double 实现实际上是浮点数,这当然是标准允许的,它要求 .sizeof double >= sizeof float

1510763846 最准确的表示是 1.510763904E9。

评论

0赞 Luc Bloom 11/16/2017
我以为你有答案,但似乎大小都是 8。难道根据 ieee754 的说法,即使双精度是 64 位,它也不能容纳整数 1510763846?
1赞 anatolyg 11/16/2017
这可能仍然是答案;只有转换到在其他地方(不知道确切的位置)。我记得在某些 ARM 平台上,C 运行时中有专用函数用于从 64 位整数转换为浮点数。这些功能可能会被破坏。float
2赞 Keith Thompson 11/16/2017
C++ 标准确实允许并具有相同的大小、表示、范围和精度(尽管它们始终是不同的类型),但它也对类型的范围和精度提出了最低要求。我相当确定 32 位类型无法满足这些要求。doublefloatdouble
1赞 Aki Suihkonen 11/16/2017
这是我发现的允许的浮子和双倍尺寸的最佳参考:stackoverflow.com/a/24157568/1716339——但我不知道发生了什么,除了有强烈的浮子气味。我们应该看一下汇编程序转储。