Simple math help
-
Catastrophe
- Retired Staff / Community Team Member
- Posts: 2571
- Joined: Sat Jun 02, 2012 2:44 am
Simple math help
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?
int r = FixedDiv(45.0, 100.0);
returns as 0.44997 instead of 0.45? Is there something I gotta do?
RE: Simple math help
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
RE: Simple math help
Isn't it supposed to be "double" instead of int? Excuse my basic knowledge of programming
RE: Simple math help
Because 0.01 is a repeating binary number, like 1/3 in decimal. No I think that's not why.
ACS uses int for everything.Nati46 wrote: Isn't it supposed to be "double" instead of int? Excuse my basic knowledge of programming
Last edited by Qent on Fri Jul 12, 2013 4:52 pm, edited 1 time in total.
RE: Simple math help
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
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:
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][/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.
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;
}
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
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
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
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.
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
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.
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
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.
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
Yep thats pretty good
You can even make a function to make your life possibly easier:
which in this case you could use
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
oh nice, thanks
