2

I have a function, mag, in a file mag.c that calculates the magnitude of an array.

#include <math.h>
#include "mag.h"

long double mag(long double arr[], int len){
    long double magnitude=0;
    for(int i=0;i<len;i++)
        magnitude+=pow(arr[i],2);
    magnitude=pow(magnitude,0.5);
    return magnitude;
}

I would like it to accept both double and long double arrays. I've read elsewhere (for instance here) that if an argument doesn't match the function declaration, it will be implicitly converted to the correct type. I wrote a function test.c to test this.

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

int main(){
        double arr1[3]={0.0,1.1,2.2};
        printf("%Lf",mag(arr1,3));
        return 0;
}

However, this produced an error

test.c: In function ‘main’:
test.c:7:19: warning: passing argument 1 of ‘mag’ from incompatible pointer type [-Wincompatible-pointer-types]
  printf("%Lf",mag(arr1,3));
                   ^~~~
In file included from test.c:3:
mag.h:4:29: note: expected ‘long double *’ but argument is of type ‘double *’
 long double mag(long double arr[], int len);

Declaring the array as a long double allows the function to work properly. I also tried changing the argument type in the header file, but that returned -nan. Is there any easy way to make the mag function accept both double and long double arguments, or would it be simpler to make 2 separate functions? (If I need to make separate functions for double and long double arguments, I would need to do this for a lot of files.)

Leon
  • 75
  • 5
  • You will need to write two separate functions. at least because they have two different return types. Otherwise the common function will be too complicated and unreadable using pointers of the type void *. – Vlad from Moscow Apr 07 '22 at 22:50
  • 2
    Passed-by-value arguments of 'simple' types can be implicitly converted but not pointers and arrays (which decay to pointers when used as function arguments). – Adrian Mole Apr 07 '22 at 22:51
  • @VladfromMoscow I can return long double for all cases and implicitly convert back to double in the file that calls the function if necessary. – Leon Apr 07 '22 at 22:54
  • @AdrianMole Ah, that makes sense. Too bad then. – Leon Apr 07 '22 at 22:55
  • There are ways such as using structs with a `union` field or vaargs. Those do change the API from simple types to slightly more complicated. So depends on which tradeoffs you want to make. – kaylum Apr 07 '22 at 23:12

2 Answers2

3

... it will be implicitly converted to the correct type. I wrote a function test.c to test this.

This works for some arguments like double converted to long double, but not double * converted to long double *.

C has _Generic just for this sort of programming. Use mag(arr, len) _Generic((arr) ... to steer selection of code.

I recommend to also use long double functions and long double constants with long double objects.

long double mag_long_double(const long double arr[], int len) {
  long double magnitude = 0;
  for (int i = 0; i < len; i++)
    magnitude += powl(arr[i], 2);  // powl
  magnitude = sqrtl(magnitude);
  return magnitude;
}

double mag_double(const double arr[], int len) {
  double magnitude = 0;
  for (int i = 0; i < len; i++)
    magnitude += pow(arr[i], 2);
  magnitude = sqrt(magnitude);
  return magnitude;
}

#define mag(arr, len) _Generic((arr), \
  long double *: mag_long_double, \
  double *: mag_double \
  )((arr), (len))

int main(void) {
  double arr1[3] = {0.0, 1.1, 2.2};
  printf("%f\n",mag(arr1,3));
  long double arr2[3] = {0.0, 3.3L, 4.4L}; // Add 'L'
  printf("%Lf\n",mag(arr2,3));
  return 0;
}

Output

2.459675
5.500000
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • This works perfectly. Thanks! By the way, is there a reason that you're adding L at the end of the numbers in arr2? I would think that simply declaring arr2 to be an array of long doubles is sufficient, and indeed it worked fine for me without the L. – Leon Apr 08 '22 at 21:25
  • @leon Do you consider `3.3` to be the same _value_ as `3.3L`? – chux - Reinstate Monica Apr 08 '22 at 21:30
  • I don't see why they wouldn't be the same value given they're initialized in a long double array. – Leon Apr 11 '22 at 22:15
  • 1
    @Leon A 64-bit `double` can exactly encode about 2^64 different values of the form: +/- some_integer times a power-of-2. `long double` (as 80 or 128 bit) can encode many more. Neither can represent the math value 3.3 _exactly_, instead nearby values are used and the `long double` uses a nearer, and different, value. – chux - Reinstate Monica Apr 11 '22 at 22:35
  • 1
    @Leon Further, it is the type of the _constant_ being discussed, not the type of the object they initialize. – chux - Reinstate Monica Apr 11 '22 at 22:46
  • 1
    That makes sense. I didn't realize decimal numbers default to doubles before being converted to the variable type. – Leon Apr 12 '22 at 00:54
  • 1
    @Leon To be clear, what the type of the object being initialized does _not_ determine the type of the constant. The constant's type is solely determined by the constant. – chux - Reinstate Monica Apr 12 '22 at 01:58
1

You are correct that a function argument will be implicitly converted to the expected type, if possible. However, this implicit conversion will not go so far as to convert the object or the array that the function argument is pointing to. It will only convert the function argument itself (i.e. the pointer).

Therefore, in C, you will have to create two separate functions, or do something ugly, like creating a single function which takes a void * as an argument, which will be interpreted either as a long double * or a double * depending on the value of an additional argument:

#include <math.h>
#include <stdbool.h>

long double mag( void *arr, int len, bool is_long )
{
    long double magnitude=0;

    if ( is_long )
    {
        //interpret array as "long double"
        long double *arr_longdouble = arr;

        for( int i=0; i<len; i++ )
            magnitude += powl( arr_longdouble[i], 2 );
    }
    else
    {
        //interpret array as "double"
        double *arr_double = arr;

        for( int i=0; i<len; i++ )
            magnitude += powl( arr_double[i], 2 );
    }

    magnitude = powl( magnitude, 0.5 );
    return magnitude;
}

However, it is worth nothing that C++ (but not C) has the ideal solution for what you want to accomplish. In that language, you can make a single function template:

template <typename T>
T mag( T arr[], int len )
{
    T magnitude = 0;
    for( int i=0; i<len; i++ )
        magnitude += pow( arr[i], 2 );
    magnitude = pow( magnitude, 0.5 );
    return magnitude;
}

In this function, the data type T can be either a double or a long double (or any other data type, as long as the code compiles). The compiler will automatically create a double and/or a long double version of the function for you, as required.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39