Closed shuen4 closed 2 months ago
You mean that if the highBound (2nd argument) is smaller than the lowBound (1st argument), it should swap them?
For me on 1.36.1.21015, this is not the case for GetRandomInt
, either.
The formulae for min and max output values seem to be:
min = lowBound
max = (lowBound > highBound) ? (2 * lowBound - highBound) : highBound
edit: Also seems to hold for GetRandomReal
.
GetRandomInt
does swap the value but GetRandomReal
does not
Nice observation but isn't it allready documented? https://lep.duckdns.org/jassbot/doc/GetRandomReal
Shuen, thanks for the issue. These functions were among the earliest things I had described. I'll look at code logic later, here's a clean room description of the implementation via v1.29 (because I can't just paste the C-looking something... right? riiight?)
Now it prepares the random part:
(unknown_constant) & subroutine_probably_rand(likely_seed_or_state)
| (all bits FP32 mantissa except for MSB aka 0x3F800000) // 1, 2The code is viewable elsewhere, but I will leave it at that ;)
So the random function really swaps the bounds if low <= high. What I hadn't understood at the time is what happens with the result, it was a quick test.
Judging by the calculated delta, it's how the interval is normalized to be reapplied to the returned randomly generated value (probably in (0.0; 1.0] as per usual), the low bound is added at the last step before return. Important to note, that we observe a different epsilon here, so random reals in Jass will be a common source of <0.001 values, which is supposed to be the epsilon.
@note
Undefined behavior when lowBound > highBound.
it might better if change to
@note
/@bug
This does not swap value when lowBound > highBound, unlike GetRandomInt
.
GetRandomReal(10, -10)
if result is >= 0.0000002f then return low
0 > result then return low
(1.26 and 1.27), possible mean return low
if low == high
if low <= high then calculate delta of float_minus(out2, high, low) ...otherwise calculate delta of float_minus(out3, low, high)
swapped here
float_minus(temp_var, 10, -10)
delta
= 20
(Y) get 32-bits of the calculation: (unknown_constant) & subroutine_probably_rand(likely_seed_or_state) | (all bits FP32 mantissa except for MSB aka 0x3F800000)
unknown_constant = 0x7FFFFF
fp32 fraction(23 bits)
Y
= float between 1 ~ 2, assume 1.5
float_sum(out3, (X), unknown_var)
it should be Y
instead of X
unknown_var = -1.0
out3
= 0.5
float_mul(outZ, (delta), out3)
outZ
= 10
return float_sum(temp_var, low, outZ)
since above already swap this should swap too
2nd arg is low
arg
result
= 20
2nd arg is high
arg
result
= 0
if no swap values in GetRandomReal
function
delta
= -20
outZ
= -10
result
= 0
PS: hvnt tested for GetRandomReal(-10, 10)
Heh in the end I didn't go by the "C-like pseudocode" someone provided us. I found it easier to go off generated values. 😅 Shuen, I wanted very nice values to play with so I found the following seeds (v1.36.2):
local lowSeed = 1229611 -- low bound
local midSeed = 7685839 -- 50% middle in between low and high
local highSeed = 23999343 -- 99.99999% high bound, actual high bound is not in range
Using this function (just call step() repeatedly from chat):
nextSeed = 23993461
seedSteps = 10000000
--[[ Steps must not be a scientific notation or the number becomes a float
and breaks the iterator, it goes on infinitely]]
function step()
local newMax = nextSeed + seedSteps
for i = nextSeed, newMax, 1 do
SetRandomSeed(i)
local r = GetRandomReal(0, 8)
if r == 0 or r == 4 or r >= 7.99999 then
print(table.concat({i, r, tostring(r==0), tostring(r==4), tostring(r >= 7.99999)}, ","))
print(string.format("Found seed: %-16d", i))
newMax = i
break
end
end
nextSeed = newMax + 1
print(string.format("...reached seed: %-16d", newMax))
end
Then test the values (remember due to string.format the values are truncated. The 'high' value is always wrong but it's correctly approximated for reading):
Un/comment the pairs
iterations else it won't fit in Eikonium's debug multiboard.
function testFormula()
local testRReal = function (seed, low, high)
SetRandomSeed(seed);
return string.format("%.16f", GetRandomReal(low,high))
end
local reFormula = function(low, high)
local min = low
local mid
local max = (low > high) and (2 * low - high) or high
if low == high then
mid = low
elseif low < high then
mid = (low + high) / 2
else -- low > high
mid = (low + -high) / 2 + low
end
return min, mid, max
end
local lowSeed = 1229611
local midSeed = 7685839
local highSeed = 23999343
print(string.format("%-10s | %-6s %-6s %-6s | %-6s %-6s %-6s",
" / ",
"wcMin", "wcMid", "wcHigh",
"reMin", "reMid", "reMax"
))
for _, iters in pairs({
--[[{iterL = {-8, -7, 1}, iterH = {-2, 1, 1}},
{iterL = {8, 7, -1}, iterH = {-2, 1, 1}},]]
{iterL = {-2, 1, 1}, iterH = {8, 7, -1}},
{iterL = {-2, 1, 1}, iterH = {-8, -7, 1}},
}) do
local lMin, lMax, lStep = table.unpack(iters.iterL)
local hMin, hMax, hStep = table.unpack(iters.iterH)
for l = lMin, lMax, lStep do
for h = hMin, hMax, hStep do
local wc3min = testRReal(lowSeed, l, h)
local wc3mid = testRReal(midSeed, l, h)
local wc3max = testRReal(highSeed, l, h)
local reMin, reMid, reMax = reFormula(l, h)
print(string.format("%-5.0f, %-5.0f | %-7.1f %-7.1f %-7.1f | %-7.1f %-7.1f %-7.1f",
l, h,
wc3min, wc3mid, wc3max,
reMin, reMid, reMax
))
end
end
end
end
testFormula()
wc3min/mid/max are game's rng values. reMin etc is reverse engineered formula (see reFormula
function)
Results:
Now that I haven't given any thought whether any values swap there - you'll answer this instead 😝 And because I hope this is correct and I don't want to come back to this later if it's really all right, I will add the formula function and seed as-is to the GetRandomReal description.
PS: I don't know what I find cooler. The three seeds or the (hopefully final) formula. Water's min/max code was correct, I used it above.
Now that I haven't given any thought whether any values swap there
local max = (low > high) and (2 * low - high) or high
(2 * low - high)
simplify from low + low - high
or high
simplify from low + high - low
function(low, high)
local min = low
local delta = (low > high) and (low - high) or (high - low)
local mid = low + delta / 2
local max = low + delta
return min, mid, max
end
side note:
final output of GetRandomReal
was low + delta * (random - 1)
[^1]
[^1]: random
= random float between 1(0x3f800000) ~ 1.99999988079071044921875(0x3fffffff)[^2], also the reason for GetRandomReal
does not include highBound
[^2]: used https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert hex to float
Right thanks. This looks correct and simple as it was supposed to be :) I updated the PR with your code.
i think
GetRandomReal
doesn't swap value likeGetRandomInt
doesGetRandomReal(-12, 99)
- generate 1 number and add -12GetRandomReal(99, -12)
- generate 1 number and add 99, resulting 99 - (-12) offset compared to correct function usageGetRandomReal(99, -12) - /* 99 - (-12) */ 111
- same above but offset is used to produce same result asGetRandomReal(-12, 99)