C while loop tricky exceptions [duplicate] - c

This question already has answers here:
Why are floating point numbers inaccurate?
(5 answers)
Closed 5 years ago.
I am a beginner and this is (a simplified version of) part of a code I am writing:
1.prompt user to input a number (float dollar amount), lets say user will only key in amount which are multiples of 0.05 ;
2.calculate the (int)number of nickels in that dollar amount.
int main(void)
{
float amount = 0;
printf("$ ");
amount = get_float();
int n = 0;
while ((amount - 0.05) >= 0)
{
amount -= 0.05;
n ++;
}
printf("%i nickels\n", n);
}
It works for any amount <=0.3 or >=1.2 (so $2 has 40 nickels, and $0.30 has 6 nickels);
but one nickel less for any amount between/including 0.35 and 1.15 (so $0.35 has 6 nickels, and $1 has 19 nickels)
I did step by step printing, and it turns out that for any amount between 0.35 and 1.15, the program stops at the last 0.050000, instead of going back to the loop and loop it one last time.
What went wrong? Please help... Thanks so much for your time!

You are hitting up against floating point rounding.
Floating points are never exactly accurate, try calculating
printf("one third 'ish' = %6.20lf\n", (double) 1.0/ (double) 3.0);
Look at the answer, it's not quite accurate. :)
Your best bet is to accept the input, convert it to an integer (ie, cents) by multiplying by 100, removing any decimals using round() and assigning to a long.
#include <math.h> // to get the round() function
.
.
.
long amountInCents = round(amount * 100);
Then, change your code to compare against 5 cents, not $0.05, and to reduce by 5 cents, not $0.05
This way, you get rid of all 'rounding' issues.
EDIT: cmaster showed me that using floor() didn't work, using $0.57 as input, when using 'double' variables (not float), $0.57 converted to (long) 56! He supplied code and I couldn't believe my eyes!

The is no bug in your code in the strictest sense as the underlying reasoning is correct; however note that the value 0.35 cannot be represented exactly in the float datatype, the same holds for 0.05; as the division (what basically is the sematics of the code in the question) is implemented via repeated subtraction, the error caused by the inability to exactly represent the desired values accumulates. The result is that the number of subtractions is different from the analytically expected result, as the termination condition is reached too soon. The float datatype is not suitable for financial calculations, even in such a very small everyday example.
This is terrible, but it is a matter of fact.

I found a fix. This is working
enter code here
while ((amount - 0.0499) >= 0)
{
amount -= 0.05;
n ++;
}
while scanning .35 amount as float the actual amount value is 0.3499

Related

Taylor Series to calculate cosine (getting output -0.000 for cosine(90))

I have written the following function for the Taylor series to calculate cosine.
double cosine(int x) {
x %= 360; // make it less than 360
double rad = x * (PI / 180);
double cos = 0;
int n;
for(n = 0; n < TERMS; n++) {
cos += pow(-1, n) * pow(rad, 2 * n) / fact(2 * n);
}
return cos;
}
My issue is that when i input 90 i get the answer -0.000000. (why am i getting -0.000 instead of 0.000?)
Can anybody explain why and how i can solve this issue?
I think it's due to the precision of double.
Here is the main() :
int main(void){
int y;
//scanf("%d",&y);
y=90;
printf("sine(%d)= %lf\n",y, sine(y));
printf("cosine(%d)= %lf\n",y, cosine(y));
return 0;
}
It's totally expected that you will not be able to get exact zero outputs for cosine of anything with floating point, regardless of how good your approach to computing it is. This is fundamental to how floating point works.
The mathematical zeros of cosine are odd multiples of pi/2. Because pi is irrational, it's not exactly representable as a double (or any floating point form), and the difference between the nearest neighboring values that are representable is going to be at least pi/2 times DBL_EPSILON, roughly 3e-16 (or corresponding values for other floating point types). For some odd multiples of pi/2, you might "get lucky" and find that it's really close to one of the two neighbors, but on average you're going to find it's about 1e-16 away. So your input is already wrong by 1e-16 or so.
Now, cosine has slope +1 or -1 at its zeros, so the error in the output will be roughly proportional to the error in the input. But to get an exact zero, you'd need error smaller than the smallest representable nonzero double, which is around 2e-308. That's nearly 300 orders of magnitude smaller than the error in the input.
While you coudl in theory "get lucky" and have some multiple if pi/2 that's really really close to the nearest representable double, the likelihood of this, just modelling it as random, is astronomically small. I believe there are even proofs that there is no double x for which the correctly-rounded value of cos(x) is an exact zero. For single-precision (float) this can be determined easily by brute force; for double that's probably also doable but a big computation.
As to why printf is printing -0.000000, it's just that the default for %f is 6 places after the decimal point, which is nowhere near enough to see the first significant digit. Using %e or %g, optionally with a large precision modifier, would show you an approximation of the result you got that actually retains some significance and give you an idea whether your result is good.
My issue is that when i input 90 i get the answer -0.000000. (why am i getting -0.000 instead of 0.000?)
cosine(90) is not precise enough to result in a value of 0.0. Use printf("cosine(%d)= %le\n",y, cosine(y)); (note the e) to see a more informative view of the result. Instead, cosine(90) is generating a negative result in the range [-0.0005 ... -0.0] and that is rounded to "-0.000" for printing.
Can anybody explain why and how i can solve this issue?
OP's cosine() lacks sufficient range reduction, which for degrees can be exact.
x %= 360; was a good first step, yet perform a better range reduction to a 90° width like [-45°...45°], [45°...135°], etc.
Also recommend: Use a Taylor series with sufficient terms (e.g. 10) and a good machine PI1. Form the terms more carefully than pow(rad, 2 * n) / fact(2 * n), which inject excessive error.
Example1, example2.
Other improvements possible, yet something to get OP started.
1 #define PI 3.1415926535897932384626433832795

