carraylistc99

Is up-casting numeric types in C always reversible?


EDIT: Oh dear I feel foolish. Of course you're not going to be able to cast from longs to doubles and back - longs have more significant bits. Will casting to a long double improve this?

I'm implementing an Array List in C which I want to be type-agnostic. I also don't want it to be so verbose in usage, thus I'd rather my get functions not return void pointers and require type-casting by the user. Therefore I'm storing an enum for the type (one type for each list), which can be INT, FLOAT, LONG, or DOUBLE. Parameters are passed as doubles (so the compiler will implicitly cast if you pass an int, float or long), then depending on the type of data stored in the list, the double which is passed gets casted to the correct type. Will this always work? Will a number, casted to a double, and then back down to its original type ever have a different value? Are there any other issues with the overarching idea? Barebones implementation is below

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>

typedef enum{
    INT,
    LONG,
    FLOAT,
    DOUBLE
}TYPE;

typedef struct arrayList{
    void * data;
    TYPE type;
    size_t capacity;
    size_t num_elements;
}arrayList;

void reallocateArrayList(arrayList * list, size_t capacity){
    TYPE type=list->type;
    if(type == INT){
        void * toAllocate = malloc(capacity*sizeof(int));
        memcpy(toAllocate, list->data, list->num_elements*sizeof(int));
        if(list->data!=NULL){
            free(list->data);
        }
        list->data = toAllocate;
    }
    if(type == FLOAT){
        void * toAllocate = malloc(capacity*sizeof(float));
        memcpy(toAllocate, list->data, list->num_elements*sizeof(float));
        if(list->data!=NULL){
            free(list->data);
        }
        list->data = toAllocate;
    }
    if(type==LONG){
        void * toAllocate = malloc(capacity*sizeof(long));
        memcpy(toAllocate, list->data, list->num_elements*sizeof(long));
        if(list->data!=NULL){
            free(list->data);
        }
        list->data = toAllocate;
    }
    if(type==DOUBLE){
        void * toAllocate = malloc(capacity*sizeof(double));
        memcpy(toAllocate, list->data, list->num_elements*sizeof(double));
        if(list->data!=NULL){
            free(list->data);
        }
        list->data = toAllocate;        
    }
    list->capacity=capacity;
    if(list->data==NULL) raise(ENOMEM);
}

arrayList * createArrayList(TYPE type){
    arrayList * retval = (arrayList *) malloc(sizeof(arrayList));
    retval->capacity=256;
    retval->data = NULL;
    retval->type = type;
    retval->num_elements = 0;
    reallocateArrayList(retval, 256);
    return retval;

}

void add(arrayList * list, double val){
    if(list->capacity<=list->num_elements){
        reallocateArrayList(list, 4*list->capacity);
    }
    TYPE type=list->type;
    if(type == INT){
        ((int *)(list->data))[list->num_elements]=(int) val;
    }
    if(type == FLOAT){
        ((float *)(list->data))[list->num_elements]=(float) val;
    }
    if(type==LONG){
        ((long *)(list->data))[list->num_elements]=(long) val;

    }
    if(type==DOUBLE){
    ((double *)(list->data))[list->num_elements]=val;
    }
    list->num_elements++;
}

double get(arrayList * list, size_t index){
    double retval;
    if(index>=list->num_elements){
        printf("Error: arrayList get index out of range");
        raise(SIGSEGV);
    }
    if(list->type==INT){
        retval=((int*)(list->data))[index];
    }
    if(list->type==FLOAT){
        retval=((float*)(list->data))[index];
    }
    if(list->type==LONG){
        retval=((long*)(list->data))[index];
    }
    if(list->type==DOUBLE){
        retval=((double*)(list->data))[index];
    }
    return retval;
}


Solution

  • Is up-casting numeric types in C always reversible?

    No, it is implementation-defined behaviour and it is a very bad idea.

    I would use voids and some helper macros to make it completely universal. It is completely type agnostic and can be used with any data type (also structs, unions and arrays)

    typedef struct arrayList{
        size_t capacity;
        size_t num_elements;
        size_t elemsize;
        unsigned char data[];
    }arrayList;
    
    #define listCreate(capacity, type) reallocateArrayList(NULL, capacity, sizeof(type))
    #define listReaaloc(list, capacity) reallocateArrayList(list, capacity, list ? list -> elemsize : 0)
    #define listGet(list, object, index) get(list, index, &object)
    #define listAdd(list, object) add(list, &object)
    
    
    arrayList *reallocateArrayList(arrayList * list, size_t capacity, size_t elemsize)
    {
        size_t new_num_elements = list ? list -> num_elements : 0;
        
        list = realloc(list, sizeof(*list) + capacity * elemsize * sizeof(list -> data[0]));
        if(list)
        {
            list -> capacity = capacity;
            list -> elemsize = elemsize;
            list -> num_elements = new_num_elements;
        }
        return list;
    }
    
    int add(arrayList * list, void *val)
    {
        int result = -1;
        if(list && list -> num_elements < list -> capacity)
        {
            memcpy(list -> data + list -> num_elements++ * list -> elemsize, val, list -> elemsize);
            result = 0;
        }
        return result;
    }
    
    int get(arrayList * list, size_t index, void *element)
    {
        int result = -1;
        if(list && index < list -> num_elements)
        {
            memcpy(element, list -> data + index * list -> elemsize, list -> elemsize);
            result = 0;
        }
        return result;
    }
    

    and some example usage:

    int main(void)
    {
        arrayList *al = listCreate(100, double);
        double dbl = 10.3;
    
        listAdd(al, (double){4.3});
        listAdd(al, dbl);
    
        printf("%zu\n", al -> num_elements);
    
        for(size_t index = 0; index < al -> num_elements; index++)
        {
            double y;
            listGet(al, y, index);
            printf("%f\n", y);
        }
    
        free(al);
    }
    
    

    https://godbolt.org/z/EYezracjT