Casting a void* pointer

189 Views Asked by At

i have a problem in my code when i try to use compelx structure in a generic sorting library on C lang: (you can find the complete code https://github.com/Tramontz/ASD_2023)

i have a binary-merge insertion sort, thatt take in imput a void pointer (first element of an array), and ,can work with array of int and string (all unity test pass).

Now i'm tryng to use it with an array of:

struct record{
  int id;
  char* string_field_1;
  int integer_field_2;
  double float_field_3;
};

stored in another struct

struct _StructArray{
  void**array;
  unsigned long el_num;
  unsigned long array_capacity;
};

with subroutine

void struct_array_add(StructArray *struct_array, void* element){
  if(struct_array->el_num >= struct_array->array_capacity){
    printf("array too short, must be reallocate \n");
    struct_array->array = (void**)realloc(struct_array->array,2*(struct_array->array_capacity)*sizeof(void*));
    struct_array->array_capacity = 2*struct_array->array_capacity;
  }
  struct_array->array[struct_array->el_num] = element;
  struct_array->el_num++;
}

void* struct_array_get(StructArray *struct_array, unsigned long index){
  return &(struct_array->array)[index];
}

i must order the void**array of record.

this is an example of the data took from a csv file

<POSIZION:0, ID:0, String:noto, Integer:233460, Float:32209.073312 >

<POSIZION:1, ID:1, String:piangea, Integer:4741192, Float:81176.622633 >

<POSIZION:2, ID:2, String:spenti!, Integer:1014671, Float:4476.013614 >

<POSIZION:3, ID:3, String:misericordia, Integer:496325, Float:61628.929334 >

the ordering mode is choosed by

switch (field) {
      case 1://struct_array_get(array,0) return the first element of the array
          merge_binary_insertion_sort(struct_array_get(array,0), struct_array_size(array), sizeof(struct record), k, precedes_record_string_field);

so basically i store with a loop all my data in the record *array inside structArray.

the array is correctly loaded, because the print function

for(unsigned long i=0;i<el_num;i++){
    array_element = (struct record *)struct_array_get(array, i);
    printf("<POSIZION:%d, ID:%d, String:%s, Integer:%d, Float:%lf >\n",i,array_element->id,array_element->string_field_1,array_element->integer_field_2,array_element->float_field_3); 
  }

can show all the record in the array.

so the problem appear when the sorting function is called:

    void bin_insertion_sort(void *arr, int n, size_t elementSize, CompareFunction compare) {
        int i, loc, j;
        void *selected = malloc(elementSize);
        if (selected == NULL) {
          fprintf(stderr, "Errore nell'allocazione di memoria per 'selected'\n");
           exit(EXIT_FAILURE);
         }
        for (i = 1; i < n; ++i) {
            j = i - 1;
            memcpy(selected, arr + i * elementSize, elementSize);

            // Find location where selected should be inserted
            loc = binary_search(arr, selected, 0, j, compare, elementSize);

i store in the void *selected the item that i want to find location in the array, and call the binary search


int binary_search(void *arr, void *item, int low, int high, CompareFunction compare, size_t elementSize) {
        if (high <= low){
            return (compare(item, arr + low * elementSize) > 0) ? (low + 1) : low ;

We can focus only in the binary_search, here is the problem: when i call return (compare(item, arr + low * elementSize) > 0) ? (low + 1) : low ; for the records's array, with this compare function

 static int precedes_record_string_field(const void* r1_p, const void* r2_p) {
    if (r1_p == NULL || r2_p == NULL) {
        fprintf(stderr, "precedes_record_string_field: one of the parameters is a null pointer\n");
        exit(EXIT_FAILURE);
    }

    const struct record* rec1_p = (const struct record*)r1_p;
    const struct record* rec2_p = (const struct record*)r2_p;

    printf("Record 1: ID=%d, String=%s, Integer=%d, Float=%f\n", rec1_p->id, rec1_p->string_field_1, rec1_p->integer_field_2, rec1_p->float_field_3);
    sleep(1);
    printf("Record 2: ID=%d, String=%s, Integer=%d, Float=%f\n", rec2_p->id, rec2_p->string_field_1, rec2_p->integer_field_2, rec2_p->float_field_3);
sleep(5);

    return strcmp(rec1_p->string_field_1, rec2_p->string_field_1);
}

it print the rec2 data, but not the rec1 data, so i suppose that it can cast the arr + low * elementSize pointer, but not the void item* pointer that I pass through the compare(item, arr + low * elementSize) and store in rec1(item) and rec2(arr + low * elementSize).

I'm doing something wrong in the memory adress management? maybe i'm adressing all the structArray with item, and not the second element of the record* array?

i'm stucked because all my unity test with single, null, and multiple string and integer arrays work correctly.

Thank you all.

1

There are 1 best solutions below

17
arfneto On BEST ANSWER

It seems very very very complex the way you wrote.

Maybe you could test the functions first...

I will show you a methodical way of writing his,
using your `struct` and `qsort`,
that you can easily adapt...

EDIT: added a second part, PART II, below. In that I added code to use the same method below to sort a void** array, and then a StructArray as in the original question, as an example of a way to separate the container, the methods and the algorithm. This was a heavy use of copy and paste: the changes are only in the compare functions (2 lines in each) and a function added to StructArray (4 lines) to extract he base address of the array of pointers.

keep things separated

typedef struct
{
    int    id;
    char*  string;
    int    i_field;
    double d_field;
} Target;

int cmp_1(const void*, const void*);
int cmp_2(const void*, const void*);
int cmp_3(const void*, const void*);
int cmp_4(const void*, const void*);

int so_show(unsigned, Target[], const char*);

Here we have your data record, the 4 required functions and a function to show the results

Use encapsulation and write code around your data.

See this :

    Target some_value[] = {
        [0] =
            {.id      = 42,
             .string  = "value 33",
             .i_field = 4242,
             .d_field = 42.42},
        [1] =
            {.id      = 4,
             .string  = "stack",
             .i_field = 42,
             .d_field = 42.242},
        [2] =
            {.id      = 342,
             .string  = "overflow",
             .i_field = 4,
             .d_field = 142.42},
        [3] =
            {.id      = 412,
             .string  = "take the survey",
             .i_field = 2,
             .d_field = 2142.42},
};

This is data to test all your functions.

###' main for a test ###

Here:

  • the array is printed as declared
  • the array is sorted by each criteria and displayed
    so_show(4, some_value, "\nBefore sort:\t");

    qsort(some_value, 4, sizeof(Target), cmp_1);
    so_show(4, some_value, "\nAfter sort by id:\t");

    qsort(some_value, 4, sizeof(Target), cmp_2);
    so_show(
        4, some_value, "\nAfter sort by string field:\t");

    qsort(some_value, 4, sizeof(Target), cmp_3);
    so_show(
        4, some_value, "\nAfter sort by integer field:\t");

    qsort(some_value, 4, sizeof(Target), cmp_4);
    so_show(
        4, some_value, "\nAfter sort by double field:\t");

the output


Before sort:    4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|   42 | "            value 33" |     4242 |      42.4200 |
|    4 | "               stack" |       42 |      42.2420 |
|  342 | "            overflow" |        4 |     142.4200 |
|  412 | "     take the survey" |        2 |    2142.4200 |
|=========================================================|


After sort by id:       4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|    4 | "               stack" |       42 |      42.2420 |
|   42 | "            value 33" |     4242 |      42.4200 |
|  342 | "            overflow" |        4 |     142.4200 |
|  412 | "     take the survey" |        2 |    2142.4200 |
|=========================================================|


After sort by string field:     4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|  342 | "            overflow" |        4 |     142.4200 |
|    4 | "               stack" |       42 |      42.2420 |
|  412 | "     take the survey" |        2 |    2142.4200 |
|   42 | "            value 33" |     4242 |      42.4200 |
|=========================================================|


After sort by integer field:    4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|  412 | "     take the survey" |        2 |    2142.4200 |
|  342 | "            overflow" |        4 |     142.4200 |
|    4 | "               stack" |       42 |      42.2420 |
|   42 | "            value 33" |     4242 |      42.4200 |
|=========================================================|


After sort by double field:     4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|    4 | "               stack" |       42 |      42.2420 |
|   42 | "            value 33" |     4242 |      42.4200 |
|  342 | "            overflow" |        4 |     142.4200 |
|  412 | "     take the survey" |        2 |    2142.4200 |
|=========================================================|

And it seems to be ok.

It is easier this way: first test all functions.

Since you said the code already works for integers, just make sure the sorting code really abstracted the data record. In general is just a case of writing correct swapping functions. And paying special attention to the navigating process...

complete C code

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

typedef struct 
{
    int    id;
    char*  string;
    int    i_field;
    double d_field;
}  Target;

int cmp_1(const void*, const void*);
int cmp_2(const void*, const void*);
int cmp_3(const void*, const void*);
int cmp_4(const void*, const void*);

int so_show(unsigned, Target[], const char*);

int main(void)
{
    Target some_value[] = {
        [0] =
            {.id      = 42,
             .string  = "value 33",
             .i_field = 4242,
             .d_field = 42.42},
        [1] =
            {.id      = 4,
             .string  = "stack",
             .i_field = 42,
             .d_field = 42.242},
        [2] =
            {.id      = 342,
             .string  = "overflow",
             .i_field = 4,
             .d_field = 142.42},
        [3] =
            {.id      = 412,
             .string  = "take the survey",
             .i_field = 2,
             .d_field = 2142.42},
    };

    so_show(4, some_value, "\nBefore sort:\t");

    qsort(some_value, 4, sizeof(Target), cmp_1);
    so_show(4, some_value, "\nAfter sort by id:\t");

    qsort(some_value, 4, sizeof(Target), cmp_2);
    so_show(
        4, some_value, "\nAfter sort by string field:\t");

    qsort(some_value, 4, sizeof(Target), cmp_3);
    so_show(
        4, some_value, "\nAfter sort by integer field:\t");

    qsort(some_value, 4, sizeof(Target), cmp_4);
    so_show(
        4, some_value, "\nAfter sort by double field:\t");

};  // main

int cmp_1(void* one, void* other)
{
    Target* a = one;
    Target* b = other;
    if (a->id < b->id) return -1;
    if (a->id == b->id) return 0;
    return 1;
};

int cmp_2(void* one, void* other)
{   // now for the string
    Target* a = one;
    Target* b = other;
    return strcmp(a->string, b->string);
};

int cmp_3(void* one, void* other)
{
    Target* a = one;
    Target* b = other;
    if (a->i_field < b->i_field) return -1;
    if (a->i_field == b->i_field) return 0;
    return 1;
};

int cmp_4(void* one, void* other)
{
    Target* a = one;
    Target* b = other;
    if (a->d_field < b->d_field) return -1;
    if (a->d_field == b->d_field) return 0;
    return 1;
};
int so_show(unsigned N, Target r[], const char* msg)
{
    if (r == NULL) return -1;
    if (msg != NULL) printf("%s", msg);
    printf("%d records\n", N);
    const char* l0 =
        "\
|  id  |           string value |  integer |       double |";
    const char* l1 =
        "\
|=========================================================|";
    printf("%s\n%s\n%s\n", l1,l0,l1);
    for ( unsigned u=0;u<N;u+=1)
        printf(
            "| %4d | \"%20s\" | %8d | %12.4f |\n", r[u].id,
            r[u].string, r[u].i_field, r[u].d_field);
    printf("%s\n\n",l1);
    return 0;
}

Part II

This is main.c for the second example:

int main(void)
{
    Target some_value[] = {
        [0] =
            {.id      = 42,
             .string  = "value 33",
             .i_field = 4242,
             .d_field = 42.42},
        [1] =
            {.id      = 4,
             .string  = "stack",
             .i_field = 42,
             .d_field = 42.242},
        [2] =
            {.id      = 342,
             .string  = "overflow",
             .i_field = 4,
             .d_field = 142.42},
        [3] =
            {.id      = 412,
             .string  = "take the survey",
             .i_field = 2,
             .d_field = 2142.42},
    };

    const N = sizeof(some_value) / sizeof(some_value[0]);

    test_with_struct_array(
        N, some_value,
        "\n\n\t***** using an array of structs *****\n\n");
    test_with_voidp_array(
        N, some_value,
        "\n\n\t***** using a 'void**' *****\n\n");
    test_with_Struct_Array(
        N, some_value,
        "\n\n\t***** using an original 'StructArray' "
        "*****\n\n");

    return 0;
};  // main

test_with_struct_array() is the original program. The 2nd function sorts the same array in a void** array, in order to test the mechanics in isolation, and the 3rd function uses the author's original StructArray --- with a minor modification, see code below.

usind a void**

This is the code to build the array using the original data:

    Target** base = malloc(n * sizeof(Target*));
    for (unsigned i = 0; i < n; i += 1)
        base[i] = &some_value[i];

The compare funcions are all different, since there is a new level of indirection to accesss the actual data. The new functions are cmp_[5678] from cmp_[1234] and here is a pair, that compares the i_field from Target:


int cmp_3(const void* one, const void* other)
{
    Target* a = (Target*)one;
    Target* b = (Target*)other;
    if (a->i_field < b->i_field) return -1;
    if (a->i_field == b->i_field) return 0;
    return 1;
};

int cmp_7(const void* one, const void* other)
{
    Target* a = (Target*)*((const void**)one);
    Target* b = (Target*)*((const void**)other);
    if (a->i_field < b->i_field) return -1;
    if (a->i_field == b->i_field) return 0;
    return 1;
};

The difference is just in getting the data address. Since StructArray is also a void** thing, these functions are used there too.

Sister functions were added to display the data from the different sources, so the output is exactly the same, as expected.

int so_show_one(Target*);
int so_show(
    unsigned, Target[], int (*show)(void*), const char*);
int so_show_from_StructArray(
    StructArray*, int (*show)(void*), const char*);
int so_show_from_voidp_array(
    unsigned, void**, int (*show)(void*), const char*);

The first function displays a single data record, so can be used by all 3 formats. It is passed as a parameter so it is easy to use an alternate display for each test if needed.

The change in StructArray

void* struct_array_get_base(StructArray* x)
{
    if (x == NULL) return NULL;
    return &(x->array)[0];
}

This function was added in order to get the base address from inside StructArray

On using StructArray as a container

int test_with_Struct_Array(
    unsigned n, Target some_value[], const char* msg)
{
    if (msg != NULL) printf("%s", msg);

    StructArray* tgt = struct_array_create();
    for (size_t i = 0; i < n; i += 1)
        struct_array_add(tgt, &some_value[i]);
    fprintf(
        stderr, "\n\t%u values inside target StructArray\n",
        struct_array_size(tgt));

    so_show_from_StructArray(
        tgt, so_show_one,
        "\n    With data inside StructArray:\t");

    // this is the base for sort
    void*  array_base = struct_array_get_base(tgt);
    size_t size       = struct_array_size(tgt);
    qsort(array_base, size, sizeof(void*), cmp_5);
    so_show_from_StructArray(
        tgt, so_show_one, "\n\tAfter sort by id:\t");

This is how the container was abstracted for sorting, at the start of the test function. This should work for any sorting algorithm on the contiguous array of void*.

Hope it helps

code for the second example

#pragma pack(push, 1)
#pragma pack(show)

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

#include "struct_array.h"

typedef struct
{
    int    id;
    char*  string;
    int    i_field;
    double d_field;
} Target;

int cmp_1(const void*, const void*);
int cmp_2(const void*, const void*);
int cmp_3(const void*, const void*);
int cmp_4(const void*, const void*);

int cmp_5(const void*, const void*);
int cmp_6(const void*, const void*);
int cmp_7(const void*, const void*);
int cmp_8(const void*, const void*);

int so_show_one(Target*);
int so_show(
    unsigned, Target[], int (*show)(void*), const char*);
int so_show_from_StructArray(
    StructArray*, int (*show)(void*), const char*);
int so_show_from_voidp_array(
    unsigned, void**, int (*show)(void*), const char*);

int test_with_struct_array(unsigned, Target[], const char*);
int test_with_voidp_array(unsigned, Target[], const char*);
int test_with_Struct_Array(unsigned, Target[], const char*);

int main(void)
{
    Target some_value[] = {
        [0] =
            {.id      = 42,
             .string  = "value 33",
             .i_field = 4242,
             .d_field = 42.42},
        [1] =
            {.id      = 4,
             .string  = "stack",
             .i_field = 42,
             .d_field = 42.242},
        [2] =
            {.id      = 342,
             .string  = "overflow",
             .i_field = 4,
             .d_field = 142.42},
        [3] =
            {.id      = 412,
             .string  = "take the survey",
             .i_field = 2,
             .d_field = 2142.42},
    };

    const N = sizeof(some_value) / sizeof(some_value[0]);

    test_with_struct_array(
        N, some_value,
        "\n\n\t***** using an array of structs *****\n\n");
    test_with_voidp_array(
        N, some_value,
        "\n\n\t***** using a 'void**' *****\n\n");
    test_with_Struct_Array(
        N, some_value,
        "\n\n\t***** using an original 'StructArray' "
        "*****\n\n");

    return 0;
};  // main

int cmp_1(const void* one, const void* other)
{  // compare ids
    Target* a = (Target*)one;
    Target* b = (Target*)other;
    if (a->id < b->id) return -1;
    if (a->id == b->id) return 0;
    return 1;
};

int cmp_2(const void* one, const void* other)
{  // now for the string
    Target* a = (Target*)one;
    Target* b = (Target*)other;
    return strcmp(a->string, b->string);
};

int cmp_3(const void* one, const void* other)
{
    Target* a = (Target*)one;
    Target* b = (Target*)other;
    if (a->i_field < b->i_field) return -1;
    if (a->i_field == b->i_field) return 0;
    return 1;
};

int cmp_4(const void* one, const void* other)
{  // for doubles
    Target* a = (Target*)one;
    Target* b = (Target*)other;
    if (a->d_field < b->d_field) return -1;
    if (a->d_field == b->d_field) return 0;
    return 1;
};

int cmp_5(const void* one, const void* other)
{
    Target* a = (Target*)*((const void**)one);
    Target* b = (Target*)*((const void**)other);
    if (a->id < b->id) return -1;
    if (a->id == b->id) return 0;
    return 1;
};

int cmp_6(const void* one, const void* other)
{  // now for the string
    Target* a = (Target*)*((const void**)one);
    Target* b = (Target*)*((const void**)other);
    return strcmp(a->string, b->string);
};

int cmp_7(const void* one, const void* other)
{
    Target* a = (Target*)*((const void**)one);
    Target* b = (Target*)*((const void**)other);
    if (a->i_field < b->i_field) return -1;
    if (a->i_field == b->i_field) return 0;
    return 1;
};

int cmp_8(const void* one, const void* other)
{  // sort as double
    Target* a = *((void**)one);
    Target* b = *((void**)other);
    if (a->d_field < b->d_field) return -1;
    if (a->d_field == b->d_field) return 0;
    return 1;
};

int so_show(
    unsigned N, Target r[], int (*show)(void*),
    const char* msg)
{
    if (r == NULL) return -1;
    if (msg != NULL) printf("%s", msg);
    printf("%d records\n", N);
    const char* l0 =
        "\
|  id  |           string value |  integer |       double |";
    const char* l1 =
        "\
|=========================================================|";
    printf("%s\n%s\n%s\n", l1, l0, l1);
    for (unsigned u = 0; u < N; u += 1) show(r + u);
    printf("%s\n\n", l1);
    return 0;
}

int so_show_one(Target* one)
{
    printf(
        "| %4d | \"%20s\" | %8d | %12.4f |\n", one->id,
        one->string, one->i_field, one->d_field);
    return 0;
}

int so_show_from_StructArray(
    StructArray* A, int (*show)(void*), const char* msg)
{
    if (A == NULL) return -1;
    if (show == NULL) return -2;
    if (msg != NULL) printf("%s", msg);
    unsigned long N = struct_array_size(A);
    printf("%u records\n", N);
    const char* l0 =
        "\
|  id  |           string value |  integer |       double |";
    const char* l1 =
        "\
|=========================================================|";
    printf("%s\n%s\n%s\n", l1, l0, l1);
    for (unsigned u = 0; u < N; u += 1)
        show((Target*)struct_array_get(A, u));
    printf("%s\n\n", l1);
    return 0;
}

int so_show_from_voidp_array(
    unsigned N, void** base, int (*show)(void*),
    const char* msg)
{
    if (base == NULL) return -1;
    if (show == NULL) return -2;
    if (N <= 0) return -3;
    if (msg != NULL) printf("%s", msg);
    printf("%u records\n", N);
    const char* l0 =
        "\
|  id  |           string value |  integer |       double |";
    const char* l1 =
        "\
|=========================================================|";
    printf("%s\n%s\n%s\n", l1, l0, l1);
    for (unsigned u = 0; u < N; u += 1)
        show((Target*)base[u]);
    printf("%s\n\n", l1);
    return 0;
}

int test_with_struct_array(
    unsigned n, Target some_value[], const char* msg)
{
    if (msg != NULL) printf("%s", msg);

    so_show(
        n, some_value, so_show_one, "\n    Before sort:\t");

    qsort(some_value, n, sizeof(Target), cmp_1);
    so_show(
        n, some_value, so_show_one,
        "\n    After sort by id:\t");

    qsort(some_value, n, sizeof(Target), cmp_2);
    so_show(
        n, some_value, so_show_one,
        "\n    After sort by string field:\t");

    qsort(some_value, n, sizeof(Target), cmp_3);
    so_show(
        n, some_value, so_show_one,
        "\n    After sort by integer field:\t");

    qsort(some_value, n, sizeof(Target), cmp_4);
    so_show(
        n, some_value, so_show_one,
        "\n    After sort by double field:\t");

    return 0;
}

int test_with_voidp_array(
    unsigned n, Target some_value[], const char* msg)
{
    if (msg != NULL) printf("%s", msg);

    Target** base = malloc(n * sizeof(Target*));

    for (unsigned i = 0; i < n; i += 1)
        base[i] = &some_value[i];

    so_show_from_voidp_array(
        n, base, so_show_one, "\tbefore sort: ");

    size_t sz_one = sizeof(Target*);
    qsort(&base[0], n, sz_one, cmp_5);
    so_show_from_voidp_array(
        n, base, so_show_one, "\tsorted by id field: ");

    qsort(&base[0], n, sz_one, cmp_8);
    so_show_from_voidp_array(
        n, base, so_show_one, "\tsorted by double field: ");

    qsort(&base[0], n, sz_one, cmp_7);
    so_show_from_voidp_array(
        n, base, so_show_one,
        "\tsorted by integer field: ");

    qsort(&base[0], n, sz_one, cmp_6);
    so_show_from_voidp_array(
        n, base, so_show_one, "\tsorted by string field: ");
    return 0;
}

int test_with_Struct_Array(
    unsigned n, Target some_value[], const char* msg)
{
    if (msg != NULL) printf("%s", msg);

    StructArray* tgt = struct_array_create();
    for (size_t i = 0; i < n; i += 1)
        struct_array_add(tgt, &some_value[i]);
    fprintf(
        stderr, "\n\t%u values inside target StructArray\n",
        struct_array_size(tgt));

    so_show_from_StructArray(
        tgt, so_show_one,
        "\n    With data inside StructArray:\t");

    // this is the base for sort
    void*  array_base = struct_array_get_base(tgt);
    size_t size       = struct_array_size(tgt);
    qsort(array_base, size, sizeof(void*), cmp_5);
    so_show_from_StructArray(
        tgt, so_show_one, "\n\tAfter sort by id:\t");

    qsort(array_base, size, sizeof(void*), cmp_6);
    so_show_from_StructArray(
        tgt, so_show_one,
        "\n\tAfter sort by string field:\t");

    qsort(array_base, size, sizeof(void*), cmp_7);
    so_show_from_StructArray(
        tgt, so_show_one,
        "\n    After sort by integer field:\t");

    qsort(array_base, size, sizeof(void*), cmp_8);
    so_show_from_StructArray(
        tgt, so_show_one,
        "\n    After sort by double field:\t");

    return 0;
}

output for the 3rd test

Not interesting since it is the same data 3 times, but generated by differen containers built from the same original data.

        ***** using an original 'StructArray' *****


        4 values inside target StructArray

    With data inside StructArray:       4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|    4 | "               stack" |       42 |      42.2420 |
|   42 | "            value 33" |     4242 |      42.4200 |
|  342 | "            overflow" |        4 |     142.4200 |
|  412 | "     take the survey" |        2 |    2142.4200 |
|=========================================================|


        After sort by id:       4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|    4 | "               stack" |       42 |      42.2420 |
|   42 | "            value 33" |     4242 |      42.4200 |
|  342 | "            overflow" |        4 |     142.4200 |
|  412 | "     take the survey" |        2 |    2142.4200 |
|=========================================================|


        After sort by string field:     4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|  342 | "            overflow" |        4 |     142.4200 |
|    4 | "               stack" |       42 |      42.2420 |
|  412 | "     take the survey" |        2 |    2142.4200 |
|   42 | "            value 33" |     4242 |      42.4200 |
|=========================================================|


    After sort by integer field:        4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|  412 | "     take the survey" |        2 |    2142.4200 |
|  342 | "            overflow" |        4 |     142.4200 |
|    4 | "               stack" |       42 |      42.2420 |
|   42 | "            value 33" |     4242 |      42.4200 |
|=========================================================|


    After sort by double field: 4 records
|=========================================================|
|  id  |           string value |  integer |       double |
|=========================================================|
|    4 | "               stack" |       42 |      42.2420 |
|   42 | "            value 33" |     4242 |      42.4200 |
|  342 | "            overflow" |        4 |     142.4200 |
|  412 | "     take the survey" |        2 |    2142.4200 |
|=========================================================|