• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

real Talk - Floats in Warcraft 3

Status
Not open for further replies.
Level 14
Joined
Dec 12, 2012
Messages
1,007
That's why i added //! +rb.

Ok, I would say we move this discussion to this thread since this is getting a bit OT.

The script I gave printed the correct alive and dead terms based on your value of minimum life. I used a constant of 0.405 for that which seemed to work with less than 0.405 being dead.

Where do you get from that this value is correct? You output something like if life < 0.405 then dead, but since this 0.405 is the exact value that we are looking for I don't even get why you put the output alive/dead there? This has to be verified with real units anyway.

If I use SetWidgetLife like explained above on real units, then I get exactly the reported results.
 
Level 9
Joined
Jul 30, 2012
Messages
156
I just found something pretty interesting:
JASS:
BJDebugMsg(R2SW(3.14159, 50, 50));
BJDebugMsg(R2SW(3.141592, 50, 50));
BJDebugMsg(R2SW(3.1415926, 50, 50));
BJDebugMsg(R2SW(3.14159265, 50, 50));
BJDebugMsg(R2SW(3.141592653, 50, 50));
BJDebugMsg(R2SW(3.1415926535, 50, 50));
BJDebugMsg(R2SW(3.14159265359, 50, 50));
BJDebugMsg(R2SW(Deg2Rad(180.0), 50, 50));
BJDebugMsg(R2SW(Acos(-1.0), 50, 50));
Output:
Code:
3.141589872
3.141591776
3.141592496
3.141592496
3.141592496
4.004156590
4.048209664
3.141592496
3.141592736

That's quite strange. The first value is the very same literal specified in blizzard.j (it yields the same result if you use bj_PI instead of the literal). As the precision increases, the results become even weirder. The last two literals have more than 10 digits, which seems to bug everything.

For comparison, I'm also testing 2 natives that should return Pi. Deg2Rad is consistent with the literals, while Acos returns a slightly bigger value, probably the next one that can be represented, and the closest to the real Pi.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
I just found something pretty interesting:

Yes, it seems that too long literals cause errors... however, after increasing the accuracy even more, the value gets "correct" again sometimes. I still didn't find out which rules determine this behavior, but its really strange.

Usually floating point literals that are not exactly representable get rounded towards the next representable value by the compiler. Blizzard seems to do something different here.

Bribe said:
R2SW has problems all on its own.

True, but this phenomenon isn't related to those problems... the standard R2S for example also displays those results.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Fun fact:

set time[dex] = time[dex] - 0.031250000 decreases time[dex] by 0.03125 also prints 0.03125 ingame

set time[dex] = time[dex] - 0.0312500000 decreases time[dex] by 0.222 also prints 0.222 ingame

Maybe the answer for this freakin bug is in here. I didn't read everything.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,188
Maybe the answer for this freakin bug is in here. I didn't read everything.
Most probably a bug with the constant parser breaking once a real is declared past a certain length (internal overflow/underflow?). Only way to know would be to find the constant inside the process memory for both cases.

One must remember that a decimal rational number has no meaning to a computer. Instead it needs to be parsed character by character with some internal state and eventually produces a float out. Due to precision limitations it is possible the internal state overflows/underflows while parsing such a number. The big question is how they could allow such a bug to exist since number parsing is extremely common and most languages offer standard functions that do not break in such a way.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Most probably a bug with the constant parser breaking once a real is declared past a certain length (internal overflow/underflow?). Only way to know would be to find the constant inside the process memory for both cases.

One must remember that a decimal rational number has no meaning to a computer. Instead it needs to be parsed character by character with some internal state and eventually produces a float out. Due to precision limitations it is possible the internal state overflows/underflows while parsing such a number. The big question is how they could allow such a bug to exist since number parsing is extremely common and most languages offer standard functions that do not break in such a way.

Because the engine was built for users who would only do two decimals of precision. There was no user-JASS when the game was released. 9 decimals of precision is all the game can handle. Notice in the Blizzard.j script that even the developers never bothered with more than 5 points of precision? It's not that important.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I thought of something.

You have the literals right? well lets say 3.14159365..., now if we use 9 numbers, we get like, pretty good value right? I thought that maybe the freaking out happens because the engine just parses the number to the 32bit representation, but then somehow shifts them as he reads too many values? so it like, shifts the whole 32 bits to left. I cant test it right now tho.

so like if I read 2.22221 I get 2.22221 just fine, but if I put more numbers to there, it will start shifting the floating to fit in all those numbers resulting in garbage.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Can't reply to the real Talk - Floats in Warcraft 3 thread so...

A NaN doesn't seem to exist at all, since division by zero causes a thread crash and therefore termination of the function trying to compute a NaN.

It seems to exist, although am not sure if there are false positives for the is_nan function:

