How exactly do I determine if using Random.Range is operating to return floats or ints?
It makes a pretty big difference, especially since the max for one is inclusive and the max for the other is exclusive.
Just check the docs - it returns a float
Manual aside, you do have an IDE, right?
And then this:
or an intâŚ
Did you try reading that yourself? Maybe you should scroll down a littleâŚ
This is a prime example of a bad method overload: same number of parameters, similar parameters type (numerics). Unity should have had two different methods: .Range and .RangeF(loat) like Microsoftâs implementation (.Next and NextDouble). Well weâre stuck now with it. You just need to learn it the hard way, after being stung 2 or 3 times and spending hours debugging and wondering why the code does not work
In a sense I agree. On the other hand it really, really bothers me when devs do this:
float value = 13; // implicit conversion: int to float
Get in the habit of writing integral floating point values by suffixing with âfâ:
float value = 13f;
You wonât have this issue anymore:
float value = Random.Range(0, 1);
And then ⌠the compiler has something to say about this:
int value = Random.Range(0f, 1f);
This is an error: cannot convert float to int.
Well, this is certainly debatable. Note that Random.Range works 100% as number multiplication or division. If both arguments are integers, the computer will carry out an integer multiplication / division regardless of the target type. As soon as one of them is a float, it will be a float division / multiplication and the result will be a float as well.
int can be implicitly converted into floats but not the other way round. This is just basic C# rules.
One should also keep in mind that the return type of a method does not contribute to the selection of an overload. In fact method overloads can not only differ in the return type. In theory one could argue that the compiler could figure out which overload is meant based on the type of the receiving variable or argument. However this could cause many more ambiguities, especially since you can call methods and ignoring the return value. In this case the compiler would have no clue at all which overload was meant. So only the method name and the arguments are taken into account when it comes to selecting the proper overload.
So if you want to complain, the blame would be on C# and the implicit type conversion between int and float.
ps: C# Math.Min also has those several overloads and would behave similar.
Certainly, writing:
float myFloat = 10;
should be treated as an error by our IDE. It was in the past but, for practicability I guess, it was removed.
Iâd say it works 99.99999% the same way, since the returns of Random.Range(int a, int b) approaches b, while the returns of Random.Range(float a, float b) includes b. The type matters to the functionality. And across the dozen or so languages/corelibs I have seen, UnityEngine.Randomâs the only one with such a snag.
In most languages with promotion, it depends on promotion or demotion (down-promotion).
Up-promotion, such as int > float > double, is not an error.
Down-promotion, such as double > float > int, is an error.
Well during my experience, bad method overloads caused significant problems in at least two occasions, one in particular, like 10 years ago, in which the whole IIS process of a website crashed randomly without any exception being thrown/caught/logged. The memory just grew exponentially then boom⌠crash, IIS website restarts and the website worked normally until it happened again few hours/days later. We spent weeks trying to reproduce the issue nothing! We couldnât reproduce it. The culprit:
public void SendNotification(Notification notification, Guid deviceId)
{
// send notification to user code here...
}
public void SendNotification(Notification notification, params Guid[] devicesId)
{
foreach (var id in devicesId)
{
SendNotification(notification, id);
}
}
Yep, it was a stack overflow, the 2nd overload called itself instead of calling the 1st methodâŚ
And from that time, I decided to never use method overload with similar argument types and/or the same number of parameters. And I think this is why Microsoft have a .Next and a .NextDouble in their Random implementation.
This is more explicit and cannot be mistaken:
public void SendNotificationToDevices(Notification notification, params Guid[] devicesId)
{
foreach (var id in devicesId)
{
SendNotification(notification, id);
}
}
This happens when you never bother learning why Unity is doing certain things on the way they are. Itâs not a snag. It is a properly documented behavior with the explanation why it is. You just have to learn it. Unity is not .NET enterprise, stop handling it as it were.
I didnât say it was undocumented, and I understand their rationale entirely. Itâs simply different from other libraries which chose different rationales, and that can be a snag for those who make assumptions based on experience. The point remains, the type used in the overload here matters to the functionality.
To answer the question, yes, there are 2 different Random.Ranges. One takes 2 ints and returns an int up to 1 less than the 2nd one. The other takes 2 floats and returns a float between either of them. It could return the last one, but since it uses floats, that barely matters: Random.Range(1.0f, 2.0f) could return 1.9, 1.97, 1.9993 ⌠. The big difference is int or float.
The first thing to know is that C# uses a common thing called âoverloadingâ. You can have two functions with the exact same name, but they count as different if the input types are different. The computer will figure it out, or give an error if it canât. So Random.Range(1,7) rolls 1,2,3,4,5 or 6, and Random.Range(1.0f, 7.0f) can roll 1.2, 5.04, 6.84 and so on. Itâs obvious to the computer since we specifically used 1.0f instead of 1 (and same for the 7).
The second thing to know is how C# fixes int/float mismatches. float f=7;
is technically an error â putting int 7 into a float. But the computer assumes you wanted 7.0f and fixes it. But it wonât do the opposite int n = 3.6f; gives an error, because clearly the programmer was confused. Even int n=7.0f is an error, for the same reason (7 is correct, so why did they do extra work to be wrong?) So the rule is â ints can turn into floats, but not the other way around.
Knowing that, we can figure out what Random.Range(1, 6.0f) does. The computer canât turn 6.0f into an int, but it can turn 1 into 1.0f. So this counts as (1.0f, 6.0f) and chooses the float version. The same thing happens with variables: Random.Range(n,f) uses the float version (assuming n is an int and f is a float). The short version: it only uses the int version if both inputs are ints, otherwise it uses the float one.
The last, last part is, what if you want the int version, but have float variables? Well, C# uses the standard way to turn floats into ints: Random.Range((int)f1, (int)f2); rounds them both down to ints first, and course uses the int version.
Itâs also good to check yourself for cases when youâre not sure:
void Start() {
print("==== 1,3.0f");
for(int i=0;i<10;i++)
print(Random.Range(1 , 3.0f);// should give things like 1.57
print("==== (int)f1, (int)f2");
float f1=5.3f, f2=7.99f;
for(int i=0;i<10;i++)
print(Random.Range((int)f1 , (int)f2); // should give only 5 or 6
}
That feels like a problem with the compiler not handling âparamsâ correctly. A call with a single Guid should use the first function, which is more specific so was clearly intended. Then, besides this bad choice of always choosing âparamsâ, the compiler allowed you to write the first overload, which can never be used, and gave no error.
In fact, https://stackoverflow.com/questions/28561065/method-overloading-using-params-keyword from 8 years ago says your now works â overload resolution uses âparamsâ as a last resort. So you were the victim of a buggy early compiler (or remembered wrong).
Oh, and of course the first version doesnât even need to be there, but I can understand how it happened. There was probably a 1-Guid function, someone wanted to add a multi-Guid one, didnât want to mess with existing code (by simply adding âparamsâ to it) and added the 2nd one.
Exactly. The fact that the float variant includes the top limit is pretty irrelevant since we talk about floats and âone valueâ greater or smaller is something you canât / shouldnât really rely on anyways. Especially when you do math with that value.
Right. I use C# for quite some time now and params was always handled as a last resort. In fact a lot of the standard libraries in the framework often provide several explicit overloads to improve performance and only use the params version when more parameters are needed.
For example string.Concat (which is actually used when you â+â strings together) has several overloads taking 2, 3 or 4 explicit arguments as well as a params implementation to catch everything that requires more than 4. This is a quite common pattern in the CLR libraries and as far as I remember it has been like that for a long time.
Yes youâre right.
I donât remember it exactly but I know it was a method overload bug that caused a stackoverflow. I still have access to the TFS server, Iâll find the bug fix when I have time and post it here.
Okay. So why is it doing it this way?
Iâve seen lots of things that I change my mind about once I see the real reason behind it. But Iâm not seeing the reasoning here; I just see ambiguity that is real easy for someone (even an expert) to get wrong because a variable wasnât what they thought it was. Ceil has CeilToInt so we can force an Int value when we need it; what is the reasoning behind Random.Rangeâs overload design?
To help the simples way possible to feed the result into other systems while youâre making a game.
The float version is inclusive on both ends because the most common usage is 0âŚ1, so it returns the most commonly used range 0âŚ1, so you donât have to mock around with values to get true 0âŚ1 range as most systems expect values.
The int version is minInclusiveâŚmaxExclusive because of the most common usage is to select a value from a list or array or similar kind. And we know thatâs 0âŚLength-1. So feeding 0âŚLength into the range is giving you the value in most commonly used range.
And when once in a blue moon you need maxInclusive behavior you can still add one there. Also once you read the manual how it works and it clicks, itâs easy to remember.
As for what happens when you put one float and one int in, once you learn how types in C# work, it will be clear.