How to ensure that floating-point variables entered with 2 decimal places retain their exact value

Is there any way to ensure that a floating-point variable entered by the user, having 2 decimal places after the decimal point, retains its exact value, rather than losing precision?
This is the example case:
I want to round a float with 50 numbers after radix point, like this
Before rounding = 0.70999997854232788085937500000000000000000000000000
to this:
After rounding = 0.71000000000000000000000000000000000000000000000000
I became confused when I wanted to compare a float number in a condition like this:
== Program Start==
Input : 0.71
/* program logic */
if (input == 0.71) {
printf("True !");
} else {
printf("False !");
}
Output : False !
==Program End==
The output was False ! and will always be False ! because the true value of user's input is 0.70999997854232788085937500000000000000000000000000, and not 0.71000000000000000000000000000000000000000000000000
Is there any way to round a float value like that? I read about the potential for inaccuracies with floating point here:
Why Are Floating Point Numbers Inaccurate?
and followed it to these links: Is there a function to round a float in C or do I need to write my own?
and Rounding Number to 2 Decimal Places in C
However, these don't answer my question. If I use ceilf(input * 100) / 100 function, it will make the input 0.71 into 0.71000 when printf()d using %.5f format - which seems to work. But when I print with %.50f, the real input value appears as 0.709999978542327880859375. So, I can't compare to that value in my condition.
So, if floating point can never be accurate, what is the trick for the program logic above to get a true return value at that condition?
All of the user's input comes in as text. The easiest -- and, possibly, safest -- way to check it is to compare it as a string before even converting to number:
if (strcmp("0.71", input) == 0) {
printf("True !");
} else {
printf("False !");
}
Of course, this may not be acceptable to you, if you wish to check for something other than equality :)
The other point is that you are interested in fixed, rather than floating numbers here -- but are confusing them with actual integers. If hundredths is what you'd like to work with, then work with the integer numbers of hundredths... For example, consider money -- instead of using float (or double) to store the amounts of dollars (like $7.61), use int (or, god bless you, long) to store cents (like ¢761).
Similarly, for another example, if you are counting time and need precision of one thousandth of a second, then use integer numbers of milliseconds, instead of floating-point number of seconds.
This will sidestep your entire problem altogether and may also make your programs faster...
I'm sure this question has been answered before, however:
float rounded_down = floorf(input * 100) / 100; /* Result: 0.70 */
float nearest = roundf(input * 100) / 100; /* Result: 0.71 */
float rounded_up = ceilf(input * 100) / 100; /* Result: 0.71 */
The library this functions belongs I think is math.h.
You can test equality with a range as you've experienced.
Just rewrite your equality test with:
if(input <= .71 + EPSILON && input >= .71 - EPSILON) {
}
Here you can create your own EPSILON based on your tolerance, or you can perhaps use FLT_EPSILON from #include<cfloat> for C++ or #include <float.h> in C.
Recall that floats do not have 50 digits of decimal precision. So trying to round at the 50th decimal place isn't going to work out so well. Floats have about 6 (sometimes more) digits of precision.

adding numbers in a for loop [duplicate]

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 6 years ago.
I tried to sum some numbers in a for loop but it didn't go as I expected
float sum = 0;
int i;
printf("0.1+0.1=%f\n", 0.1 + 0.1);
for (i = 0; i<1000000; i++)
{
sum = sum + 0.1;
}
printf("the sum need to be 100000 \n");
printf("the real sum is:\n %f\n", sum);
system("PAUSE");
this program prints:
0.1+0.1=0.200000
the sum need to be 100000
the real sum is:
100958.343750
Press any key to continue . . .
can you explain please this strange result?
the international standard for floating point numbers does not have an exact representation for some decimal numbers.
http://en.wikipedia.org/wiki/IEEE_754
It is due to the way they are stored in memory, the way the mantissa and exponent are stored.
https://en.wikipedia.org/wiki/Floating_point
This is also the reason why you should never compare two float numbers even if they look "the same".
I still remember how surprised I was the fist time a simple code comparing two float numbers didn't work :) This alone would open a dedicated universe of discussions. It is very worth reading anyway:
http://floating-point-gui.de/errors/comparison/
The floating numbers are stored in memory as x*2^y where x is between 0 and 1 with some precision and y is integer and so they accurately don't represent most numbers, they represent numbers "close enough".
When you do this addition multiple times, the error is just more visible.
You can use double type for better accuracy.