JASS:
library nan initializer init
    //# +nosemanticerror
    private function int_to_real takes integer i returns real
        return i
        // return i // prevent jasshelper from inlining
    endfunction
    //# +nosemanticerror
    private function real_to_int takes real r returns integer
        return r
        // return r // prevent jasshelper from inlining
    endfunction

    private function clean_real takes real r returns real
        return r
        return 0.0 // prevent jasshelper from inlining
    endfunction
    private function clean_int takes integer i returns integer
        return i
        return 0 // prevent jasshelper from inlining
    endfunction

    private function i2r takes integer i returns real
        return clean_real(int_to_real(i))
        return 0.0 // prevent jasshelper from inlining
    endfunction
    private function r2i takes real i returns integer
        return clean_int(real_to_int(i))
        return 0 // prevent jasshelper from inlining
    endfunction


    globals
        /*constant*/ real nan
    endglobals
    private function init takes nothing returns nothing
        set nan = i2r(0x7FC00000)
    endfunction

    function is_nan takes real r returns boolean
        return r != 0.0 and r2i(r + r) == -r2i(r)
    endfunction
endlibrary
 
Level 13
Joined
Nov 7, 2014
Messages
571
Might of been for nothing though (sorry about that), but Jass ops don't "respect" nan:

JASS:
function init takes nothing returns nothing
    local real r = 6.28

    call BJDebugMsg("nan: " + I2S(r2i(nan)))
    call BJDebugMsg("r: " + R2SW(r, 0, 9))

    call BJDebugMsg("r + nan: " + I2S(r2i(r + nan)))
    call BJDebugMsg("nan + r: " + I2S(r2i(nan + r)))

    call BJDebugMsg("r - nan: " + I2S(r2i(r - nan)))
    call BJDebugMsg("nan - r: " + I2S(r2i(nan - r)))

    call BJDebugMsg("r * nan: " + I2S(r2i(r * nan)))
    call BJDebugMsg("nan * r: " + I2S(r2i(nan * r)))

    call BJDebugMsg("r / nan: " + I2S(r2i(r / nan)))
    call BJDebugMsg("nan / r: " + I2S(r2i(nan / r)))
endfunction

// output:
// nan: 2143289344
// r: 6.279999712
// r + nan: 2143289344
// nan + r: 2143289344
// r - nan: -4194304
// nan - r: 2143289344
// r * nan: 0
// nan * r: 0
// r / nan: 0
// nan / r: 2121569798
 

MindWorX

Tool Moderator
Level 20
Joined
Aug 3, 2004
Messages
709
I just wanted to add a bit of confirmation to this post.

In version 1.28.0.7205 of game.dll, at address 6FCDF3A0 you can find the following bytes: 3A83126F, which converted to float results in 0.0010000000474974513

This float is used in the virtual machine when it encounters the opcode for equality check and it needs to compare a real.
C:
...
    case OP_EQUAL:
        if ( vm->variable[curop->r2].vartype2 == J_REAL )
        {
          v162 = vm->variable[curop->r2].is_func_arg;
          v156 = vm->variable[curop->r1].is_func_arg;
          LOBYTE(v91) = sub_6F05B190(&v162, &v156); // this is the result
          goto LABEL_115;
        }
    ...
...

C:
bool __fastcall sub_6F05B190(float* a1, float* a2)
{
  float* v3; // [sp+0h] [bp-4h]@1

  v3 = a1;
  return *&dword_6FCDF3A0 > COERCE_FLOAT(*sub_6F06E1C0(&v3, a1, a2) & 0x7FFFFFFF);
}
You can see that it compares to dword_6FCDF3A0 which holds the 0.001... float.
The part on the right of the comparison appears to find the absolute difference between the two values.

Here's the code for not equal comparison.

C:
...
    v95 = vm->variable[curop->r2].is_func_arg == vm->variable[curop0->r1].is_func_arg
    v94 = !v95; // this is the result
...
Which is a much more straight comparison as expected.
 
Last edited:

MindWorX

Tool Moderator
Level 20
Joined
Aug 3, 2004
Messages
709
The 0x7FFFFFFF is just used to cast?
Do you know how sub_6F06E1C0 looks like?
It's an binary & comparison. The result is that it switches the "sign" bit off, which turns the number positive. You can see how floats work here: IEEE-754 Floating Point Converter
This is the correct function yeah. It's peculiar that it's so big, but it might be because they have some additional safety precautions in place to ensure it computes predictably across clients.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,188
This is the correct function yeah. It's peculiar that it's so big, but it might be because they have some additional safety precautions in place to ensure it computes predictably across clients.
The actual function is probably only 4-5 lines. Just the decompiled compiled code is very long and complex.

It will compute predictably as long as a compiler is used and configured to use a strict float format. Much of that code to make it predictable would then be automatically generated.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
I gets automaticaly locked after some months of inacitivty, but then Aniki wanted to share something.

Ah ok, didn't know that. Thanks for the clarification.

According to the NaN behavior: Yes, unfortunatly these numbers (same with Inf) are not treated like they should according to the IEEE standard... back then I hoped I can use Inf with the hidden attacktype to break through ethereal form, but that doesn't work unfortunatly^^
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,188
IEEE standard floats come with a performance penalty, often in the form of interrupts when one of those boarder-line conditions occur. For real time applications it is often good enough to take same precision but less conformant floats which do not have the interrupt conditions and might be less accurate in some areas.
 
Status
Not open for further replies.
Top