【发布时间】: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