为何Math.Round(Math.Log(1, 2)+0.5)的计算结果为0?
为什么Math.Round(Math.Log(1,2)+0.5)返回0而非1?
测试代码
public static void Main(string[] args) { Console.WriteLine($"{Math.Round(0.5)} = round(0.5)"); Console.WriteLine($"{ToBinaryString(0.5)} = binary 0.5"); Console.WriteLine(); Console.WriteLine($"{Math.Log(1, 2)} = log2(1)"); Console.WriteLine($"{ToBinaryString(Math.Log(1, 2))} = binary log2(1)"); Console.WriteLine(); Console.WriteLine($"{Math.Round(Math.Log(1, 2) + 0.5)} = round(log2(1) + 0.5)"); Console.WriteLine($"{ToBinaryString(Math.Log(1, 2) + 0.5)} = binary log2(1) + 0.5"); Console.WriteLine(); Console.WriteLine($"{Math.Round(0.0 + 0.5)} = round(0.0 + 0.5)"); Console.WriteLine($"{ToBinaryString(0.0 + 0.5)} = binary 0.0 + 0.5"); Console.WriteLine(); Console.WriteLine($"{Math.Log(1, 2) + 0.5 == 0.5} = Math.Log(1, 2) + 0.5 == 0.5"); Console.WriteLine($"{Math.Log(1, 2) == 0.0} = Math.Log(1, 2) == 0.0"); } static string ToBinaryString(double value) { const int bitCount = sizeof(double) * 8; long intValue = System.BitConverter.ToInt64(BitConverter.GetBytes(value), 0); return Convert.ToString(intValue, 2).PadLeft(bitCount, '0'); }
输出结果
1 = round(0.5) 0011111111100000000000000000000000000000000000000000000000000000 = binary 0.5 0 = log2(1) 0000000000000000000000000000000000000000000000000000000000000000 = binary log2(1) 0 = round(log2(1) + 0.5) // ???? Why ???? 0011111111100000000000000000000000000000000000000000000000000000 = binary log2(1) + 0.5 1 = round(0.0 + 0.5) 0011111111100000000000000000000000000000000000000000000000000000 = binary 0.0 + 0.5 True = Math.Log(1, 2) + 0.5 == 0.5 True = Math.Log(1, 2) == 0.0
问题解释
你观察到的矛盾现象,核心是浮点数精度特性与Math.Round舍入规则共同作用的结果:
Math.Log(1,2)并非严格等于0.0
表面上的相等判断和二进制显示都显示它等于0.0,但实际上Math.Log(1, 2)返回的是一个极其接近0的负极小值(例如-2.220446049250313E-16)。这个值在默认字符串格式化时会被舍入为0.0,且浮点数精确比较对这种极小误差不敏感,因此与0.0的相等判断返回True;而你的ToBinaryString函数转换时,因这个极小值的二进制差异仅存在于最低几位,被处理后显示为全0(实际并非完全一致)。相加结果略小于0.5
当这个极小负值与0.5相加时,得到的是0.5 - ε(ε为极小正数),这个值非常接近0.5,但严格小于0.5。Math.Round的舍入行为差异
从输出Math.Round(0.5) = 1可以推断,你使用的是**.NET Framework 1.x版本**,该版本中Math.Round默认采用向远离零方向舍入:- 严格的0.5会被舍入到1;
- 略小于0.5的值会被舍入到0,因为它更接近0而非1。
而0.0 + 0.5得到的是严格的0.5,因此Math.Round返回1,与前者形成差异。
验证方式
你可以通过输出高精度数值确认这一点:
Console.WriteLine($"{Math.Log(1, 2):G17} = precise log2(1)"); Console.WriteLine($"{Math.Log(1, 2) + 0.5:G17} = precise log2(1)+0.5");
运行后会显示出Math.Log(1,2)的真实极小负值,以及相加后略小于0.5的结果。
内容的提问来源于stack exchange,提问作者Nikolai
相关产品推荐
相关产品推荐

