# Fractions

Discussion in 'Graveyard' started by Bribe, Oct 3, 2010.

1. ### Bribe

Joined:
Sep 26, 2009
Messages:
8,059
Resources:
25
Maps:
3
Spells:
10
Tutorials:
3
JASS:
9
Resources:
25
Code (vJASS):

library Fractions
//===========================================================================
// Created by Bribe. Largely based on the "fractions" module used in Python.
// It allows you to do things like:
//
//
// 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

//===================================================================
// 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: Nov 8, 2010
2. ### watermelon_1234

Joined:
Nov 18, 2007
Messages:
1,066
Resources:
10
Spells:
9
JASS:
1
Resources:
10
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.

Joined:
Oct 16, 2008
Messages:
9,674
Resources:
23
Models:
2
Packs:
1
Maps:
1
Spells:
16
Tutorials:
1
JASS:
2
Resources:
23
wouldn't this return -.14? since it will be 3 - 3.14
Code (vJASS):

//=======================================================================
// Returns 0.14 from 3.14.
//
function GetRemainder takes real r returns real
return R2I(r) - r
endfunction

4. ### Bribe

Joined:
Sep 26, 2009
Messages:
8,059
Resources:
25
Maps:
3
Spells:
10
Tutorials:
3
JASS:
9
Resources:
25
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.

5. ### Nestharus

Joined:
Jul 10, 2007
Messages:
6,146
Resources:
8
Spells:
3
Tutorials:
4
JASS:
1
Resources:
8
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.

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.

6. ### Bribe

Joined:
Sep 26, 2009
Messages:
8,059
Resources:
25
Maps:
3
Spells:
10
Tutorials:
3
JASS:
9
Resources:
25
I am particularly concerned about whether a designated integer-variable syntaxes properly as a parameter in Pow. Pow(10, i), whereas i is an integer...

7. ### Nestharus

Joined:
Jul 10, 2007
Messages:
6,146
Resources:
8
Spells:
3
Tutorials:
4
JASS:
1
Resources:
8
Let your worries be released; it does.

8. ### xD.Schurke

Joined:
Feb 22, 2006
Messages:
948
Resources:
5
Maps:
1
Spells:
4
Resources:
5
your real to int can be done shorter:

Code (vJASS):

function RealToInt takes real r returns integer
return R2I(r+0.5)
endfunction

9. ### YourNameHere

Joined:
Apr 29, 2007
Messages:
745
Resources:
4
Maps:
1
Spells:
2
JASS:
1
Resources:
4
Code (vJASS):
function RealToInt takes real r returns integer
if r > 0 then
return R2I(r+.5)
endif
return R2I(r-.5)
endfunction

10. ### azlier

Joined:
Oct 3, 2008
Messages:
354
Resources:
4
JASS:
4
Resources:
4
It depends on the type of rounding you want, really. But yeah, that's the one for the round function most people have in mind.

11. ### xD.Schurke

Joined:
Feb 22, 2006
Messages:
948
Resources:
5
Maps:
1
Spells:
4
Resources:
5
true, only thought about positive values

12. ### Bribe

Joined:
Sep 26, 2009
Messages:
8,059
Resources:
25
Maps:
3
Spells:
10
Tutorials:
3
JASS:
9
Resources:
25
Thank you all for the suggestions. I have finally found the time to add a description to each function/method so someone can follow my thinking.

### Code Moderator

Joined:
Nov 11, 2006
Messages:
7,426
Resources:
18
Icons:
1
Spells:
4
Tutorials:
9
JASS:
4
Resources:
18
Code (vJASS):
function Real2Int takes real r returns integer
if r > 0 then
return R2I(r + 0.50)
else
return R2I(r - 0.50)
endif
endfunction

-->

Code (vJASS):
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

14. ### Bribe

Joined:
Sep 26, 2009
Messages:
8,059
Resources:
25
Maps:
3
Spells:
10
Tutorials:
3
JASS:
9
Resources:
25
It syntaxes correctly, try it for yourself

That's one of the few intelligent things about war3's syntax checker: it knows that the "else" block is valid for a final return statement.

15. ### YourNameHere

Joined:
Apr 29, 2007
Messages:
745
Resources:
4
Maps:
1
Spells:
2
JASS:
1
Resources:
4
It's better performance-wise without an else. Not that it would make any difference since JASS is pretty slow anyway .

16. ### Bribe

Joined:
Sep 26, 2009
Messages:
8,059
Resources:
25
Maps:
3
Spells:
10
Tutorials:
3
JASS:
9
Resources:
25
An 'else' as a performance hit? That would have to be the smallest performance hit in recorded history.

17. ### azlier

Joined:
Oct 3, 2008
Messages:
354
Resources:
4
JASS:
4
Resources:
4
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.

18. ### xD.Schurke

Joined:
Feb 22, 2006
Messages:
948
Resources:
5
Maps:
1
Spells:
4
Resources:
5
no it won't bug, since a boolean only can be true/false so one of those two if cases will be called :O

19. ### Bribe

Joined:
Sep 26, 2009
Messages:
8,059
Resources:
25
Maps:
3
Spells:
10
Tutorials:
3
JASS:
9
Resources:
25
Aye, as I have not toyed with previous versions of war3 JASS, I cannot argue the chance it may have bugged in the past. Fixed for everyone's benefit.

20. ### Laiev

Joined:
Oct 11, 2008
Messages:
273
Resources:
1
Template:
1
Resources:
1
Code (vJASS):
function IsRational takes real r returns boolean
return S2I(SubString(R2SW(r - R2I(r), 0, 5)), 2, 6) == 0
endfunction

>>

Code (vJASS):
function IsRational takes real r returns boolean
return S2I(SubString(R2SW(r - R2I(r), 0, 5), 2, 6)) == 0
endfunction