寻找float、double、long double最小正数,float结果异常排查
尝试通过while循环计算float、double、long double三种类型的最大值和最小正数值,其中double、long double的结果及float的最大值均正确,但float的最小正数输出为0。观察循环过程发现,正确的最小正数是倒数第二个值,但循环会多执行一次,错误地将0赋值给small_f——即使最后一次计算的small_f*0.5已经被舍入为0,仍判定0>0并进入循环。
程序代码如下:
#include <iostream> #include <cmath> using namespace std; int main() { float big_f = 1, small_f = 1; double big_d = 1, small_d = 1; long double big_ld = 1, small_ld = 1; while(!(isinf(big_f*2))) { big_f *= 2; } while((small_f*0.5)>0 and !((small_f*0.5)==0)) { small_f *= 0.5; } cout << "Najmensie cislo float je "<< small_f << endl; cout << "Najvacsie cislo float je "<< big_f << endl; while(!(isinf(big_d*2))) { big_d *= 2; } while((small_d*0.5)>0) { small_d *= 0.5; } cout << "Najmensie cislo double je "<< small_d << endl; cout << "Najvacsie cislo double je "<< big_d << endl; while(!(isinf(big_ld*2))) { big_ld *= 2; } while((small_ld*0.5)>0) { small_ld *= 0.5; } cout << "Najmensie cislo long double je "<< small_ld << endl; cout << "Najvacsie cislo long double je "<< big_ld << endl; }
注:作业要求不能直接使用FLT_MIN等宏。
核心原因
问题出在隐式类型提升:代码中使用的0.5是double类型,当float类型的small_f与double运算时,small_f会被自动转换为double类型。
当small_f达到float的最小正正规数时,乘以0.5(double)的结果在double类型中仍能表示为一个非零的极小值,因此循环条件(small_f*0.5)>0会判定为真;进入循环后将这个double值赋值回float时,会因为超出float的最小可表示范围而下溢为0,最终导致small_f被设置为0。
你添加的!((small_f*0.5)==0)判断也无效,因为此时small_f*0.5是double类型的非零值,与0(会转为double)比较结果为假,取反后为真,依然满足循环条件。
修复方法
将循环中的0.5改为float类型的0.5f,确保整个运算过程都在float类型下进行:
while((small_f*0.5f) > 0) { small_f *= 0.5f; }
这样,当small_f是float的最小正正规数时,乘以0.5f的结果会直接下溢为float类型的0,此时(small_f*0.5f)>0判定为假,循环终止,small_f将保留正确的最小正数值。同时可以去掉多余的!((small_f*0.5)==0)判断,因为使用float类型运算时,下溢后的结果直接是0,>0的判断已经足够终止循环。
内容的提问来源于stack exchange,提问作者CaptainParsifal

