The short version is that +12 isn't a preprocessor token, and neither is -12; rather, each of these are two preprocessor tokens (namely, + and - are punctuators; 12 separately is a pp-number).
We're essentially dealing with a pp-number, so here's the grammar rules for pp-number's:
pp-number:
digit
. digit
pp-number digit
pp-number identifier-nondigit
pp-number e sign
pp-number E sign
pp-number p sign
pp-number P sign
pp-number .
Note here that 1, 1e+, 1e+12, and 1e+12f are all pp-numbers, but +12 and -12 are not. This is what's biting you.
Per the preprocessor rules, a parameter in the replacement list is replaced by the argument token sequence; then, each ## is applied. The application removes ## and concatenates the preceding token to the following token. If that combination is not a valid preprocessor-token, the result is undefined. (For reference, a preprocessor-token can be a header name, identifier, pp-number, character-constant, string-literal, punctuator, or a non-whitespace character that's not one of those; that's it).
When applying 1e##IE##f with IE being +12, you're essentially doing <1e>##<+><12>##<f>, using angle braces to denote individual tokens. Both pastes, regardless of order (which is good, because the ## order is unspecified), will produce valid pp-numbers <1e+> and <12f>. But the result leaves you with <1e+><12f> rather than the desired <1e+12f>.
How can I avoid it?
Unfortunately you have to give up on passing +12 and -12 token sequences as an argument. You could accept +, 12 as two arguments, but you need to be careful when combining these in steps, since the order of ## operators is unspecified and +12 is not a valid preprocessor token (otherwise it might work, but would be allowed not to... and that situation is a potentially nightmarish time bomb):
#define PASTE(A,B) A##B
#define DECL_FL(IE) 1e##IE##f
#define DECL_FL_SGN(S,IE) PASTE(1e##S,IE##f)
float val[] =
{
DECL_FL(12),
DECL_FL_SGN(+,12),
DECL_FL_SGN(-,12),
};
...or you could simply use a sign specific macro; and strictly since 1e12f and 1e+12f are the same value (and nobody's going to see it anyway unless they just run the preprocessor), you can get away with only two macros:
#define DECL_FL(IE) 1e##IE##f
#define DECL_FL_E_NEG(IE) 1e-##IE##f
float val[] =
{
DECL_FL(12),
DECL_FL_E_NEG(12),
DECL_FL(12),
};