【问题标题】:Decompose a floating-point number into its integral and fractional part将浮点数分解为整数部分和小数部分
【发布时间】:2020-05-18 13:57:02
【问题描述】:

我正在实施分数延迟线算法。 涉及的任务之一是将浮点值分解为其整数部分和小数部分。 我知道在 SO 上有很多关于这个主题的帖子,我可能读过其中的大部分。 但是,我还没有找到一篇涉及此场景细节的帖子。

  • 算法必须使用 64 位浮点值。

  • 保证输入浮点值始终为正。 (延迟时间不能为负数)

  • 输出整数部分必须用整数数据类型表示。

  • 整数数据类型必须有足够多的位,以便双精度到整数的转换没有溢出的风险。

  • 必须避免因浮点值缺乏精确的内部表示而导致的问题。 (即 9223372036854775809.0 可能在内部表示为 9223372036854775808.9999998,当转换为整数时,它错误地变为 9223372036854775808)

  • 无论舍入模式或编译器优化设置如何,该实现都应该工作。

于是我写了一个函数:

 double my_modf(double x, int64_t *intPartOut);

如您所见,它的签名类似于 C 标准库中的 modf() 函数。

我想出的第一个实现是:

double my_modf(double x, int64_t *intPartOut)
{
    double y;
    double fracPart = modf(x, &y);
    *intPartOut = (int64_t)y;
    return fracPart;
}

我也一直在试验这种实现——至少在我的机器上——运行速度比以前快,但我怀疑它的稳健性。

double my_modf(double x, int64_t *intPartOut)
{
    int64_t y = (int64_t)x;
    *intPartOut = y;
    return x - y;
}

...这是我最近的尝试:

double my_modf(double x, int64_t *intPartOut)
{
    *intPartOut = llround(x);
    return x - floor(x);
}

我无法决定哪种实现最适合使用,或者是否还有其他我认为可以更好地实现以下目标的实现。 我正在寻找 (1) 最强大和 (2) 最有效的实现,将浮点数分解为其整数和小数部分,同时考虑上述点列表。

【问题讨论】:

  • Re “11.0 可能在内部表示为 10.99999999999999”:不,11 在内部表示为 11。如果使用实数算术执行,您可能会得到一些计算的结果,产生了 11,但是,通过浮点算法计算,产生了一个不同于 11 的数字,例如 10.9999999999999982236431605997495353221893310546875。但是当这个数字被传递给my_modf 时,它是 10.9999999999999982236431605997495353221893310546875 并且没有迹象表明在另一个宇宙中它会是 11……
  • ... 这意味着,除非您有一些关于号码的旁道信息,否则my_modf 不可能在 10.99999999999999982236431605997495353221893310546875 时产生 11,而在它产生 10 时“应该是“小于 11 的数字。例如,如果您知道传递给 my_modf 的所有数字将是,如果它们是用实数算术计算的,是 1/60 的倍数,那么 my_modf 可以四舍五入它的所有输入都可以解释这一点。
  • 将整数正确转换为浮点格式永远不会产生非整数。将“9223372036854775809”转换为浮点格式,或者甚至执行任何计算以产生接近 9223372036854775809 的 IEEE-754 binary64 格式的数字,都不会产生非整数。 IEEE-754 binary64 中可表示的两个最接近的值是 9223372036854775808 和 9223372036854775810。但是,如果将其中任何一个传递给 my_modf,它就无法知道需要 9223372036854775809。
  • @EricPostpischil:我的最大幅度界限是 2^63 - 1:有符号 64 位整数的正限制。
  • "9223372036854775809.0 可能在内部表示为 9223372036854775808.9999998" 是完全错误的。不是,也不可能。

标签: c casting floating-point integer precision


【解决方案1】:

假设浮点输入 x 的整数部分的最大值为 263-1 且 x 为非负数,那么两者:

double my_modf(double x, int64_t *intPartOut)
{
    double y;
    double fracPart = modf(x, &y);
    *intPartOut = y;
    return fracPart;
}

和:

double my_modf(double x, int64_t *intPartOut)
{
    int64_t y = x;
    *intPartOut = y;
    return x - y;
}

无论舍入模式如何,都会正确返回intPartOut 中的整数部分和返回值中的小数部分。

GCC 9.2 for x86_64 does a better job optimizing the latter version,Apple Clang 11.0.0 也是如此。

llround 不会按需要返回整数部分,因为它会舍入到最接近的整数而不是截断。

关于x 包含错误的问题无法通过问题中提供的信息解决。上面显示的例程没有错误;它们准确地返回输入的整数和小数部分。

【讨论】:

    【解决方案2】:

    更新答案阅读您的下方评论后。

    如果您已经确定值在 [0, 2^63-1] 范围内,那么简单的强制转换将比 llround() 更快,因为此函数还可以检查溢出(在我的系统上,手册页指出,但 C 标准不需要它)。

    在我的机器上,例如 (x86-64 Nehalem),强制转换是一条指令 (cvttsd2si),而 llround() 显然不止一条。

    我能保证通过简单的强制转换(截断)得到正确的结果还是舍入更安全?

    取决于您对“正确”的含义。如果double 中的值可以用int64_t 正确表示,那么您肯定会得到完全相同的值。但是,如果值不能由 double 精确表示,则在强制转换时会自动执行截断。如果您想以不同的方式对值进行四舍五入,那就另当别论了,您必须使用ceil()floor()round() 之一。

    如果您还确定没有值是 +/- Infinity 或 NaN(在这种情况下您可以使用 -Ofast),那么如果您想要截断,您的第二个实现应该是最快的,而第三个应该是如果你想floor()这个值,最快的。

    【讨论】:

    • 我的最大震级界限是 2^63 - 1。在您的回答中,您提到了 modf() 和 llround()。 modf() 显然用于将值分解为整数和小数部分,两者都以浮点格式表示。我还应该使用 llround() 将浮点整数部分转换为整数数据类型,还是可以简单地使用强制转换?我能保证通过简单的强制转换(截断)得到正确的结果还是舍入更安全?
    • @LuigiCastelli 在这种情况下,您可以毫无问题地使用这两个功能。 llround() 有助于识别域错误(即,如果要转换的 double 太大,它不会导致未定义的行为,它会适当地设置 errno),但如果您已经确定数据是在 [0, 2^63-1] 内,那么简单的演员阵容会更快。
    • "llround() 因为这个函数也检查溢出。" --> C 没有指定该功能。该函数可能会检查溢出 - 可能不会。
    • @chux-ReinstateMonica 好吧,我在手册页上读到:If x is a NaN or an infinity, or the rounded value is too large to be stored in a long (long long in the case of the ll* functions), then a domain error occurs, and the return value is unspecified. The following errors can occur: Domain error: x is a NaN or infinite, or the rounded value is too large. An invalid floating-point exception (FE_INVALID) is raised.
    • C 规范说“如果舍入值超出返回类型的范围,则数值结果未指定,可能会出现域错误或范围错误。” C17/18dr § 7.12.9.7 2 当“手册页”和 C 规范不同时,我使用 C 规范。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 2012-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多