3

hello i am writing basic calculator. everytime i use 90 degrees on cos function it gives -0 as result

    int deg;
    float rad,result;
    printf("\ndegree\n");
    scanf("%d",&deg);
    rad = deg * (M_PI/180);
    result=cos(rad);
    printf("\nresult= %f",result);

result

i dont even know what to try. i just googled it and did not see any similar results.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • 1
    result is correct. what is the problem? – stark Dec 01 '22 at 12:46
  • There is very little difference between the floats `0.0` and `-0.0`, so it's fine. – unwind Dec 01 '22 at 12:48
  • 4
    The likeliest explanation is that the conversion to radians gives a value just a tiny wee bit greater than pi/2, so the angle moves into the second quadrant, where cosines have negative values. – Adrian Mole Dec 01 '22 at 12:50
  • Which is weird to you? That the floating-point number model provides for negative zero, or that that's the result of your computation? – John Bollinger Dec 01 '22 at 12:55
  • This is a surprising result, but it turns out there's no way to fix it. You're going to have to live with it, and learn from it. Once you learn enough from it you'll realize that it's not even "wrong". – Steve Summit Dec 01 '22 at 13:02
  • The fundamental problem is that it is just not possible to compute the cosine of 90.0000000000 degrees, because you are never going to be able to represent the value π/2 perfectly accurately in radians. You're inevitably always going to be working with the equivalent of 89.9999999999 degrees, or 90.0000000001 degrees, so to speak. And when the cosine ends up being -0.0000000001, `printf` is going to round it to -0, because that's a thing in floating point. – Steve Summit Dec 01 '22 at 13:04
  • To more clearly see what's going on, just change `result = %f` to `result = %.20f` in the last line. On my machine, I get `result = -0.00000004371138828674`. (And after seeing how far off the result actually is, you might want to change `float` to `double`.) – Steve Summit Dec 01 '22 at 13:10
  • `scanf("%d", &deg)` without checking return code -- if the user entered something that is not a number, `deg` remains uninitialized and your program runs into undefined behavior. – DevSolar Dec 01 '22 at 13:26

5 Answers5

4

M_PI is defined as 3.141593... which is slightly over PI, consequently, the cos(90.xxx) is lesser than 0.

If you try with 3.1415926, you will get positive result: https://onlinegdb.com/7MWNEkkqI

None of those two values match the real PI value, and they might even be defined differently on different compilers. The point is that having one above the real PI and the other below the real PI make them to fall in different quadrants, and a different sign on the result.

The float being represented by 32bit, it is not possible to represent exactly most of the real numbers (except those few ~2^32 values). And going to double will not solve this.

At the end, it is the function converting the number to a string for representation on the screen who can detect that "-0" and write "0" instead. That is why if you open most applications, you don't get "-0" very often.

The solution is to have the "print" (note, that this is not necessarily the official printf ) which is aware of the number of relevant bits, and can convert this -0.0000000x to 0; 0.9999999x to 1, etc.. Modern "print" functions will provide a mechanism to set the precision (for example std::setprecision in C++). Note: rounding the value will not work with very big or small numbers.