Why is this do while loop not working properly? [duplicate]

This question already has answers here:
Comparing currency values as floats does not work
(4 answers)
Closed 7 years ago.
I want it to ask user for a float input to assign to money such as a dollar. The smallest unit would be a cent or 0.01 so I want it to reprompt the user for input every time he enters a negative value or zero. The while condition seems fine. What is wrong with it?
#include <stdio.h>
#include <cs50.h>
#include <math.h>
int main(void)
{
float amount = 0;
do
{
printf("How much change to be returned?\n");
amount = GetFloat();
}
while(amount < 0.01);
}
Sample run:
jharvard#appliance (~/Dropbox/pset1): ./greedy
How much change to be returned?
0.01
How much change to be returned?
As you can see when I enter 1 cent it repeats the loop. If I enter 0.02 it works but I want to know why it's doing this.
Floats definition doesn't make them a good type for comparisons.
This is probably duplicate of similar topic
while loop does not terminate - float arithmetic
Previous answers are correct - float values cannot be represented exactly in a binary format. So you should always compare float number with some value of error:
Change the line
while(amount < 0.01)
To
while(amount - 0.01 < EPS)
Where the EPS constant is defined as (put any number that is small enough to be insignificant)
const float EPS = 1e-5;
0.01 Cannot be represented exactly in a float variable, therefore I conclude that in your case the input of 0.01 is converted to a float value bigger than the 0.01 constant and consequently the loop is not terminating for input of 0.01.
To fix this you can switch to a full blown currency or fixed point datatype or more simply change the program to work with cents instead of dollars, thereby avoiding fractional values and especially those of them that cannot be represented exactly. Another often used technique from Kirhog's answer is to use an Epsilon in you comparisons, e.g.:
double EPSILON = 0.005;
...
while(amount < 0.01+EPSILON);

In C, why is the ratio 10.0/100 different from 0.1? [duplicate]

This question already has answers here:
Why Floating point numbers cant be compared? [duplicate]
(7 answers)
Closed 7 years ago.
This is a simple question and I searched the forums, but couldn't find an answer (I found one about Log but I don't think there is a rounding error here).
I wrote a program to determine the value of a fine for a range of expired products, but when the ratio is exact, the program will return the next fine category, ignoring the = sign in the conditional.
The program must return the following fines:
0 if no product is expired.
100 if up to 10% of products are expired.
10000 if more than 10% of products and up to 30% are expired.
100000 if more than 30% of products are expired.
This is the code I wrote:
#include <stdio.h>
int calculate_fine(int ncheckedproducts, int nexpiredproducts)
{
int fine;
float ratio;
ratio=(float)nexpiredproducts/ncheckedproducts;
if(nexpiredproducts==0)
fine=0;
else
if(ratio<=0.1)
fine=100;
else
if(ratio<=0.3)
fine=10000;
else
fine=100000;
return fine;
}
int main(void)
{
int ncheckedproducts, nexpiredproducts, fine;
printf("Type number of checked and expired products\n");
scanf("%d %d", &ncheckedproducts, &nexpiredproducts);
fine=calculate_fine(ncheckedproducts, nexpiredproducts);
printf("The fine is: %d\n", fine);
return 0;
}
But for values of 100 and 10, and 100 and 30, exactly 10% and 30% of expired products respectively, the program will return the wrong fine.
The teacher failed to explain me why, and corrected me to the following function:
int calculate_fine(int ncheckedproducts, int nexpiredproducts)
{
int fine;
if(nexpiredproducts==0)
fine=0;
else
if(nexpiredproducts<=0.1*ncheckedproducts)
fine=100;
else
if(nexpiredproducts<=0.3*ncheckedproducts)
fine=10000;
else
fine=100000;
return fine;
}
However, I wish to know why the first 10% ratio is greater than 0.1, and why I cannot use this approach.
This most probably is a rounding issue, but a different than you might think: Many finite decimal fractions do not have a finite binary fraction representation. Thus, some rounding the the closest number representable as a floating point number of the given type happens.
What you're dealing with is are the fine-grained aspects of floating point numbers:
Computers save floating point numbers in a binary format. Your float is probably a IEEE-754 single precision floating point number.
In those formats, you can typically represent only numbers exactly that are only a sum of a very limited amount of powers of two; for example, 0.75 is 2-1 + 2-2 and can hence be exactly reconstructed. In your case, you try to divide 100 = 26 + 25 + 22 by 3 = 21+20 and hope you get exactly the same result as 0.3 = 2-2+ 2-5+ 2-9+ 2-10+ 2-13+ ...
That won't happen.

Resources