简体   繁体   中英

Largest "float" less than or equal to `int.MaxValue`

I'm trying to write a conversion function that takes a float and returns an int , which basically does a saturated conversion. If the it's greater than int.MaxValue then int.MaxValue should be returned, likewise for int.MinValue .

I'd rather not catch exceptions as part of my normal flow control and instead just explicitly check the bounds except I'm not sure what the upper bound is as the largest float that can be stored in a int is smaller than int.MaxValue as a float has less precision than an int for values that size.

Basically I'm looking for the ... in:

float largestFloatThatCanBeStoredInAnInt = ...

I would suggest you just hard-code it as the right data type:

var largestFloatThatCanBeStoredInAnInt = 2147483000f;

2,147,483,000 is the highest value you can store in a float which is less than int.MaxValue

Let's carry out an experiment :

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

The outcome is

   max float = 2147483520
min overflow = 2147483650
     max int = 2147483583

So we can conclude that the max float which can be cast to int is 2147483520 . The max int which can be cast into float and back to int is 2147483583 ; if we try to cast 2147483583 + 1 = 2147483584 we'll get 2147483650f which will throw excpetion if we try to cast it back to int .

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

or even

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

Finally, float to int conversion (no exceptions; int.MaxValue or int.MinValue if float is out of range ):

  // 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 approach works around the issue:

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

The use of >= is important here. Use > only and you'll miss (float) int.MaxValue , and then when you do the ordinary cast you'll find (int) (float) int.MaxValue == int.MinValue which as a result would make this function return the wrong value.

Won't that work?

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

This expression is true:

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

Why don't you just check the limits?

if(myFloat > int.MaxValue)
 return int.MaxValue;

if(myFloat < int.MinValue)
 return int.MinValue;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM