Reputation: 23135
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 = ...
Upvotes: 4
Views: 1703
Reputation: 186708
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;
Upvotes: 3
Reputation: 23135
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.
Upvotes: 1
Reputation: 136114
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
Upvotes: 2
Reputation: 30205
Won't that work?
float largestFloatThatCanBeStoredInAnInt = (float)int.MaxValue - 1;
This expression is true:
(float)int.MaxValue - 1 < int.MaxValue
Upvotes: -1