开发者

Array index in C, does it have to be an integer? Can it be a floating point?

as the title suggests... I need to use floating points as array indexes, but the GCC compiler spits out an error complaining.

Basically I have a mathematical function say F(x,t) where the function has variables x and t. The problem I'm having is that I'm trying to increment x and t in type float, so that I can calculate different values for the function at different x, and t. So natural开发者_开发知识库ly I would have two for loops:

for (x = x_min; x < x_max; x += dx) {
    for (t = t_min; t < t_min; t += dt) {
        f[x][t] = 10*x + 10*t; // Over simplified.. but you get the idea
    }
}

// And then perform some fourier transform
fft(f[x][t], 256, 1);

So yea, that is why I was wondering if it is possible to get floating points as the array index.


Yes, it has to be an integer because you are essentially performing pointer arithmetic where &array[0] is a pointer to the beginning of the array (well, technically it has to be an integer because that's what the spec says, but this is why).

In that context it makes no sense to move from the base pointer up some fraction of the size of an object. You are pretty much guaranteeing that you won't be pointing to the beginning of an element.

Look at it this way:

int array[10] = { 0 };

// analagous to *(array + 5), where '5' is 
// offsetting the pointer by sizeof(int) * 5 bytes   
// If you were able to add 5.5 to the base address 
// the value assigned to 'i' below would be interpreted as
// the four bytes following *(array + 5.5), i.e., garbage data.
int i = array[5];  

Since this strikes me as an odd question to begin with, perhaps you could give us more information regarding what you are actually trying to accomplish rather than your proposed solution? We can probably give you more helpful answers in that case.


If you're just storing whole numbers in floating point variables, casting or otherwise converting the values to an integer type should work just fine. For instance:

array[(int)x] = y;

If you really want to index an array with non-integral indices, you're going to have to design yourself a higher-level data structure, and it will probably not be an "array" in the sense of its time-efficiency properties.


Yes. From the C99 standard §6.5.2.1 (Array Subscripting):

One of the expressions shall have type ‘‘pointer to object type’’, the other expression shall have integer type, and the result has type ‘‘type’’.

If you want to use a floating-point number as an array index, you need to cast it to an integer. This is often a bad idea, because any slight rounding errors during your calculations could easily result in the array index being off by 1 after the truncation.


As you have discovered, the array indices must be integral types. To achieve the effect you want, you can scale and offset the integer index by the floating point deltas:

double x, t;
int x_i, t_i;

for (x_i = 0; x_i < NX; x_i ++) {
    x = x_min + x_i * dx;
    for (t_i = 0; t_i < NT, t_i++) {
        t = t_min + t_i * dt;
        f[x_i][t_i] = 10*x + 10*t;
    }
}


Array indices in C must be integral


Depending on what it is you really want to do, you might be able to use a scaled an offset version of your float as an "index":

#define arraySize 100
Entry array[arraySize];
float scaleFactor = 10;
float base = 0.1;
float value = 0.3;
// This truncation is where we have a many to one mapping.
index = (int)( (value - base) * scaleFactor);
if (index >=0 && index < arraySize)
    Entry* entry = array + index;


yes we can, but it will cost 64 KiByte memory per stored byte

a float is stored in 4 bytes = 32 bits, which you can "read" with an integer pointer.
not convert/cast/round to integer, but "read" the raw bytes.
in c code:

float f = 3.1415;

int *ip = NULL;
ip = (int *)&f;

printf("f = %f\ni = %i = 0x%x\n", f, *ip, *ip);

// f = 3.141500
// i = 1078529622 = 0x40490e56
//                    ^^^^
//                    bfloat16

to use all floats as array index, we would need 2**32 Byte = 4 GiByte of memory

to reduce memory use, we can use the bfloat16 type,
which is simply a "truncated" version (lossy compression) of the 32 bit float,
with only 7 bit mantissa:

bfloat16 = SEEEEEEE EMMMMMMM
 float32 = SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM

bfloat16 came to hype with "machine learning", but is also used for sensor data.
some exotic processors have hardware support for bfloat16.

the conversion between float32 and bfloat16 is trivial,
only complicated by different byte order (endianness)

bfloat16.c
// convert between float32 and bfloat16
// use float as array index --> see end of file
// public domain + no warranty

#include <stdint.h> // uint16_t, int8_t
#include <stdlib.h> // malloc, free, size_t
#include <string.h> // memset
#include <stdbool.h> // bool
#include <stdio.h> // printf



typedef uint16_t bfloat16; // --> "maybe remove"
typedef float     float32;



// stolen from tensorflow bfloat16.cc

void float32_to_bfloat16_be(float32 *src, bfloat16 *dst, size_t size)
{
    uint16_t *s = (uint16_t *)src;
    uint16_t *d = (uint16_t *)dst; // maybe remove

    for (; size != 0; s += 2, d++, size--) {
        // big endian byte order
        *d = s[0];
    }
}

void float32_to_bfloat16_le(float32 *src, bfloat16 *dst, size_t size)
{
    uint16_t *s = (uint16_t *)src;
    uint16_t *d = (uint16_t *)dst; // maybe remove

    for (; size != 0; s += 2, d++, size--) {
        // little endian byte order
        *d = s[1];
    }
}

void bfloat16_to_float32_be(bfloat16* src, float32* dst, size_t size)
{
    uint16_t *s = (uint16_t *)src; // maybe remove
    uint16_t *d = (uint16_t *)dst;

    for (; size != 0; s++, d += 2, size--) {
        // big endian byte order
        d[0] = *s;
        d[1] = 0;
    }
}

void bfloat16_to_float32_le(bfloat16* src, float32* dst, size_t size)
{
    uint16_t *s = (uint16_t *)src; // maybe remove
    uint16_t *d = (uint16_t *)dst;

    for (; size != 0; s++, d += 2, size--) {
        // little endian byte order
        d[0] = 0;
        d[1] = *s;
    }
}



// detect byte order at runtime
// http://esr.ibiblio.org/?p=5095#comment-415728
// this usually does not generate any code at all with GCC even with -O1
// ignore byte orders other than LE and BE

static inline bool is_big_endian() {
    const uint16_t endianness = 256; // 0b 00000001 00000000
    return *(const uint8_t *)&endianness;
}



// demo program

int main()
{
    // function pointers for the current byte order
    void (*float32_to_bfloat16)(float32*, bfloat16*, size_t) = NULL;
    void (*bfloat16_to_float32)(bfloat16*, float32*, size_t) = NULL;

    // detect byte order at runtime
    if (is_big_endian())
    {
        printf("byte order is big endian\n");

        float32_to_bfloat16 = &float32_to_bfloat16_be;
        bfloat16_to_float32 = &bfloat16_to_float32_be;
    }
    else
    {
        printf("byte order is little endian\n");

        float32_to_bfloat16 = &float32_to_bfloat16_le;
        bfloat16_to_float32 = &bfloat16_to_float32_le;
    }



    // convert one number

     float32 a1 = 3.1415;
    bfloat16 b1 = 0;

    float32_to_bfloat16(&a1, &b1, 1);



    bfloat16 a2 = 0x4049;
     float32 b2 = 0;

    bfloat16_to_float32(&a2, &b2, 1);



    printf("%1.4f --> 0x%04x\n", a1, b1);
    printf("%1.4f <-- 0x%04x\n", b2, a2);



    // convert many numbers

     float32 a3[2] = {2.7182, 1.4142};
    bfloat16 b3[2] = {0};

    float32_to_bfloat16(a3, b3, 2);



    bfloat16 a4[2] = {0x402d, 0x3fb5};
     float32 b4[2] = {0};

    bfloat16_to_float32(a4, b4, 2);



    printf("%1.4f --> 0x%04x\n", a3[0], b3[0]);
    printf("%1.4f <-- 0x%04x\n", b4[0], a4[0]);

    printf("%1.4f --> 0x%04x\n", a3[1], b3[1]);
    printf("%1.4f <-- 0x%04x\n", b4[1], a4[1]);



    // array with float index [half-float index]
    int8_t *n_fi = NULL; // int8_t = -128 .... +127

    // init array
    n_fi = malloc(0xffff * sizeof(typeof(*n_fi)));
    memset(n_fi, 0, 0xffff * sizeof(typeof(*n_fi)));
    // 0xffff = 2**16-1 = 65535 = 64Ki-1

    // key
    float32 k = 3.1415;

    // convert key
    bfloat16 k16 = 0;
    float32_to_bfloat16(&k, &k16, 1);

    // value
    int8_t v = 123;

    // write
    n_fi[k16] = v;

    // read
    printf("n_fi[0x%04x] = %i\n", k16, n_fi[k16]);

    // read next = zero from array init
    printf("n_fi[0x%04x] = %i\n", k16+1, n_fi[k16+1]);

    // close array
    free(n_fi);

}

64 KiByte per Byte is too much?
we can reduce the key space to "small positive floats"
but then we need boundary checks to prevent segfaults.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