Simple math help

Discuss all aspects related to modding Zandronum here.
Post Reply
Catastrophe
Retired Staff / Community Team Member
Posts: 2571
Joined: Sat Jun 02, 2012 2:44 am

Simple math help

#1

Post by Catastrophe » Fri Jul 12, 2013 10:03 am

Can someone please explain to me why

int r = FixedDiv(45.0, 100.0);

returns as 0.44997 instead of 0.45? Is there something I gotta do?

Lollipop
Zandrone
Posts: 1124
Joined: Tue Jul 24, 2012 10:34 am
Location: Denmark

RE: Simple math help

#2

Post by Lollipop » Fri Jul 12, 2013 11:03 am

I can't see why it returns 0.44997, do you have any other stuff that might be changing the outcome of the division and what are you using it for?
Combinebobnt wrote:i can see the forum league is taking off much better than the ctf ones
GalactusToday at 1:07 PM
are you getting uncomfortable jap
feeling something happen down there

Nati46
Forum Regular
Posts: 379
Joined: Mon Jun 04, 2012 11:39 am
Location: Ramat Aviv

RE: Simple math help

#3

Post by Nati46 » Fri Jul 12, 2013 11:52 am

Isn't it supposed to be "double" instead of int? Excuse my basic knowledge of programming
Want to know how to run faster? Check this out!

Zandronum Duel Championships

Image

Qent
Retired Staff / Community Team Member
Posts: 1424
Joined: Tue May 29, 2012 7:56 pm
Contact:

RE: Simple math help

#4

Post by Qent » Fri Jul 12, 2013 4:21 pm

Because 0.01 is a repeating binary number, like 1/3 in decimal. No I think that's not why.
Nati46 wrote: Isn't it supposed to be "double" instead of int? Excuse my basic knowledge of programming
ACS uses int for everything.
Last edited by Qent on Fri Jul 12, 2013 4:52 pm, edited 1 time in total.

Synert
Forum Regular
Posts: 228
Joined: Mon Jun 04, 2012 12:54 pm
Contact:

RE: Simple math help

#5

Post by Synert » Fri Jul 12, 2013 4:28 pm

possibly because acs is retarded but hell if I know

Qent
Retired Staff / Community Team Member
Posts: 1424
Joined: Tue May 29, 2012 7:56 pm
Contact:

RE: Simple math help

#6

Post by Qent » Fri Jul 12, 2013 4:49 pm

Never mind, I'm not quite sure. But 0.45 in fixed point is 0.449997 (it's another repeating binary). Did you forget a "9"?
Last edited by Qent on Fri Jul 12, 2013 4:55 pm, edited 1 time in total.

Ijon Tichy
Frequent Poster Miles card holder
Posts: 901
Joined: Mon Jun 04, 2012 5:07 am

RE: Simple math help

#7

Post by Ijon Tichy » Fri Jul 12, 2013 4:56 pm

It's because of how the format is stored. Everything in completing land is made of powers of two. Fixed point and floating point numbers as stored as 0.5 ± 0.25 ± 0.125 ± 0.0625... for as many bits as make up fractional part of the number. 16 bits for the fractional part (as in ACS), 16 iterations of this, with the smallest possible floating point number in ACS being 2^(-16).