Adrian Maire
  • 14,354
  • 9
  • 45
  • 85
  • @AdrianMole Yes well never mind, I meant that `1415926` is slightly less than `14159265`. Anyway, not important. – Lundin Dec 01 '22 at 13:01
  • You're right, but `M_PI` is not standard (although a common non-standard addition to math.h) and so "is defined as 3.141593" may or may not be true with any particular compiler/stdlib. For example, It's defined as 3.14159265358979323846 with glibc assuming `__USE_MISC` is set. – Paul Hankin Dec 01 '22 at 13:02
  • @PaulHankin And `3.14159265358979323846` gives -0.00 on gcc/glibc – Lundin Dec 01 '22 at 13:04
  • I just added a comment to avoid the confusion. I hope it helps – Adrian Maire Dec 01 '22 at 13:09
  • @AdrianMaire thanks. I don't think there was any confusion, but C is nuanced and I think your answer is better with the disclaimer because I genuinely believe it's better for people to have to face the nuance if they're going to program in C. – Paul Hankin Dec 01 '22 at 13:12
  • @PaulHankin Absolutely right, and even when presented with them, people often manage to ignore the nuances at first. For example, at the moment the OP seems happy with the idea of changing the value of π slightly, so that cos(π/2) comes out slightly positive, so the "problem" of seeing -0 is "fixed". – Steve Summit Dec 01 '22 at 13:30
  • `M_PI` is not defined as `3.141593`. – Eric Postpischil Dec 01 '22 at 13:42
  • 1
    @EricPostpischil: It really does not matter: it value is over the exact PI value. – Adrian Maire Dec 01 '22 at 13:47
  • @AdrianMaire: It does matter. Stack Overflow is intended to be a durable repository of questions and answers for future readers. Answers will not always be used in ways specific to the question asked, and giving false information will mislead future readers. And there is no reason to give this false information. – Eric Postpischil Dec 01 '22 at 13:51
  • 1
    (commenting here because it's the accepted answer, at the moment) If you believe you have solved a problem by *changing the value of π*, alarm bells ought to be going off in your head, on several levels! :-) – Steve Summit Dec 01 '22 at 13:58
3

Other answers have suggested changing the value of pi slightly.
Other answers have suggested changing type float to type double.
Both of these suggestions move the problem around slightly, perhaps changing the objectionable displayed value of -0 to plain 0.
(And switching from float to double is almost always a good idea, no matter what.)
But none of these suggestions actually "solve" this particular "problem", because fundamentally there is no actual problem here.

The real issue, as I said in a comment, is that it is just not possible to compute the cosine of 90.0000000000 degrees, at all, because you are never going to be able to represent the value π/2 perfectly accurately in radians. You're inevitably always going to be working with the equivalent of 89.9999999999 degrees, or 90.0000000001 degrees, so to speak. That is, the problem isn't that cos() is computing the wrong value; the problem is that you're not even passing it the "right" value to begin with! And when π/2 comes out a little bit over, meaning that cos() ends up computing a value like -0.0000000001, a high-quality version of printf is going to round and display it as -0, because -0 is a thing in computer floating point.

If you have a "fixed" version of the original program that no longer displays cos(90) as -0, I suggest trying it with cos(-90), or cos(270) — I bet one or the other of those will display -0, so you're right back out of the frying pan and into the fire again.

If you have a requirement that says you must never display "-0", I believe you would want to pursue a completely different approach, perhaps something along the lines of

char tmp[50];
snprintf(tmp, sizeof(tmp), "%f", result);
if(*tmp == '-' && atof(tmp) == 0) result = -result;
printf("result = %f\n", result);

It may seem strange to be tinkering around with the string representation like this, but it actually makes sense, because it's only after converting (and perhaps rounding) to a string representation that we can be sure we've got the "-0.000000" case that needs special attention.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
2

If you use %g instead of %f, you would see that the result is not exactly 0, but a very small, negative value. Hence the minus sign with %f.

Now, for a more accurate result, you should use the type double instead of float for the variables rad and result (cos already takes a double and returns a double). The sign will be positive, but the result will still not be 0 exactly. As π/2 is irrational, there is no way to get an exact 0 with the cos function (unless its implementation is buggy).

The next C standard (C23) will include a cospi function (as recommended by the IEEE 754 standard), which could solve your issue as it is defined as cospi(x) = cos(πx). So, for 90 degrees, you would call cospi with the argument 0.5, which is exactly representable.

EDIT: Some implementations may be tempted to hide the issue by guessing what the result should be, such as assuming that if the cos argument is very close to π/2, then it is regarded as π/2 exactly, so that an exact 0 is returned. This is a bad idea (in particular for generic libraries like the C library), which could yield surprising results. Even user code should be careful. See this video to see possible consequences of such hacks on a Casio calculator.

vinc17
  • 2,829
  • 17
  • 23
  • I had not heard of this new `cospi` function! Thanks for the tip. (I wonder how long it will take for the "cosplay" jokes to start...?) – Steve Summit Dec 01 '22 at 16:02
0

cos(90) is exactly 0. Purely mathematically, 0 is equal to -0. I assume that the problem is the minus sign?

M_PI is of type double, with the value 3.14159265358979323846, which is slightly less than true pi.

Converting it to a float makes it 3.1415927410125732421875, which is slightly more than true pi. The calculations could be done in type double, and converted to float afterwards? That code would of course be slightly less efficient, but depending on the use case, it would probably not matter, and the risk for any similar errors would be minimized.

EDIT: I just realized M_PI exists as a float too, depending on the library, as previous answer states. Both ways should solve the problem.

hakuni
  • 57
  • 7
  • yes my problem was minus sign and changing pi value solved my issue. normally it doesnt matter but it is school project and im sure teacher would break couple points for that – emircan lalecan Dec 01 '22 at 13:21
  • 1
    Changing the value of pi slightly might have "solved" your "problem" for 90 degrees, but now try -90, or 270 — I bet the -0 value will come back somewhere else. It turns out there is *nothing wrong* with that -0 value, and if your teacher takes off points for it, your teacher is just wrong. – Steve Summit Dec 01 '22 at 13:26
0

everytime i use 90 degrees on cos function it gives -0 as result

Rounded text

The result is not -0 but a value between 0.0 and -0.0000005 that when printed using "%f" (in the form d.dddddd) OP saw a rounded value as "-0.000000".

To see a more informative output, use "%g".

printf("\nresult= %g",result);
// result= -4.37114e-08

Related graphic


Why -4.37114e-08?

rad = deg * (M_PI/180); attempts to convert degrees to radians using an approximation to π. π is irrational. All finite floating point values are rational and so M_PI is never exactly π, regardless how precise the floating point type. Thus rad = deg * (M_PI/180); introduces small errors that are magnified in cos(rad) when it performs it argument range reduction.

There is an alternative. Perform the argument range reduction in degrees, which can be exact, scale and then call cos().

#include <math.h>
#include <stdio.h>

#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
#endif

static double d2r(double d) {
  return (d / 180.0) * ((double) M_PI);
}

double cosd(double x) {
  if (!isfinite(x)) {
    return cos(x);
  }
  int quo;
  double d_45 = remquo(fabs(x), 90.0, &quo);
  // d_45 is in the range [-45...45]
  double r_pidiv4 = d2r(d_45);
  switch (quo % 4) {
    case 0:
      return cos(r_pidiv4);
    case 1:
      // Add 0.0 to avoid -0.0
      return 0.0 - sin(r_pidiv4);
    case 2:
      return -cos(r_pidiv4);
    case 3:
      return sin(r_pidiv4);

  }
  return 0.0;
}

Test

int main(void) {
  int prec = DBL_DECIMAL_DIG - 1;
  for (int d = -360; d <= 360; d += 15) {
    double r = d2r(d);
    printf("cos (%6.1f degrees) = % .*e\n", 1.0 * d, prec, cos(r));
    printf("cosd(%6.1f degrees) = % .*e\n", 1.0 * d, prec, cosd(d));
  }
  return 0;
}

Ouput

cos (-360.0 degrees) =  1.0000000000000000e+00
cosd(-360.0 degrees) =  1.0000000000000000e+00
...
cos (-270.0 degrees) = -1.8369701987210297e-16
cosd(-270.0 degrees) =  0.0000000000000000e+00  // Exactly zero
...
cos (   0.0 degrees) =  1.0000000000000000e+00
cosd(   0.0 degrees) =  1.0000000000000000e+00
...
cos (  60.0 degrees) =  5.0000000000000011e-01 // Not 0.5
cosd(  60.0 degrees) =  4.9999999999999994e-01 // Not 0.5, yet closer
...
cos (  90.0 degrees) =  6.1232339957367660e-17
cosd(  90.0 degrees) =  0.0000000000000000e+00  // Exactly zero, OP's goal
...
cos ( 270.0 degrees) = -1.8369701987210297e-16
cosd( 270.0 degrees) =  0.0000000000000000e+00  // Exactly zero
...
cos ( 360.0 degrees) =  1.0000000000000000e+00
cosd( 360.0 degrees) =  1.0000000000000000e+00
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256