0

I am trying to print out an std::array as seen below, the output is supposed to consist of only booleans, but there seem to be numbers in the output aswell (also below). I've tried printing out the elements which give numbers on their own, but then I get their actual value, which is weird.

My main function:

float f(float x, float y)
{
    return x * x + y * y - 1;
}

int main()
{
    std::array<std::array<bool, ARRAY_SIZE_X>, ARRAY_SIZE_Y> temp = ConvertToBinaryImage(&f);
    for(int i = 0; i < (int)temp.size(); ++i)
    {
        for(int j = 0; j < (int)temp[0].size(); ++j)
        {
            std::cout << temp[i][j] << " ";
        }
        std::cout << std::endl;
    }
}

The function that sets the array:

std::array<std::array<bool, ARRAY_SIZE_X>, ARRAY_SIZE_Y> ConvertToBinaryImage(float(*func)(float, float))
{
    std::array<std::array<bool, ARRAY_SIZE_X>, ARRAY_SIZE_Y> result;

    for(float x = X_MIN; x <= X_MAX; x += STEP_SIZE)
    {
        for(float y = Y_MIN; y <= Y_MAX; y += STEP_SIZE)
        {
            int indx = ARRAY_SIZE_X - (x - X_MIN) * STEP_SIZE_INV;
            int indy = ARRAY_SIZE_Y - (y - Y_MIN) * STEP_SIZE_INV;
            result[indx][indy] = func(x, y) < 0;
        }
    }
    return result;
}

The constants

#define X_MIN -1
#define Y_MIN -1
#define X_MAX 1
#define Y_MAX 1
#define STEP_SIZE_INV 10
#define STEP_SIZE (float)1 / STEP_SIZE_INV
#define ARRAY_SIZE_X (X_MAX - X_MIN) * STEP_SIZE_INV
#define ARRAY_SIZE_Y (Y_MAX - Y_MIN) * STEP_SIZE_INV

My output:

184 225 213 111 0 0 0 0 230 40 212 111 0 0 0 0 64 253 98 0
0 0 0 0 1 0 1 0 1 1 1 1 6 1 0 0 168 0 0 0
0 183 213 111 0 0 0 0 0 0 0 0 0 0 0 0 9 242 236 108
0 0 0 1 64 1 1 0 1 1 1 1 240 1 1 1 249 1 0 0
0 21 255 0 0 0 0 0 98 242 236 108 0 0 0 0 0 0 0 0
0 0 0 1 128 1 1 0 1 1 1 1 128 1 1 1 0 1 1 0
0 1 255 1 0 1 1 0 1 1 1 1 0 1 1 1 31 1 1 1
0 0 0 0 184 225 213 111 0 0 0 0 2 0 0 0 0 0 0 0
9 1 0 1 0 1 1 0 1 1 1 1 0 1 1 1 64 1 1 1
0 1 0 1 64 1 1 0 1 1 1 1 96 1 1 1 249 1 1 1
0 1 213 1 0 1 1 0 1 1 1 1 0 1 1 1 32 1 1 1
0 1 0 1 0 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1
0 21 255 0 0 0 0 0 80 59 117 0 0 0 0 0 32 112 64 0
0 1 0 1 17 1 1 16 1 1 1 1 104 1 1 1 0 1 1 1
0 0 144 1 249 1 1 0 1 1 1 1 0 1 1 1 0 1 1 0
0 0 0 1 80 1 1 0 1 1 1 1 24 1 1 1 0 1 1 0
0 0 0 0 0 0 0 0 17 0 1 16 0 0 0 0 112 7 255 0
0 0 0 1 134 1 1 30 1 1 1 1 8 1 1 1 0 1 0 0
0 0 0 0 0 1 1 0 1 1 1 1 0 1 1 1 32 0 0 0
0 0 0 0 0 0 1 0 1 1 1 1 0 1 0 0 0 0 0 0
JanTheMan
  • 23
  • 5
  • 5
    what are XMIN, XMAX, XSETP etc – pm100 Mar 22 '22 at 20:50
  • 2
    Why are you storing floating point numbers in a bool array? – NathanOliver Mar 22 '22 at 20:52
  • XMIN, XMAX are the range over which I evaluate the function which is passed as a parameter. – JanTheMan Mar 22 '22 at 20:55
  • I am not storing floating point numbers, I am storing the boolean expression f(x,y) < 1 – JanTheMan Mar 22 '22 at 20:56
  • 1
    Ah, missed that `< 0` at the end. for a bool, 0 means false and any other value is true, so this isn't really unexpected. You might be able to force strictly 0 or 1 by using double negation, ie `func(x, y) < 0` -> `!!(func(x, y) < 0)` – NathanOliver Mar 22 '22 at 21:00
  • bools are either 0 or not 0 so those can be valid values (cout does not print true or false). Since you dont show all those constants XMIN, MAX, STEP etc its hard to say what should be happening, especially if step is > 1 – pm100 Mar 22 '22 at 21:00
  • Now I also tried printing std::cout << (temp[i][j] ? 1 : 0) << " "; But that results in the same error, although I specifically say to print 1 or 0 – JanTheMan Mar 22 '22 at 21:02
  • I added the constants in the post – JanTheMan Mar 22 '22 at 21:07
  • some elements of `result` are uninitialised so your program has undefined behaviour, `indx` and `indy` also go out of bounds of the array leading to more undefined behaviour – Alan Birtles Mar 22 '22 at 21:16
  • Using floating-point values to calculate array indices often leads to off-by-one errors. Use `indx` and `indy` as the loop control variables and calculate `x` and `y` from their values. Or, at least, look at the values that they take on in the loops. – Pete Becker Mar 22 '22 at 21:16
  • Thanks! I expressed x and y in terms of indx and indy and it worked – JanTheMan Mar 22 '22 at 21:25
  • @NathanOliver doesn't `bool` already imply strict values of `1` or `0`? It is said that `sizeof(bool)` is implementation defined, but I could't find any info if `bool` can yield something besides the 2 values. Is any compiler for c++11 and higher allowed to do that? https://godbolt.org/z/z9zcv7Ghe I would understand if a `reinterpret_cast` was somehow involved, but even type punning gives `1` – Sergey Kolesnik Mar 22 '22 at 21:28
  • @SergeyKolesnik TIL :) Looks like true and false are the only valid values once you have a bool: https://timsong-cpp.github.io/cppwp/basic.types#basic.fundamental-10.sentence-2 – NathanOliver Mar 22 '22 at 21:32
  • Probably some UB going on with mixing floating point calculation with integer indicies. – NathanOliver Mar 22 '22 at 21:33
  • You have a lot of out of bounds accesses: https://godbolt.org/z/a9Gqr1ebx – mch Mar 22 '22 at 21:35
  • @NathanOliver it doesn't matter if there is any UB in comparing of floats (why would there be?). The code is printing from an `std::array` of `bool`. No casting in that place – Sergey Kolesnik Mar 22 '22 at 21:38
  • @SergeyKolesnik Not from the comparison, from the index calculation. `ARRAY_SIZE_X - (x - X_MIN) * STEP_SIZE_INV;` generates a floating point result, and that result can be going out of bounds of the array, leading to UB. – NathanOliver Mar 22 '22 at 21:43
  • @NathanOliver yeah, but that is not the reason. Going out of bounds does not affect array's value. What does affect them (as Alan stated in his answer) is an uninitialized state. This updated example of printing out an uninitialized array reproduces the behavior https://godbolt.org/z/fzEd9dsKE – Sergey Kolesnik Mar 22 '22 at 21:47

