获取 unsigned int 或 float 的(float 的)尾数 (C)
Getting the mantissa (of a float) of either an unsigned int or a float (C)
所以,我正在尝试编写一个函数,以其 (mantissa * 2^exponent) 格式打印给定的浮点数 (n)。我能够得到符号和指数,但不能得到尾数(无论数字是多少,尾数始终等于 0.000000)。我有的是:
unsigned int num = *(unsigned*)&n;
unsigned int m = num & 0x007fffff;
mantissa = *(float*)&m;
知道问题出在哪里吗?
C 库包含一个函数来完成这个任务,frexp
:
int expon;
float mant = frexpf(n, &expon);
printf("%g = %g * 2^%d\n", n, mant, expon);
另一种方法是使用 log2f
和 exp2f
:
if (n == 0) {
mant = 0;
expon = 0;
} else {
expon = floorf(log2f(fabsf(n)));
mant = n * exp2f(-expon);
}
对于相同的输入,这两种技术可能会给出不同的结果。例如,在我的计算机上,frexpf
技术将 4 描述为 0.5 × 23,但 log2f
技术将 4 描述为 1 × 22 。从数学上讲,两者都是正确的。此外,frexp
会为您提供尾数的确切位,而 log2f
和 exp2f
可能会舍入最后一两位。
您应该知道 *(unsigned *)&n
和 *(float *)&m
违反了针对 "type punning" 的规则并且具有未定义的行为。如果要获得与浮点数具有相同位表示的整数,反之亦然,请使用联合:
union { uint32_t i; float f; } u;
u.f = n;
num = u.i;
(注意:大约从 2003 年开始,这种联合的使用在 C 语言中得到了明确的定义,但是,由于 C++ 委员会长期以来没有足够重视 C 语言中的变化,所以它不是正式的-在 C++ 中定义。)
您还应该知道 IEEE 浮点数使用 "biased" 指数。当您初始化一个 float
变量的尾数字段但将其指数字段保留为零时,这将为您提供一个具有 大负数 指数的数字表示:换句话说,一个数字太小以至于 printf("%f", n)
会将其打印为零。每当 printf("%f", variable)
打印零时,将 %f
更改为 %g
或 %a
并在假设 variable
实际上为零之前重新运行程序。
除了 zwol 的评论:如果你想自己做,你必须了解一些关于 IEEE-754 浮点数内部结构的知识。完成后,您可以编写类似
的内容
#include <stdlib.h>
#include <stdio.h>
#include <math.h> // for testing only
typedef union {
float value;
unsigned int bits; // assuming 32 bit large ints (better: uint32_t)
} ieee_754_float;
// clang -g3 -O3 -W -Wall -Wextra -Wpedantic -Weverything -std=c11 -o testthewest testthewest.c -lm
int main(int argc, char **argv)
{
unsigned int m, num;
int exp; // the exponent can be negative
float n, mantissa;
ieee_754_float uf;
// neither checks nor balances included!
if (argc == 2) {
n = atof(argv[1]);
} else {
exit(EXIT_FAILURE);
}
uf.value = n;
num = uf.bits;
m = num & 0x807fffff; // extract mantissa (i.e.: get rid of sign bit and exponent)
num = num & 0x7fffffff; // full number without sign bit
exp = (num >> 23) - 126; // extract exponent and subtract bias
m |= 0x3f000000; // normalize mantissa (add bias)
uf.bits = m;
mantissa = uf.value;
printf("n = %g, mantissa = %g, exp = %d, check %g\n", n, mantissa, exp, mantissa * powf(2, exp));
exit(EXIT_SUCCESS);
}
注意:上面的代码是 quick&dirty(tm) 类型之一,不适用于生产。它还缺乏对次正规(非正规)数字的处理,这是你 必须 包括的东西。提示:将尾数乘以 2 的大幂(例如:2^25 或在那个范围内)并相应地调整指数(如果您将我的示例的值减去 25)。
你正在剥离指数的位,留下 0。指数 0 是特殊的,这意味着数字是非规范化的并且非常小,在可表示数字范围的最底部。我想如果你仔细观察你会发现你的结果不完全为零,只是太小以至于你无法分辨差异。
要获得一个合理的尾数,您需要重新输入一个适当的指数。如果您想要一个介于 1.0 到 2.0 之间的尾数,您需要一个 0 的指数,但是添加偏差意味着您真的需要 127 的指数。
unsigned int m = (num & 0x007fffff) | (127 << 23);
mantissa = *(float*)&m;
如果你想要一个完整的整数尾数,你需要一个 23 的指数,有偏差它变成 150。
unsigned int m = (num & 0x007fffff) | ((23+127) << 23);
mantissa = *(float*)&m;
所以,我正在尝试编写一个函数,以其 (mantissa * 2^exponent) 格式打印给定的浮点数 (n)。我能够得到符号和指数,但不能得到尾数(无论数字是多少,尾数始终等于 0.000000)。我有的是:
unsigned int num = *(unsigned*)&n;
unsigned int m = num & 0x007fffff;
mantissa = *(float*)&m;
知道问题出在哪里吗?
C 库包含一个函数来完成这个任务,frexp
:
int expon;
float mant = frexpf(n, &expon);
printf("%g = %g * 2^%d\n", n, mant, expon);
另一种方法是使用 log2f
和 exp2f
:
if (n == 0) {
mant = 0;
expon = 0;
} else {
expon = floorf(log2f(fabsf(n)));
mant = n * exp2f(-expon);
}
对于相同的输入,这两种技术可能会给出不同的结果。例如,在我的计算机上,frexpf
技术将 4 描述为 0.5 × 23,但 log2f
技术将 4 描述为 1 × 22 。从数学上讲,两者都是正确的。此外,frexp
会为您提供尾数的确切位,而 log2f
和 exp2f
可能会舍入最后一两位。
您应该知道 *(unsigned *)&n
和 *(float *)&m
违反了针对 "type punning" 的规则并且具有未定义的行为。如果要获得与浮点数具有相同位表示的整数,反之亦然,请使用联合:
union { uint32_t i; float f; } u;
u.f = n;
num = u.i;
(注意:大约从 2003 年开始,这种联合的使用在 C 语言中得到了明确的定义,但是,由于 C++ 委员会长期以来没有足够重视 C 语言中的变化,所以它不是正式的-在 C++ 中定义。)
您还应该知道 IEEE 浮点数使用 "biased" 指数。当您初始化一个 float
变量的尾数字段但将其指数字段保留为零时,这将为您提供一个具有 大负数 指数的数字表示:换句话说,一个数字太小以至于 printf("%f", n)
会将其打印为零。每当 printf("%f", variable)
打印零时,将 %f
更改为 %g
或 %a
并在假设 variable
实际上为零之前重新运行程序。
除了 zwol 的评论:如果你想自己做,你必须了解一些关于 IEEE-754 浮点数内部结构的知识。完成后,您可以编写类似
的内容#include <stdlib.h>
#include <stdio.h>
#include <math.h> // for testing only
typedef union {
float value;
unsigned int bits; // assuming 32 bit large ints (better: uint32_t)
} ieee_754_float;
// clang -g3 -O3 -W -Wall -Wextra -Wpedantic -Weverything -std=c11 -o testthewest testthewest.c -lm
int main(int argc, char **argv)
{
unsigned int m, num;
int exp; // the exponent can be negative
float n, mantissa;
ieee_754_float uf;
// neither checks nor balances included!
if (argc == 2) {
n = atof(argv[1]);
} else {
exit(EXIT_FAILURE);
}
uf.value = n;
num = uf.bits;
m = num & 0x807fffff; // extract mantissa (i.e.: get rid of sign bit and exponent)
num = num & 0x7fffffff; // full number without sign bit
exp = (num >> 23) - 126; // extract exponent and subtract bias
m |= 0x3f000000; // normalize mantissa (add bias)
uf.bits = m;
mantissa = uf.value;
printf("n = %g, mantissa = %g, exp = %d, check %g\n", n, mantissa, exp, mantissa * powf(2, exp));
exit(EXIT_SUCCESS);
}
注意:上面的代码是 quick&dirty(tm) 类型之一,不适用于生产。它还缺乏对次正规(非正规)数字的处理,这是你 必须 包括的东西。提示:将尾数乘以 2 的大幂(例如:2^25 或在那个范围内)并相应地调整指数(如果您将我的示例的值减去 25)。
你正在剥离指数的位,留下 0。指数 0 是特殊的,这意味着数字是非规范化的并且非常小,在可表示数字范围的最底部。我想如果你仔细观察你会发现你的结果不完全为零,只是太小以至于你无法分辨差异。
要获得一个合理的尾数,您需要重新输入一个适当的指数。如果您想要一个介于 1.0 到 2.0 之间的尾数,您需要一个 0 的指数,但是添加偏差意味着您真的需要 127 的指数。
unsigned int m = (num & 0x007fffff) | (127 << 23);
mantissa = *(float*)&m;
如果你想要一个完整的整数尾数,你需要一个 23 的指数,有偏差它变成 150。
unsigned int m = (num & 0x007fffff) | ((23+127) << 23);
mantissa = *(float*)&m;