That's not a "crazy behavior", it's a limitation of the algorithm they use. mt_rand() only has approx. 2^32 possible seeds; why would you expect it to support ranges larger than 2^32?
> why would you expect it to support ranges larger than 2^32?
Because this is not a simple, stupid linear congruential generator. mt_rand(), is, as I understand it, based on Mersenne Twister, a well-regarded generator which has a period of 2^19937. And if PHP had done this right, mt_rand() would be seedable with an array of some 624 integers.
Mersenne Twister has an alternative "classic" seeding algorithm, drawn from Knuth, to be compatible with old-style generators. This algorithm takes a 32-bit seed. Apparently that's all that PHP is supplying.
It's dead simple to do random(n) uniformly. When I heard that some language implemented a function called mt_rand() which was hilariously broken in this respect, the first thing that came to my mind was "that's gotta be PHP". And it was.
If we were talking about C this would be called undefined behavior. If you don't satisfy the preconditions before calling a function then the not only is the output invalid but the entire program which uses it is invalid too.
I don't think a language like PHP, or indeed anything written in it, should have undefined behavior but I'm not the author of the library.
"Fixing" broken data silently is tremendously bad behaviour in general because you can't possibly know whether the caller knew that they were providing invalid data and whether the caller is going to be happy with your fix.
If you expect input parameters in some range and you get values out of that range, then you blow up. You don't silently truncate anything and you certainly don't reduce the entropy of your random number generator by nearly 50%.
This was my main doubt about PHP 7's scalar types, which will autocast values into the desired types. I don't expect the caller to know better than the callee what are the method's boundaries, and once casted you may not be able to see if the input was garbage. But yeah, when something breaks the method's author can wash their hands.
If the user asked for more bits of randomness, they need to generate more bits. They could have chosen a different algorithm, or perhaps called the random number generator twice and concatenated the bits. Scaling up is clearly the wrong thing to do.
It also makes me wonder how it behaves for numbers below the limit. I would wager that there's substantial bias in the results if you ask for a max of, say, 2^31 + 2^30.
This. Documenting odd behavior like this isn't an excuse for having odd behavior. It should just be changed to fail on bad input. It's not even a breaking change (apart from code already using it with bad input).
The best thing to do is to not use mt_rand().