小于或等于 `int.MaxValue` 的最大 "float"

4
我正在尝试编写一个转换函数,它接受一个 float 并返回一个 int,实现饱和转换。如果大于 int.MaxValue,则应返回 int.MaxValue,小于 int.MinValue 的同理。
我不想将异常处理作为正常流程控制的一部分,而是明确检查边界,但我不确定上限是多少,因为可以存储在 int 中的最大 float 值要比 int.MaxValue 小,因为对于那么大的值,float 的精度小于 int。
基本上我正在寻找中的 ...:
float largestFloatThatCanBeStoredInAnInt = ...

2
float 中,您可以存储的最大值比 int.MaxValue 小的值是 2147483000f。在超过这个值之前,您可以使用的最大 常量 float 文字是 2147483456f,即使添加一小部分也会使其超过。我通过实验找到了这些值,因此无法回答如何以好的方式获得这些值。 - Lasse V. Karlsen
2
@LasseVågsætherKarlsen 我不确定2147483000f是小于int.MaxValue的最大浮点数。那么2147483520f呢?请参见此处 - Sebastian Schumann
我同意,那个数更大,我甚至达到了 2147483583f,然后它就翻了,我猜之前测试时我的代码有 bug。这最终变成了字节 ff-ff-ff-4e,将值增加 1 则变为 00-00-00-4f,被认为更高。而 35833520 只是在字面上的区别,无论如何都存储为 3520 - Lasse V. Karlsen
相关:在可移植的C中是否可以编写从double到int的转换。我的答案包括一个C例程,用于计算给定的int x加1后小于最大的double,因此是小于或等于x的最大double。但它依赖于C提供的信息,例如DBL_MANT_DIG - Eric Postpischil
4个回答

3

让我们进行一个实验:

  float best = 0f;

  for (int i = 2147483000; ; ++i)
  {
    float f = (float)i;

    try
    {
      checked
      {
        int v = (int)f;
      }

      best = f;
    }
    catch (OverflowException)
    {
      string report = string.Join(Environment.NewLine, 
        $"   max float = {best:g10}",
        $"min overflow = {f:g10}",
        $"     max int = {i - 1}");

      Console.Write(report);

      break;
    }
  }

结果是:
   max float = 2147483520
min overflow = 2147483650
     max int = 2147483583

我们可以得出结论,最大的可转换为intfloat2147483520。最大的可转换为float并且再转回intint2147483583; 如果我们尝试将2147483583 + 1 = 2147483584强制转换,我们将得到2147483650f,如果我们尝试将其转回int,会引发异常。

int overflow = 2147483583 + 1;
int back = checked((int)(float) overflow); // <- throws exception

或者甚至更多

float f_1 = 2147483583f;        // f_1 == 2147483520f (rounding)
  int i_1 = checked((int) f_1); // OK

float f_2 = 2147483584f;        // f_2 == 2147483650f (rounding) > int.MaxValue
  int i_2 = checked((int) f_2); // throws exception

最后,将float转换为int(无异常处理;如果float超出范围,则返回int.MaxValueint.MinValue):

  // float: round errors (mantissa has 23 bits only: 23 < 32) 
  public static int ClampToInt(this float x) =>
      x >  2147483520f ? int.MaxValue 
    : x < -2147483650f ? int.MinValue
    : (int) x;

  // double: no round errors (mantissa has 52 bits: 52 > 32)
  public static int ClampToInt(this double x) =>
      x > int.MaxValue ? int.MaxValue 
    : x < int.MinValue ? int.MinValue
    : (int) x;

谢谢那个实验。我使用这个,输入2147483000以获取指数并激活尾数中的所有位,得到了相同的值。 - Sebastian Schumann

2
我建议您将正确的数据类型硬编码: "最初的回答"
var largestFloatThatCanBeStoredInAnInt = 2147483000f;

在float中,你可以存储小于int.MaxValue的最大值为2,147,483,000。

最初的回答:2,147,483,000是可以存储在float中且小于int.MaxValue的最大值。


实际上,你 可以 更高,但这是一个技巧。如果你使用 2147483456f 它仍然不会超过但它将以 ...3000 存储,所以它不会改变结果。 - Lasse V. Karlsen
2
目前我认为 2147483520f 是最大的可能浮点数。这是通过使用此计算器将所有位设置在尾数中并使用与 2147483000f 相同的指数来计算得出的。可能存在更大的值。 - Sebastian Schumann
在C#中,最大的可表示的值为2147483520,它小于int.MaxValue,对应的数据类型是float - Eric Postpischil

1
这种方法可以解决这个问题:
public static int ClampToInt(this float x)
{
    const float maxFloat = int.MaxValue;
    const float minFloat = int.MinValue;

    return x >= maxFloat ? int.MaxValue : x <= minFloat ? int.MinValue : (int) x;
}

在这里使用 >= 是很重要的。只使用 > 会错过 (float) int.MaxValue,然后当您进行普通转换时,您会发现 (int) (float) int.MaxValue == int.MinValue,这将导致该函数返回错误的值。

@TheGeneral,也许我搞砸了。哪个反例对这个失败了? - Clinton
2
哦,糟糕,这比我想象的更复杂。 - Clinton
补丁:maxFloat = 2147483520fminFloat = -2147483650f; - Dmitry Bychenko
@DmitryBychenko,我拥有的代码似乎没有问题,可以通过您的反例:https://ideone.com/We3IK4。您能解释在什么情况下会出现问题吗? - Clinton
我不理解您的评论。Ideone 看起来即使是在您的示例中也能正常工作。您是说这个示例在本地运行时会抛出异常,但在 Ideone 上却可以正常工作吗? - Clinton
非常抱歉,这是我的误解。int.MaxValue将被转换为2147483560f,而2147483584将变为2147483560f(四舍五入)。然后,由于您已经放置了>=,因此将返回正确的int.MaxValue - Dmitry Bychenko

-1

那样不行吗?

float largestFloatThatCanBeStoredInAnInt = (float)int.MaxValue - 1;

这个表达式是真的:

(float)int.MaxValue - 1 < int.MaxValue

我不这么认为,因为(int)(float)int.MaxValue != int.MaxValue - Clinton
你不能用浮点数精确地表示任何整数,所以你只需要减去一些小数来确保它小于 int.MaxValue,这样行吗? - Ilya Chernomordik
@IlyaChernomordik:我不确定你所说的“用浮点数无法精确表示任何整数”是什么意思。你不能用浮点数精确地表示每个整数,但有很多整数是可以的。例如,1f就是精确的1。 - Jon Skeet
1
@JonSkeet 我刚才用英语写错了,我当然是指“每个” :) - Ilya Chernomordik
2
如果你从 2147484000f 减去 1f,你会得到 2147484000f - Lasse V. Karlsen
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接