0.45 does not go evenly into any power of two, and therefore, an approximation is done. 0.44997 is as close as ACS can get (as an integer, it's 29491).

This is not to say that proper decimal representations of numbers don't exist in the computing world - IBM made a specification for arbitrary-precision decimals that don't have the silliness you see. here (so 9 * 0.1 = 0.9, not 0.8999999999068677). They, however, are slower to use than normal (power of two) numbers (and also aren't integers :V), and as such aren't in ACS.

This is why I much prefer sticking to integer-only math in ACS. The loss of precision that already existed with floating point math is much, MUCH stronger in ACS. Integers don't lose precision like that.

Of course, there's the fact that integer division always rounds; according to C99, towards 0, but since ACS was pre-1999, this might not be true for it. Anyway, there is a way to get around this, with the idea of an 'error' variable. Simply put, remember division in elementary school, before things like fractions were done. When dividing, you found the quotient and the remainder, expressing things like 9/4 as 2r1, and 14/3 as 4r2. Well, the 'error' field uses that remainder.
In C:

Code: Select all

#include <stdio.h>

int main()
{
    int numerator   = 17;
    int denominator = 13;
    int quotient    = 0;
    int error       = 0;
    int result      = 0;

    // a % b  is defined as  a - b(int(a/b))
    //  aka. the modulo operator
    // 15 / 6 = 2;   15 % 6 = 3;   (2 * 6) + 3 = 15;
    // 10 / 6 = 1;   10 % 6 = 4;   (1 * 6) + 4 = 10;
    // 10 / 5 = 2;   10 % 5 = 0;   (2 * 5) + 0 = 10;

    int i;
    for (i = 0; i < 100; i++)
    {
        quotient = numerator / denominator;
        error += numerator % denominator;  // add remainder to error

        // add 1 to result every time error went over denominator
        result += quotient + (error / denominator);
        error %= denominator; // make sure error is under denominator

        printf("%d\n", result); // now what did we get?
    }

    return 0;
}
Don't worry about the int main(), #include, and return.
The point here is, we're keeping track of how 'off' our divisions are in the error variable through use of the modulo operator. When the error field goes over the denominator - here, 13 - we keep adding 1 to the result (which at first is the quotient - here, 1) and subtracting out the denominator until the error is no longer over the denominator. The result is that with absolutely no precision loss anywhere, we have a counter that can handle any fraction and properly handle numbers that don't go evenly into each other.

The output of that C program, by the way:
[spoiler]

Code: Select all

1
2
3
5
6
7
9
10
11
13
14
15
17
18
19
20
22
23
24
26
27
28
30
31
32
34
35
36
37
39
40
41
43
44
45
47
48
49
51
52
53
54
56
57
58
60
61
62
64
65
66
68
69
70
71
73
74
75
77
78
79
81
82
83
85
86
87
88
90
91
92
94
95
96
98
99
100
102
103
104
105
107
108
109
111
112
113
115
116
117
119
120
121
122
124
125
126
128
129
130
[/spoiler]

If you don't see a use for this method, then don't concern yourself with it. But it does come in handy, especially when you want speedy, with no loss of precision. Decimal operations are slower than integer operations almost as a rule. Take integer operations whenever you can.
Last edited by Ijon Tichy on Fri Jul 12, 2013 5:39 pm, edited 1 time in total.

Catastrophe
Retired Staff / Community Team Member
Posts: 2571
Joined: Sat Jun 02, 2012 2:44 am

RE: Simple math help

#8

Post by Catastrophe » Sat Jul 13, 2013 2:11 am

So there is no simple way to move two decimal places to the left so 45 is 0.45?

Ijon Tichy
Frequent Poster Miles card holder
Posts: 901
Joined: Mon Jun 04, 2012 5:07 am

RE: Simple math help

#9

Post by Ijon Tichy » Sat Jul 13, 2013 3:45 am

You already did it. It's just that floating point numbers in general can't handle non-power-of-two fractions.

If you want exactly 0.45, there's no way to get that in ACS. But you can get close.

Watermelon
Zandrone
Posts: 1244
Joined: Thu Jun 28, 2012 9:07 pm
Location: Rwanda

RE: Simple math help

#10

Post by Watermelon » Sat Jul 13, 2013 3:46 am

Sadly no. ACS is unfortunately limited. If you wanted to see what the number is, you could add 1 to the integer you get back and see the next highest decimal point to get a feel for the range you're allowed.

An alternative if you want decimal points of the hundreds, you could multiply everything by 100 so when you divide it you end up with 45 if you're using an arbitrary number.
Last edited by Watermelon on Sat Jul 13, 2013 3:47 am, edited 1 time in total.

Catastrophe
Retired Staff / Community Team Member
Posts: 2571
Joined: Sat Jun 02, 2012 2:44 am

RE: Simple math help

#11

Post by Catastrophe » Sat Jul 13, 2013 4:02 am

It's ok, I just added a bunch of if statements to lower the precision :p

So for future reference.

Don't do if(r == 0.45)

instead do if(r < 0.45 + 0.01 && r > 0.45 - 0.01) since it just needs to be two decimal places correct.

Watermelon
Zandrone
Posts: 1244
Joined: Thu Jun 28, 2012 9:07 pm
Location: Rwanda

RE: Simple math help

#12

Post by Watermelon » Sat Jul 13, 2013 6:04 am

Yep thats pretty good

You can even make a function to make your life possibly easier:

Code: Select all

// Returns 1 for true, 0 for false
// First arg is compared to the 2nd arg +/- the third to see if it is in range
function int equalsApproximately(int fixed_number, int fixed_number_compare, int fixed_deviation)
{
    return (fixed_number < fixed_number_compare + fixed_deviation && fixed_number > fixed_number_compare + fixed_deviation);
}

which in this case you could use

Code: Select all

...
if (equalsApproximately(r, 0.45, 0.01)) { ... }
...
Last edited by Watermelon on Sat Jul 13, 2013 6:04 am, edited 1 time in total.

Catastrophe
Retired Staff / Community Team Member
Posts: 2571
Joined: Sat Jun 02, 2012 2:44 am

RE: Simple math help

#13

Post by Catastrophe » Sat Jul 13, 2013 7:02 am

oh nice, thanks

Post Reply