4

I need a C# function that takes in an integer and returns a float, which is the largest float smaller than that integer. In normal (not floating point) math, this is an example: For input of 5 the expected output is 4.999999

I tried doing

float Calculate(int input) {
    return ((float)input) - float.Epsilon;
}

But it only works for input of 0, and not for any other integers. For 0 it returns -1,401298E-45 and for others it just returns the same value as input.

I understand that this is due to how floating point math works, but what would be the consistent way of handling this? Would it be manually setting mantissa bits?

12
  • 1
    "it just returns the same value as input" - how did you check that? Commented Jun 12 at 12:02
  • 2
    Does C# have a function equivalent to C++'s nextafter? I suspect that's the right way to do it: (1) Convert your int to a float f1. (2) If f1 is less than your integer, you already have your answer. (3) Use 'nextafter' to compute the next-smallest float f2. (4) Double-check that f2 is less than your integer. (5) It's your answer. (It will be a little bit tricky to do the tests in (2) and (4): you need to make sure to convert f1/f2 back to integer and compare the integers, not convert the int to float again and compare the floats.) Commented Jun 12 at 12:14
  • 2
    What is the actual use case? I imagine that you need to be quite careful whenever converting ints and floats. Since most integers cannot be exactly represented as floating point. Commented Jun 12 at 12:20
  • 3
    @LasernaTunika Nothing personal, but you've ended up asking what Stack Overflow likes to call an "XY question". You asked, "How to calculate largest float smaller than integer", but your real question is "how to get ints in range 0 to N-1, when random number generator gives 0.0 to 1.0?". And the answer is, "Just multiply by N-1". Commented Jun 12 at 14:50
  • 2
    I was interested in the question I posed in particular, that's why I didn't bring up the use case initially. Regarding the multiplying by N-1, I don't think that works, as if you just cast to (int) the only time N-1 will be the result is if exactly N-1 is rolled from the float. If you use Mathf.RoundToInt instead, 0 and N-1 will only get results that are 0.5 or less, N-1 getting same amount of results, and the "middle" ints getting double as much results,. as they get rounded from range of their value plus -0.5 to + 0.5. Correct me if I'm wrong here please! Commented Jun 13 at 12:13

3 Answers 3

8

You can use MathF.BitDecrement. This is available from .NET Core 3+, and is also available as Math.BitDecrement.

var output = MathF.BitDecrement((float)input);

On newer .NET versions this is also available on IFloatingPointIeee754.


The relevant source code for float is fairly simple: just add a 1 to the bare integer value, given that the fraction part is the lowest bits.

public static float BitDecrement(float x)
{
    uint bits = BitConverter.SingleToUInt32Bits(x);

    if (!float.IsFinite(x))
    {
        // NaN returns NaN
        // -Infinity returns -Infinity
        // +Infinity returns MaxValue
        return (bits == float.PositiveInfinityBits) ? float.MaxValue : x;
    }

    if (bits == float.PositiveZeroBits)
    {
        // +0.0 returns -float.Epsilon
        return -float.Epsilon;
    }

    // Negative values need to be incremented
    // Positive values need to be decremented

    if (float.IsNegative(x))
    {
        bits += 1;
    }
    else
    {
        bits -= 1;
    }
    return BitConverter.UInt32BitsToSingle(bits);
}

In Mono you don't have SingleToUInt32Bits so you can do

uint bits = unchecked((int)BitConverter.SingleToInt32Bits(x));
return BitConverter.Int32BitsToSingle(unchecked((int)bits));
Sign up to request clarification or add additional context in comments.

4 Comments

This seems to be correct. My mistake - I didn't specify that I am using Unity 6, with Mono and .Net Standard 2.1 and it seems to be missing this function. Should I edit the main post to make it clearer?
I've retagged it for you, yes you should have put those tags in.
OP will have to try that .. the UInt32Bit methods are (according to the c# API) only available in full .Net .. Unity uses some custom profiles that don't necessarily include all API
This works! Confirming that MathF.BitDecrement is missing, as well as BitConverter.SingleToUInt32Bits and float.PositiveInfinityBits and float.PositiveZeroBits. But ignoring the floats and using your substitute for SingleToUInt32Bits it works, thanks!
0

I know this is factually not answering the question title .. but regarding

The actual use case is a function that returns a random integer between 0 and int maxExclusive

why reimplement things that already got solutions?


sounds to me like Unity's built-in Random.Range(0, maxExclusive) (the int overload) would already exactly behave as expected.

It hooks into native code and might under the hood even use one of the mentioned c++ sided functions


Alternatively for pure c# simply go for Random.Next instead which already solves the same as well without using Unity API (and probabl even the better random implementation - Unity's is supposedly faster though)


Otherwise as was also mentioned: If you have a random method that returns values in the range [0;1] and you want your method to return values in the range [0;maxExcluded( then you should multiply by

randomFloat01 * (maxExcluded - 1)

Comments

0

Consider large integer values

(float)input is a problem when int input is so large that converting to a float is inexact.

float commonly has 24 significant binary digits and large ints may not convert exactly to a float with the same value - instead the float is a rounded value.

int main(void) {
  int i = (1 << 24) * 2 + 2;
  for (int j = 0; j < 10; j++) {
    float f = (float) i;
    printf("i:0x%X %d, f:%.9g\n", (unsigned) i, i, f);
    i++;
  }
}

Ouptut

i:0x2000002 33554434, f:33554432
i:0x2000003 33554435, f:33554436
i:0x2000004 33554436, f:33554436 exact
i:0x2000005 33554437, f:33554436
i:0x2000006 33554438, f:33554440
i:0x2000007 33554439, f:33554440
i:0x2000008 33554440, f:33554440 exact
i:0x2000009 33554441, f:33554440
i:0x200000A 33554442, f:33554440
i:0x200000B 33554443, f:33554444

With the goal of largest float smaller than input integer:
the largest float smaller than input integer 33554435 is 33554432.0
the largest float smaller than input integer 33554437 is 33554436.0

Since 33554435, 33554436, 33554437 all convert to the same float, (float)input alone is insufficient to determine the correct answer.


I would expect additional code to cope with rounding issues.

Perhaps like:

float less_than_int(int i) {
  float f = (float) i;
  int j = (int) f;
  if (j < i) {
    return f;
  }
  // https://stackoverflow.com/a/79663556/2410359
  return BitDecrement(f);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.