【问题标题】:Largest "float" less than or equal to `int.MaxValue`小于或等于 `int.MaxValue` 的最大“浮点数”
【发布时间】:2019-07-03 08:17:42
【问题描述】:

我正在尝试编写一个转换函数,它接受一个float 并返回一个int,它基本上是进行饱和转换。如果它大于int.MaxValue,则应返回int.MaxValue,同样适用于int.MinValue

我宁愿不捕获异常作为我正常流程控制的一部分,而是明确检查边界,除非我不确定上限是多少,因为可以存储在 int 中的最大浮点数小于 @ 987654328@ 作为浮点数的精度低于整数值。

基本上我在寻找...

float largestFloatThatCanBeStoredInAnInt = ...

【问题讨论】:

  • 您可以在float 中存储小于 int.MaxValue 的最高值是2147483000f。在它翻倒之前你可以使用的最大 constant float 字面量是 2147483456f,即使添加一个分数也会让它翻倒。我通过实验找到了这些,所以我无法回答获得这些值的方法。
  • @LasseVågsætherKarlsen 我不确定2147483000f是小于int.MaxValue的最大浮点数。 2147483520f 呢?见here
  • 我同意,这甚至更大,在它翻倒之前我得到了高达2147483583f,我想我以前的代码中有一个错误来测试这个。这最终成为字节 ff-ff-ff-4e,将值增加 1 到 00-00-00-4f,这被认为更高。而35833520 只是在字面上,无论看起来如何,它都存储为3520
  • 相关:Can a conversion from double to int be written in portable C。我的答案包括一个 C 例程,它计算最大的 double 小于给定的 int x 加 1,因此最大的两倍小于或等于 x。但它依赖于C提供的信息,例如DBL_MANT_DIG

标签: c# .net floating-point int rounding


【解决方案1】:

我们来做一个实验

  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

因此我们可以得出结论,可以转换为int 的最大float2147483520。可以转换为float 并返回到int 的最大int2147483583; 如果我们尝试将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

最后,floatint 的转换(没有例外;int.MaxValueint.MinValue 如果float 超出范围):

  // 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;

【讨论】:

  • 感谢您的实验。我使用this 得到了相同的值,输入2147483000 以获取指数并激活尾数中的所有位。
【解决方案2】:

我建议您将其硬编码为正确的数据类型:

var largestFloatThatCanBeStoredInAnInt = 2147483000f;

2,147,483,000 是您可以存储在小于 int.MaxValue 的浮点数中的最大值

【讨论】:

  • 其实你可以走得更高,但这是个技巧。如果你使用2147483456f,它仍然不会结束,但它会被存储为...3000,所以它不会改变结果。
  • 目前我认为2147483520f 是最大可能的浮动。它是使用this calculator 通过设置尾数中的所有位并使用与2147483000f 相同的指数来计算的。可能存在更大的值。
  • 在 C# float 中可表示的小于 int.MaxValue 的最大值正好是 2147483520。
【解决方案3】:

这种方法可以解决这个问题:

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;
}

&gt;= 的使用在这里很重要。只使用&gt;,你会错过(float) int.MaxValue,然后当你进行普通转换时,你会发现(int) (float) int.MaxValue == int.MinValue,结果会使这个函数返回错误的值。

【讨论】:

  • @TheGeneral 也许我搞砸了。失败的反例是什么?
  • 天哪,这比我想象的要复杂
  • 补丁:maxFloat = 2147483520fminFloat = -2147483650f;
  • @DmitryBychenko 我得到的代码似乎对你的反例没有问题:ideone.com/We3IK4。您能解释一下在什么情况下会出现问题吗?
  • 我不明白你的评论。即使使用您的示例,Ideone 似乎也可以正常工作。你是说这个例子在本地抛出,但在 ideone 上有效?
【解决方案4】:

这样不行吗?

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

这个表达式是真的:

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

【讨论】:

  • 我不这么认为,因为(int) (float) int.MaxValue != int.MaxValue
  • 好吧,你不能用浮点数精确地表示任何整数,所以你只需要减去一些小数来确保它小于 int.MaxValue,这样不就行了吗?跨度>
  • @IlyaChernomordik:我不确定你所说的“好吧,你不能用浮点数精确地表示任何整数”是什么意思。你不能用浮点数精确地表示每一个整数,但有很多你可以。例如,1f 正好是 1。
  • @JonSkeet 我只是用英文写错了,我的意思是“每个”当然:)
  • 如果你从2147484000f 中减去1f,你会得到2147484000f
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-30
  • 2011-01-25
  • 1970-01-01
  • 2013-09-02
相关资源
最近更新 更多