0

The problem at hand arises from the execution of the following code which constitutes example 16.15 (Chapter 16: The Preprocessor and the C Library) in Stephen Prata's book "C Primer Plus" (6th Edition). In what follows, I undertook the utmost care in copying the code as-is from the hardcopy.

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

#define RAD_TO_DEG (180/(4 * atanl(1))) 

// generic square root function
#define SQRT(X) _Generic((X),\
    long double: sqrtl,\
    default: sqrt,\
    float: sqrtf)(X)

// generic sine function, angle in degrees
#define SIN(X) _Generic((X),\
    long double: sinl((X)/RAD_TO_DEG),\
    default:     sin((X)/RAD_TO_DEG),\
    float:       sinf((X)/RAD_TO_DEG)\
) 

/*
 * 
 */
int main(void) 
{
    float x = 45.0f;
    double xx = 45.0;
    long double xxx = 45.0L;

    long double y = SQRT(x);
    long double yy = SQRT(xx);
    long double yyy = SQRT(xxx);

    printf("%.17Lf\n", y);   // matches float
    printf("%.17Lf\n", yy);  // matches default
    printf("%.17Lf\n", yyy); // matches long double

    int i = 45;
    yy = SQRT(i);            // matches default
    printf("%.17Lf\n", yy);

    yyy = SIN(xxx);          // matches long double
    printf("%.17Lf\n", yyy);

    return 0;
}

When I build/compile/run the following results are printed,

0.00000000000000000
0.00000000000000000
0.00000000000000000
0.00000000000000000
0.00000000000000000

as opposed to the results tabulated in the book:

6.70820379257202148
6.70820393249936942
6.70820393249936909
6.70820393249936942
0.70710678118654752

The only(?) way to approximate most of the aforementioned results is to typecast y, yy, and yyy variables in the printf statements (save for the third one) as follows:

printf("%.17Lf\n", (float)y);   // matches float
printf("%.17Lf\n", (double)yy);  // matches default (double)
printf("%.17Lf\n", yyy); // matches long double

int i = 45;
yy = SQRT(i);            // matches default
printf("%.17Lf\n", (double)yy);

yyy = SIN(xxx);          // matches long double
printf("%.17Lf\n", (double)yyy);

In which case, the results I get are expectedly close to those of the hardcopy:

6.70820379257202150
6.70820393249936940
0.00000000000000000
6.70820393249936940
0.70710678118654757

My question is twofold:

  1. Why do I have to typecast in order to compute?
  2. Why the code does not compute as-is?

I'm new to C programming, and from the aforementioned book I have typed every example, and solved every exercise on my own up to chapter 15, but I haven't come across anything that I didn't grasp in due time, considering my developing, but currently limited understanding of the language.

I'm using the NetBeans IDE (version 8.2), coupled with GCC 9.2.0 on a Win10 64bit PC with 8GB RAM. GCC 64bit (built by MINGW) version was verified with the following code. All codes in this question were compiled according to the C11 standard (gcc -c -g -std=c11 -MMD -MP -MF).

#include <stdio.h>
#include <stdlib.h>

/*
 * 
 */
int main(void) 
{
    printf("gcc version: %d.%d.%d\n",__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__);
    return 0;
}
  • [It works on godbolt.](https://godbolt.org/z/X2K9xV). Possibly a problem with NetBeans? – Nate Eldredge May 13 '20 at 21:02
  • 1
    What happens if you just try to printf a `long double`? Like for instance `long double x = 123.45L; printf("%.17LF\n", x);` – Nate Eldredge May 13 '20 at 21:13
  • 1
    Some implementations, particularly MinGW, have a problem with `long double` (because the compiler and library have different ideas about the representation of `long double`). https://stackoverflow.com/q/26296058/827263 – Keith Thompson May 13 '20 at 21:26
  • [This answer](https://stackoverflow.com/questions/4089174/printf-and-long-double) is old but suggests that 10-byte `long double` is not supported by the Microsoft C runtime, and standard library functions treat `long double` as just `double`. Since the compiler doesn't know about this, that would explain why it only works when you tell the compiler to cast to `double`. – Nate Eldredge May 13 '20 at 21:29
  • @NateEldredge, thanks for responding. I compiled the code as-is at [this place as well](https://www.onlinegdb.com/online_c_compiler), and it works... Also I did what you asked about the `long double x = 123.45L; printf("%.17LF\n", x);` code snippet and it prints `0.00000000000000000`. I don't know, I have to post this Stack Overflow link at the team who manages NetBeans, so as to inform them at least. – Urovoros Ofis May 13 '20 at 21:48
  • @KeithThompson and @NateEldedge, you are correct. The "alternative" to typecast, is either to leav `long double` in the `_Generic` macros as-is, and turn every `long double` elsewhere into `double`. The results are 6.70820379257202150 | 6.70820393249936940 | 6.70820393249936940 | 6.70820393249936940 | 0.70710678118654746 |. Results are almost identical, if I convert `long double` to `double` everywhere. – Urovoros Ofis May 13 '20 at 22:14
  • For the compiler version, please distinguish between mingw.org and mingw-w64, the latter is a fork for which one of the main reasons for forking was the former's refusal to fix this sort of bug. That info isn't apparent just from gcc -v, since they both have the same gcc, but different runtime libraries. The extended version output should say "Built by ____" – M.M May 13 '20 at 22:28
  • suggest closing as duplicate of the the questions Keith and Nate linked? – M.M May 13 '20 at 22:35
  • @M.M, my compiler is MINGW64, not MINGW-W64, so I edited the body of the question as you requested. As for closing the thread as duplicate, I humbly beg to differ because from the code above, it appears that using the `long double` type in the aforementioned setting, poses an adverse effect to different types such as `float`, which is not covered in the links provided by @NateEldredge and @KeithThompson. – Urovoros Ofis May 13 '20 at 22:53
  • @UrovorosOfis There is no adverse effect on `float` and `double`. Your code only ever prints `long double` and you are seeing the results of incorrect handling of long double. Your last code example causes undefined behaviour by using incorrect format specifier. You should see correct behaviour for `float` and `double` if you use those types instead of `long double` in the first place – M.M May 13 '20 at 22:56
  • 1
    @NateEldredge More precisely, Microsoft's compiler treats `double` and `long double` as distinct types (as required by the standard) with the same size and representation (as permitted by the standard). The problem with MinGW is that the compiler (gcc) and runtime library (Microsoft's) treat `long double` differently. Neither is wrong, but the combination into a single implementation causes bugs. – Keith Thompson May 14 '20 at 04:09

0 Answers0