• 🏆 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!

Fractions

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
JASS:
library Fractions
//===========================================================================
// Created by Bribe. Largely based on the "fractions" module used in Python.
// It allows you to do things like:
//
// call Fraction(1, 2).add(Fraction(3, 2)).print(5.00)
//
// Which displays the message "Fraction(2, 1)" for 5 seconds. "Fractions"
// allows you to do math without the imprecision of real numbers, and auto-
// matically simplifies results ((3, 12) becomes (1, 4)).
//
    
    
    //=======================================================================
    // Returns 0.14 from 3.14. Its purpose is to get the right-side of the
    // decimal point of a real number.
    //
    function GetRemainder takes real r returns real
        return r - R2I(r)
    endfunction

    //=======================================================================
    // Returns 2 for 10; 4 for 1000. Its purpose is to find out how many
    // digits are in a given integer.
    //
    function CountDigits takes integer i returns integer
        return StringLength(I2S(i))
    endfunction

    //=======================================================================
    // Turns 0.15 (with p == 2) into 15. Its purpose is to swap imprecise
    // real numbers into workable integers.
    //
    function PromoteReal takes real r, integer p returns integer
        return R2I(r * Pow(10, p))
    endfunction

    //=======================================================================
    // Turns 15 into 0.15; 165 into 0.165. Its purpose is to reverse the
    // effects of PromoteReal.
    //
    function DemoteInteger takes integer i returns real
        return i / Pow(10, CountDigits(i))
    endfunction

    //=======================================================================
    // Returns true for 1.00, false for 3.14. Its purpose is to determine
    // if a real number is also a rational number.
    //
    function IsRational takes real r returns boolean
        return r * 10000 == 0
    endfunction

    //=======================================================================
    // Rounds 1.2 to 1, 1.7 to 2. Its purpose is to not simply truncate reals
    // vith R2I, but to also round the number if its remainder is higher than
    // 0.50.
    //
    function Real2Int takes real r returns integer
        if r > 0 then
            return R2I(r + 0.50)
        endif
        return R2I(r - 0.50)
    endfunction
    
    //=======================================================================
    // Finds the greatest-common-denominator for a fraction to be simplified
    // into.
    //
    public function GCD takes integer a, integer b returns integer
        local integer a2
        loop
            exitwhen b == 0
            set a2= a
            set a = b
            set b = ModuloInteger(a2, b)
        endloop
        return a
    endfunction
    
    
    struct fraction
        
        readonly integer num // numerator
        readonly integer den // denominator
        
        
        //===================================================================
        // f.reset(1, 2) resets the numerator and denominator to whatever you
        // want.
        //
        method reset takes integer a, integer b returns thistype
            local integer d = GCD(a, b)
            if (d != 0) then
                set num = a / d
                set den = b / d
            else
                set num = 0
                set den = 1
            endif
            return this
        endmethod
        
        //===================================================================
        // f = fraction.create(1, 2) creates a new fraction instance with the
        // first value as the numerator, the second as the denominator.
        //
        static method create takes integer num, integer den returns thistype
            return thistype.allocate().reset(num, den)
        endmethod
        
        //===================================================================
        // f.product() returns 0.50. This is useful when you need a real num-
        // ber as a parameter.
        //
        method product takes nothing returns real
            return (num + 0.) / den
        endmethod
        
        //===================================================================
        // f2 = f.copy() returns a new fraction instance with the same num-
        // erator and denominator as f.
        //
        method copy takes nothing returns thistype
            return thistype.create(num, den)
        endmethod
        
        //===================================================================
        // Fraction(1, 3).add(Fraction(1, 3)) makes Fraction(2, 3).
        //
        method add takes thistype f returns thistype
            return this.reset((this.num * f.den) + (this.den * f.num), (this.den * f.den))
        endmethod
        
        //===================================================================
        // Fraction(2, 3).subtract(Fraction(1, 3)) makes Fraction(1, 3).
        //
        method subtract takes thistype f returns thistype
            return this.reset((this.num * f.den) - (this.den * f.num), (this.den * f.den))
        endmethod
        
        //===================================================================
        // Fraction(1, 2).multiply(Fraction(2, 3)) makes Fraction(1, 3).
        //
        method multiply takes thistype f returns thistype
            return this.reset(this.num * f.num, this.den * f.den)
        endmethod
        
        //===================================================================
        // Fraction(3, 4).divide(Fraction(4, 5)) makes Fraction(15, 16).
        //
        method divide takes thistype f returns thistype
            return this.reset(this.num * f.den, this.den * f.num)
        endmethod
        
        //===================================================================
        // f.eq(f2) returns true if the numerator and denominator are the
        // same for both f and f2.
        //
        method eq takes thistype f returns boolean
            return this.num == f.num and this.den == f.den
        endmethod
        
        //===================================================================
        // f.gt(f2) returns true if f is greater than f2.
        //
        method gt takes thistype f returns boolean
            return this.num * f.den > f.num * this.den
        endmethod
        
        //===================================================================
        // f.lt(f2) returns true if f is less than f2.
        //
        method lt takes thistype f returns boolean
            return this.num * f.den < f.num * this.den
        endmethod
        
        //===================================================================
        // f.print(5.00) displays Fraction(1, 2) for 5 seconds, useful for
        // debugging or for playing around with some math.
        //
        method print takes real duration returns nothing
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.00, 0.00, duration, "Fraction(" + I2S(num) + ", " + I2S(den) + ")")
        endmethod
        
        //===================================================================
        // Turns "10" into Fraction(10, 1). Easy way to convert an integer to
        // a fraction.
        //
        static method fromint takes integer i returns thistype
            return thistype.create(i, 1)
        endmethod
        
        //===================================================================
        // Turns "0.5" (with p==1) into Fraction(1, 2). Easy way to convert a
        // real number into a fraction. "p" is the amount of precision to
        // check on the right side of the floating point. A low "p" produces
        // more simplified fractions than a high "p".
        //
        static method fromreal takes real r, integer p returns thistype
            set p = R2I(Pow(10, p))
            return thistype.create(R2I(r * p), p)
        endmethod
        
        
    endstruct
    
    
    globals
        private timer g_timer = CreateTimer()
        private integer g_size = 0
        private fraction array a_list
    endglobals
    
    //=======================================================================
    // Auto-destroys fraction instances created with "Fraction(1, 2)".
    //
    private function Recycle takes nothing returns nothing
        local integer i = 0
        loop
            call a_list[i].destroy()
            set i = i + 1
            exitwhen i == g_size
        endloop
        set g_size = 0
    endfunction
    
    //=======================================================================
    // Returns a temporary fraction, great for numers you only need for a
    // single instance. f.add(Fraction(1, 2)) is an ideal way to use it.
    //
    function Fraction takes integer num, integer den returns fraction
        local fraction f = fraction.create(num, den)
        set a_list[g_size] = f
        set g_size = g_size + 1
        call TimerStart(g_timer, 0.00, false, function Recycle)
        return f
    endfunction
    
    
endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I'm not sure if there's much use for this, though rounding reals is pretty nice.

Is ZeroShift(10) better than just Pow(10,10)?
I think that ZeroShift would get slower depending on how high the power is.

I think GetRemainder should return an absolute value.

Useage -> this is mostly for the "fractions" struct, which I have yet to write a description for and is still a bit incomplete.

Pow(10,10) still returns a real value, and reals are calculated poorly no matter how you slice it. ZeroShift keeps things integer-based, which is one of the key goals of this library.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Useage -> this is mostly for the "fractions" struct, which I have yet to write a description for and is still a bit incomplete.

Pow(10,10) still returns a real value, and reals are calculated poorly no matter how you slice it. ZeroShift keeps things integer-based, which is one of the key goals of this library.

I think you should go with Pow also : |..

furthermore, the errors in reals come from the rounding >.<. If you keep everything in local scope, it won't round until it gets returned or goes into a global as long as you do things in intervals of 2^32/2.

where x is 2147483647

set x = x * 1000000000
set x = x + 2147483647
(is now 21474836472147483647)

set x = x - 2147483647
set x = x / 1000000000
(is now 2147483647)

The above was from my own testing. Keep in mind the above only works because wc3 engine treats reals as strings at that point : |... this means the operations are insanely slow.
 
JASS:
    function Real2Int takes real r returns integer
        if r > 0 then
            return R2I(r + 0.50)
        else
            return R2I(r - 0.50)
        endif
    endfunction

-->

JASS:
function Real2Int takes real r returns integer
    if r > 0 then
        return R2I(r+.5)
    endif
    return R2I(r-.5)
endfunction

As yournamehere posted. If I remember correctly, it either won't parse or it'll just not work if you don't have a return at the end. ;P
 
Level 8
Joined
Oct 3, 2008
Messages
367
No. Do not use else.

Especially with some older patch versions, not having the return on the last line can be buggy. With a chance of bugs. And if anything it detracts from readability. I'm telling you this for your own health.

And mine.
 
Level 7
Joined
Oct 11, 2008
Messages
304
JASS:
function IsRational takes real r returns boolean
        return S2I(SubString(R2SW(r - R2I(r), 0, 5)), 2, 6) == 0
    endfunction

>>

JASS:
function IsRational takes real r returns boolean
        return S2I(SubString(R2SW(r - R2I(r), 0, 5), 2, 6)) == 0
    endfunction
 
Level 7
Joined
Oct 11, 2008
Messages
304
another :p

JASS:
method product takes nothing returns real
            return this.num / this.den
        endmethod
this don't compile

>>

JASS:
method product takes nothing returns real
            return this.num / this.den + 0.
        endmethod
this compile
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
f.add(Fraction(1, 2)) is a lot cleaner than writing:
JASS:
local fraction f2 = fraction.create(1, 2)
call f.add(f2)
call f2.destroy()

The TriggerSleepAction will not cause any desync unless the user for some totally nuts reason creates more than 8190 instances before the thread expires, in which case the problem is in the user's brain and not in the system itself.

That said, what I have is much cleaner than:

JASS:
    private function Recycle takes fraction f returns nothing
        local timer t = GetExpiredTimer()
        call fraction(GetTimerData(t)).destroy()
        call ReleaseTimer(t)
    endfunction
    
    function Fraction takes integer num, integer den returns fraction
        local fraction f = fraction.create(num, den)
        local timer t = NewTimer()
        call SetTimerData(t, f)
        call TimerStart(t, 0.00, false, function Recycle)
        return f
    endfunction
 
Level 8
Joined
Oct 3, 2008
Messages
367
The cleanliness is certainly arguable.

And TimerUtils is totally unneeded. You can do it with a global timer and two variables or so to make a neat stack in case the user calls Fraction within one thread more than once. Which they probably will.
 
Top