当在 C/C++ 中将 64 位 int 转换为 64 位 float 并且没有完全匹配时,它是否总是落在非小数上?
When a 64bit int is cast to 64bit float in C/C++ and doesn't have an exact match, will it always land on a non-fractional number?
当 int64_t 被强制转换为 double 并且没有完全匹配时,据我所知,我得到了一种等效于 double 的 best-effort-nearest-value。例如,int64_t 中的 9223372036854775000 在 double:
中显示为 9223372036854774784.0
#include <stdio.h>
int main(int argc, const char **argv) {
printf("Corresponding double: %f\n", (double)9223372036854775000LL);
// Outputs: 9223372036854774784.000000
return 0;
}
在我看来,似乎将 int64_t 强制转换为双精度数总是以干净的非小数形式结束,即使在双精度精度非常低的较高数字范围内也是如此。但是,我只是从随机尝试中观察到这一点。对于将 int64_t 的任何值强制转换为 double,是否一定会发生这种情况?
如果我将这个非小数双精度转换回 int64_t,我是否总是会得到准确对应的 64 位 int 并切掉 .0? (假设它在转换回来的过程中没有溢出。)喜欢这里:
#include <inttypes.h>
#include <stdio.h>
int main(int argc, const char **argv) {
printf("Corresponding double: %f\n", (double)9223372036854775000LL);
// Outputs: 9223372036854774784.000000
printf("Corresponding int to corresponding double: %" PRId64 "\n",
(int64_t)((double)9223372036854775000LL));
// Outputs: 9223372036854774784
return 0;
}
或者它可能不精确,在某些极端情况下让我得到“错误”的整数?
从直觉上和我的测试来看,这两点的答案似乎都是“是”,但如果对浮点标准及其背后的数学有很好正式理解的人可以证实这一点,那将对我很有帮助.我也很好奇是否已知任何已知的更积极的优化,如 gcc 的 -Ofast
会破坏任何这些。
一般情况下是的,两者都应该是正确的。浮点数基数必须是 - 如果不是 2,则至少 integer 并且鉴于此,转换为 nearest 浮点值的整数可以 从不 产生非零分数 - 精度足够或 base 中的最低阶 integer 数字浮动类型的将被归零。例如,在您的情况下,您的系统使用 ISO/IEC/IEEE 60559 二进制浮点数。在基数2中检查时,可以看到该值的尾随数字确实被归零:
>>> bin(9223372036854775000)
'0b111111111111111111111111111111111111111111111111111110011011000'
>>> bin(9223372036854774784)
'0b111111111111111111111111111111111111111111111111111110000000000'
将不带小数的double转换为整数类型,假设double的值落在整数类型的范围内应该准确...
尽管您仍然可能会遇到实施质量问题,或完全错误 - 例如 无符号 32 位值与 MSB 集(或只是双精度值)的往返转换在 2³¹ 和 2³²-1 之间转换为 unsigned int) 将在转换中“溢出”并始终准确地产生 2³¹.
以下假定转换的值为正数。负数的行为是类似的。
C 2018 6.3.1.4 2 指定从整数到实数的转换并说:
… If the value being converted is in the range of values that can be represented but cannot be represented exactly, the result is either the nearest higher or nearest lower representable value, chosen in an implementation-defined manner.
这告诉我们一些整数值 x 被转换为浮点数只有当两个可表示值之一边界 x 时才能产生非整数 不是整数,x 不可表示。
5.2.4.2.2 指定用于浮点数的模型。每个有限浮点数都由按b[=25=缩放的特定基数b中的数字序列表示]e 对于某些指数 e。 (b 是一个大于 1 的整数。)然后,如果两个值之一限定 x,则说 p 不是整数,则缩放必须使该浮点数中的最低数字代表分数。但如果是这种情况,则将 p 中表示分数的所有数字设置为 0 必须产生一个新的整数浮点数。如果 x < p,则此整数必须为 x,因此 x 可以浮点格式表示。另一方面,如果 p < x,我们可以向代表分数的每个数字添加足够的数以使其为 0(并产生进位到下一个更高的数字)。这也将产生一个可表示为浮点类型 1 的整数,并且它必须是 x.
因此,如果将整数 x 转换为浮点类型会产生非整数,则 x 必须是可表示的在类型中。但随后转换为浮点类型必须产生 x。所以永远不可能产生非整数。
脚注
1 这可能会执行所有数字,就像将其应用于三位十进制数 9.99 时一样,结果为 10.00。在这种情况下,生成的值是 b 的下一次幂,如果它在浮点格式的范围内。如果不是,则 C 标准未定义行为。另请注意,C 标准对浮点格式必须支持的范围设定了最低要求,这排除了任何格式无法表示 1 的情况,这避免了转换可能产生类似 .999 的数字的退化情况,因为它是最大可表示的有限值。
When a 64bit int
is cast to 64bit float ... and doesn't have an exact match, will it always land on a non-fractional number?
Is this guaranteed to happen for any value of int64_t
cast to a double
?
对于common double
:是的,它总是落在非小数上
当没有匹配项时,结果是上方或下方最接近的浮点可表示值,具体取决于舍入模式。鉴于commondouble
的特点,这2个边界值也是整数。当该值不可表示时,首先是附近的整数1。
... if I cast this non-fractional double
back to int64_t
, will I always get the exact corresponding 64bit int
with the .0 chopped off?
没有。 INT64_MAX
附近的边缘案例失败,因为转换后的值可能变成高于 INT64_MAX
的 FP 值。然后转换回整数类型会导致:“新类型已签名且无法在其中表示值;结果是实现定义的或引发了实现定义的信号。” C17dr § 6.3.1.3 3
#include <limits.h>
#include <string.h>
int main() {
long long imaxm1 = LLONG_MAX - 1;
double max = (double) imaxm1;
printf("%lld\n%f\n", imaxm1, max);
long long imax = (long long) max;
printf("%lld\n", imax);
}
9223372036854775806
9223372036854775808.000000
9223372036854775807 // Value here is implementation defined.
更深层次的异常
(Question variation) When an N bit integer type is cast to a floating point and doesn't have an exact match, will it always land on a non-fractional number?
整数类型范围超过有限浮点数
转换为无穷大:用普通的float
和uint128_t
,UINT128_MAX
转换为无穷大。这对于超宽整数类型来说很容易实现。
int main() {
unsigned __int128 imaxm1 = 0xFFFFFFFFFFFFFFFF;
imaxm1 <<= 64;
imaxm1 |= 0xFFFFFFFFFFFFFFFF;
double fmax = (float) imaxm1;
double max = (double) imaxm1;
printf("%llde27\n%f\n%f\n", (long long) (imaxm1/1000000000/1000000000/1000000000),
fmax, max);
}
340282366920e27
inf
340282366920938463463374607431768211456.000000
浮点进动深度超过范围
在某些 unicorn 实现中,FP 精度非常宽且范围很小,理论上最大的有限可能是一个非整数,但实际上并非如此。然后使用更宽的整数类型,转换可能会导致这个非整数值。我不认为这是 OP 的合法关注点。
当 int64_t 被强制转换为 double 并且没有完全匹配时,据我所知,我得到了一种等效于 double 的 best-effort-nearest-value。例如,int64_t 中的 9223372036854775000 在 double:
中显示为 9223372036854774784.0#include <stdio.h>
int main(int argc, const char **argv) {
printf("Corresponding double: %f\n", (double)9223372036854775000LL);
// Outputs: 9223372036854774784.000000
return 0;
}
在我看来,似乎将 int64_t 强制转换为双精度数总是以干净的非小数形式结束,即使在双精度精度非常低的较高数字范围内也是如此。但是,我只是从随机尝试中观察到这一点。对于将 int64_t 的任何值强制转换为 double,是否一定会发生这种情况?
如果我将这个非小数双精度转换回 int64_t,我是否总是会得到准确对应的 64 位 int 并切掉 .0? (假设它在转换回来的过程中没有溢出。)喜欢这里:
#include <inttypes.h>
#include <stdio.h>
int main(int argc, const char **argv) {
printf("Corresponding double: %f\n", (double)9223372036854775000LL);
// Outputs: 9223372036854774784.000000
printf("Corresponding int to corresponding double: %" PRId64 "\n",
(int64_t)((double)9223372036854775000LL));
// Outputs: 9223372036854774784
return 0;
}
或者它可能不精确,在某些极端情况下让我得到“错误”的整数?
从直觉上和我的测试来看,这两点的答案似乎都是“是”,但如果对浮点标准及其背后的数学有很好正式理解的人可以证实这一点,那将对我很有帮助.我也很好奇是否已知任何已知的更积极的优化,如 gcc 的 -Ofast
会破坏任何这些。
一般情况下是的,两者都应该是正确的。浮点数基数必须是 - 如果不是 2,则至少 integer 并且鉴于此,转换为 nearest 浮点值的整数可以 从不 产生非零分数 - 精度足够或 base 中的最低阶 integer 数字浮动类型的将被归零。例如,在您的情况下,您的系统使用 ISO/IEC/IEEE 60559 二进制浮点数。在基数2中检查时,可以看到该值的尾随数字确实被归零:
>>> bin(9223372036854775000)
'0b111111111111111111111111111111111111111111111111111110011011000'
>>> bin(9223372036854774784)
'0b111111111111111111111111111111111111111111111111111110000000000'
将不带小数的double转换为整数类型,假设double的值落在整数类型的范围内应该准确...
尽管您仍然可能会遇到实施质量问题,或完全错误 - 例如
以下假定转换的值为正数。负数的行为是类似的。
C 2018 6.3.1.4 2 指定从整数到实数的转换并说:
… If the value being converted is in the range of values that can be represented but cannot be represented exactly, the result is either the nearest higher or nearest lower representable value, chosen in an implementation-defined manner.
这告诉我们一些整数值 x 被转换为浮点数只有当两个可表示值之一边界 x 时才能产生非整数 不是整数,x 不可表示。
5.2.4.2.2 指定用于浮点数的模型。每个有限浮点数都由按b[=25=缩放的特定基数b中的数字序列表示]e 对于某些指数 e。 (b 是一个大于 1 的整数。)然后,如果两个值之一限定 x,则说 p 不是整数,则缩放必须使该浮点数中的最低数字代表分数。但如果是这种情况,则将 p 中表示分数的所有数字设置为 0 必须产生一个新的整数浮点数。如果 x < p,则此整数必须为 x,因此 x 可以浮点格式表示。另一方面,如果 p < x,我们可以向代表分数的每个数字添加足够的数以使其为 0(并产生进位到下一个更高的数字)。这也将产生一个可表示为浮点类型 1 的整数,并且它必须是 x.
因此,如果将整数 x 转换为浮点类型会产生非整数,则 x 必须是可表示的在类型中。但随后转换为浮点类型必须产生 x。所以永远不可能产生非整数。
脚注
1 这可能会执行所有数字,就像将其应用于三位十进制数 9.99 时一样,结果为 10.00。在这种情况下,生成的值是 b 的下一次幂,如果它在浮点格式的范围内。如果不是,则 C 标准未定义行为。另请注意,C 标准对浮点格式必须支持的范围设定了最低要求,这排除了任何格式无法表示 1 的情况,这避免了转换可能产生类似 .999 的数字的退化情况,因为它是最大可表示的有限值。
When a 64bit
int
is cast to 64bit float ... and doesn't have an exact match, will it always land on a non-fractional number?
Is this guaranteed to happen for any value ofint64_t
cast to adouble
?
对于common double
:是的,它总是落在非小数上
当没有匹配项时,结果是上方或下方最接近的浮点可表示值,具体取决于舍入模式。鉴于commondouble
的特点,这2个边界值也是整数。当该值不可表示时,首先是附近的整数1。
... if I cast this non-fractional
double
back toint64_t
, will I always get the exact corresponding 64bitint
with the .0 chopped off?
没有。 INT64_MAX
附近的边缘案例失败,因为转换后的值可能变成高于 INT64_MAX
的 FP 值。然后转换回整数类型会导致:“新类型已签名且无法在其中表示值;结果是实现定义的或引发了实现定义的信号。” C17dr § 6.3.1.3 3
#include <limits.h>
#include <string.h>
int main() {
long long imaxm1 = LLONG_MAX - 1;
double max = (double) imaxm1;
printf("%lld\n%f\n", imaxm1, max);
long long imax = (long long) max;
printf("%lld\n", imax);
}
9223372036854775806
9223372036854775808.000000
9223372036854775807 // Value here is implementation defined.
更深层次的异常
(Question variation) When an N bit integer type is cast to a floating point and doesn't have an exact match, will it always land on a non-fractional number?
整数类型范围超过有限浮点数
转换为无穷大:用普通的float
和uint128_t
,UINT128_MAX
转换为无穷大。这对于超宽整数类型来说很容易实现。
int main() {
unsigned __int128 imaxm1 = 0xFFFFFFFFFFFFFFFF;
imaxm1 <<= 64;
imaxm1 |= 0xFFFFFFFFFFFFFFFF;
double fmax = (float) imaxm1;
double max = (double) imaxm1;
printf("%llde27\n%f\n%f\n", (long long) (imaxm1/1000000000/1000000000/1000000000),
fmax, max);
}
340282366920e27
inf
340282366920938463463374607431768211456.000000
浮点进动深度超过范围
在某些 unicorn 实现中,FP 精度非常宽且范围很小,理论上最大的有限可能是一个非整数,但实际上并非如此。然后使用更宽的整数类型,转换可能会导致这个非整数值。我不认为这是 OP 的合法关注点。