很多年前,调研过浮点数与整数之间的双射问题:
win7 intel x64 cpu vs2013 c语言浮点数精度失真问题
最近重新学习了一下IEEE754标准,也许实际还有很多深刻问题没有被揭示。
计算机程序设计艺术,据说这本书中也有讨论。
参考:https://upimg.baike.so.com/doc/643382-681042.html
float32在线测试页面 :https://www.h-schmidt.net/FloatConverter/IEEE754.html
我用手写计算的方式,算过一遍之后,得到一个结论:
任意数值的32bit,对应的float,都可以被精确地计算,转换为一个无误差对应的字符串,且与printf %.Nf一致,只要N足够大。
eg:float FLT_TRUE_MIN == 0x00000001, 0.00000000000000000000000000000000000000000000140129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125 (149位小数)
所有其他float都是FLT_TRUE_MIN这个数的整数倍。
稍微计算几个值就明白为什么。fraction中第一个1代表0.5,第二个1是0.25,然后是0.125,0.0625,0.03125 …尾数一直可以被2整除。题外话,如果是前苏联的3进制计算机,可能就存在无限循环除不尽的问题。
但是,前面提过,float与u32的双射问题。
显然32bit最多只有4,294,967,295个值。
u32只有10位十进制数,与149位十进制小数到FLT_MAX(340282346638528859811704183484516925440.000000)是无法一一对应的。
那么中间,绝大多数的浮点数数值都没有32bit的对应值,也就是说,人工随便写的一个浮点数字符串,有99%以上的概率转换为float之后,再转换为string,是无法还原的!
但是,c基础库的printf %f和atof有一种不失真、可逆的转换实现!只是这种实现是从32bit到float string,再还原到32bit。
人为造假float值比较容易被识别,只要是不可逆的就一定是人为改过的!
个人觉得IEEE754标准的设计,c语言的实现,并不是看起来那么简单。
稍微提几个方面:硬件电路、编译器、累计误差…这些方面的坑感觉都很深。
下面是个人测试float的代码及测试结果:
#include <stdio.h>
#include <float.h>
#include <math.h>
union u32f32 {
unsigned int u32;
float f32;
struct {
unsigned int fraction : 23;
unsigned int exp : 8;
unsigned int sign : 1;
};
};
static char c_u32[64] = {
0 };
static char c_sign[64] = {
0 };
static char c_exp[64] = {
0 };
static char c_fraction[64] = {
0 };
void to_binary_string_with_spaces(const unsigned int u32, const int bits, char c_temp[64]) {
int valid_bits = bits <= 32 ? bits : 32;
int char_index = 0;
for (int i = valid_bits - 1; i >= 0; i--) {
if (i % 4 == 3 && i != valid_bits - 1) {
c_temp[char_index++] = ' ';
}
c_temp[char_index++] = (u32 & (1U << i)) ? '1' : '0';
}
c_temp[char_index] = '\0';
}
void print_float_binary(const char* p_name, const unsigned int i) {
const union u32f32 u32_f32_obj = {
.u32 = i };
to_binary_string_with_spaces(u32_f32_obj.u32, 32, c_u32);
to_binary_string_with_spaces(u32_f32_obj.sign, 1, c_sign);
to_binary_string_with_spaces(u32_f32_obj.exp, 8, c_exp);
to_binary_string_with_spaces(u32_f32_obj.fraction, 23, c_fraction)