1 Answers1

4

Floating point maths will often not produce accurate results, see Is floating point math broken?.

If we print out the values of indx and indy:

20, 20
20, 19
20, 18
20, 17
20, 15
20, 14
20, 13
20, 13
20, 11
20, 10
20, 9
20, 9
20, 8
20, 6
20, 5
20, 5
20, 3
20, 3
20, 1
20, 1
19, 20
19, 19
19, 18
19, 17
...

You can see that you are writing to indexes with the value 20 which is out of bounds of the array and also you aren't writing to every index leaving some of the array elements uninitialised. Though normally booleans are only true or false they are usually actually stored as a byte allowing storing values between 0 and 255, printing the uninitialised values is undefined behaviour.

We can fix your code in this particular instance by calculating the indexes a little more carefully:

int indx = std::clamp(int(round(ARRAY_SIZE_X - (x - X_MIN) * STEP_SIZE_INV)), 1, ARRAY_SIZE_X)-1;
int indy = std::clamp(int(round(ARRAY_SIZE_Y - (y - Y_MIN) * STEP_SIZE_INV)), 1, ARRAY_SIZE_Y)-1;

There are two fixes here, you were generating values between 1 and 20, the -1 reduces this to 0 to 19. The round solves the issue of not using all the indexes (you were simply truncating by assigning to an int). The clamp ensures the values are always in range (though in this case the calculations work out to be in range).

As you want to always write to every pixel a better solution would be to iterate over the values of indx and indy and calculate the values of x and y from the indices:

for (int indx = 0; indx < ARRAY_SIZE_X; indx++)
{
  float x = X_MIN - (indx - ARRAY_SIZE_X) * STEP_SIZE;
  for (int indy = 0; indy < ARRAY_SIZE_Y; indy++)
  {
    float y = Y_MIN - (indy - ARRAY_SIZE_Y) * STEP_SIZE;
    result[indx][indy] = func(x, y) < 0;
  }
}
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • you can mention that unitialized state can be tackled (and should always be) by explicit initialization of the output array: `std::array<...> result{};` - gives zero-initiliazed values – Sergey Kolesnik Mar 22 '22 at 21:51
  • @SergeyKolesnik if the intent is to write to every element then initialising the array would be wasteful – Alan Birtles Mar 22 '22 at 21:54
  • Wouldn't it be optimized? – Sergey Kolesnik Mar 22 '22 at 21:55
  • @SergeyKolesnik if you don't put it in to start with you don't need to rely on the optimiser – Alan Birtles Mar 22 '22 at 21:58
  • That contradicts modern practises where safe is better than "fast". Zero-initiliazed is guaranteed to not contain any UB. And you deploy with `-O3` in 99.999% of cases. So it boils down to whether it is guaranteed to be optimized. If you can provide cases that do not and it noticably affetcs the efficiency, I'd be glad to look at them – Sergey Kolesnik Mar 22 '22 at 22:03