Switch for floating !!

K

Kaz Kylheku

Hi,

Why does the switch case statement does not have support
for floating points ?

The likely reason is that switch statements are not often used for values that
represent mathematical integers, but rather values that represent codes:
characters representing elements of text, whose codes are arbitrarily assigned,
and enumerations representing types, states, graph labels, error codes, and
the like.

Rarely is a switch used for some kind of mathematical property, because
mathematical properties tend to generate infinite sets.

Would you use a giant switch statement to distinguish fibonacci integers from
other integers?
int main(void)
{
int a = 10;
switch(a)
{
case 1:
printf("1 \n");
break;
case 2.5:
printf("2.5 \n");
break;
default:
printf("Other Number \n");
break;
}
return 0;
}

You need a better example to actually rationalize switching on floating point
values. I.e. show some numerical code that benefits from a floating point
switch statement.

Her is an idea:

#include <math.h>

// copysign is a C99 function

switch (copysign(1.0, x)) {
case -1.0:
// x is negative
break;
case 0:
// x is zero
break;
case 1.0:
// x is positive
break;
}

I can tell you that the utility of this is very low, by the following argument.
Fortran once had a three-way arithmetic if statement for doing exactly the
above: taking one of three different actions based on a value being negative,
positive or zero. This statement had so little utility that it was marked
obsolescent and removed from Fortran.

So a better example than this one would have to be found.
 
K

Kaz Kylheku

You mean, float?


Switch is designed to work with integer values only. Working with floating
point would not have been impossible, but because it rules out the
possibility of a jump table, a switch would have little advantage over
if-else statements.

The purpose of a switch statement isn't specifically to express a jump table
optimization, but simply to express the idea of branching on a code, whereby
the machine generates the implementation.

Jump table optimizations are possible only when the codes form consecutive
integer values (or values that can be mapped to consecutive integers by
removing a common displacement, and dividing by a common divisor).

When codes do not represent consecutive values, it is still possible to take
advantage of inequality comparisons to build a decision tree which optimizes
the switch. Such a tree would be tedious to build and maintain by hand.

And such a tree can be optimized further by making use of run-time statistical
data, which would be tedious to do by hand also.

A group of floating point values could be handled in that way.

But in actual numeric code, floating point numbers tend to be classified into
buckets, not into exact values.

The most useful form of a floating point switch statement would have to accept
ranges, using some new syntax.

This is because the cases that arise in numeric computing usually involve
ranges, and not distinct values.

switch (floating_value) {
case < -5.0:
// ...
break;
case [ -5.0, 0.0 ): // half open interval, including -5.0, excluding 0.
// ...
break;
case > 0.0: // or default:
// ...
break;
}

There would be constraints: none of the ranges can overlap, and when two ranges
share a boundary value, at least one of them must be open for that value.

This is analogous to the constraint that the cases in an integer switch must be
distinct.

The above syntax could be useful for integers too, as a way of condensing
cases. The syntax is straightforward: an optional inequality operator (>, <,
<=, or ==) followed by a constant (the absence of such an operator defaulting
to ==) or a range specification similar to the mathematical notation for
intervals: either [ or (, followed by a comma-separated pair of constants
(which undergo promotion and conversion to a common type) followed by ) or ].

There is value in the above construct in that it can optimize the
classification into ranges; the classification certainly doesn't have to
proceed by testing each of the ranges one by one.
There are also problems in exactly matching one floating point number with
another; a special way of testing would be needed.

Right. Taken care of by ranges.
You will have a long wait.

More to the point, if you wait, nothing will happen.

A more proactive approach is to hack this into the GNU Compiler Collection (for
instance) to demonstrate its feasibility. Write some body of code which uses
the extensions (or better yet, sucker some other people into using them).
 
K

Kaz Kylheku

Do you mean to say that C does not have facility for exact Floating
Point

``exact'' and ``floating point'' are mutually exclusive terms, so
``exact floating point'' is an oxymoron.

C does not support oxymoronic numeric types. :)
 
B

Bartc

You need a better example to actually rationalize switching on floating
point
values. I.e. show some numerical code that benefits from a floating point
switch statement.
switch (copysign(1.0, x)) {
case -1.0:
// x is negative
break;
case 0:
// x is zero
break;
case 1.0:
// x is positive
break;
}

I can tell you that the utility of this is very low, by the following
argument.
Fortran once had a three-way arithmetic if statement for doing exactly the
above: taking one of three different actions based on a value being
negative,
positive or zero. This statement had so little utility that it was marked
obsolescent and removed from Fortran.

Possibly because you can do a similar thing (to arithmetic IF) with a
Computed Goto.

There was a discussion on c.l.c perhaps a year ago on allowing /any/ type as
a switch index, and allowing non-constant case values. But the c.l.c lot
are a very conservative bunch who don't seem to like new syntax (or even
existing syntax a lot of the time). (Myself, I like interesting new bits of
syntax.)

One argument I think (possibly mine, can't remember) was that if the
language allows:

float x;
if (x==a)...
else if (x==b || x==c)...
else if (x==d)...
else e;

Then you might as well allow, in a switch or a new statement:

switch (x) {
case a:
case b: case c:
case d:
default e:

Whether using == on floating point values is a good idea, is irrelevant.
Another idea mooted was a case range operator ".." or "...", which to me
sounds ideal for floating point.

Again, the idea was shot down.

(Not that it would have made much difference; I'll be long dead before any
such changes would ever be widely available in C.)
 
B

Bartc

Kaz Kylheku said:
The purpose of a switch statement isn't specifically to express a jump
table
optimization, but simply to express the idea of branching on a code,
whereby
the machine generates the implementation.

Right. Taken care of by ranges.

I was trying to be impartial here, and just stating some probable facts. In
fact I quite like the idea.
More to the point, if you wait, nothing will happen.

A more proactive approach is to hack this into the GNU Compiler Collection
(for
instance) to demonstrate its feasibility. Write some body of code which
uses
the extensions (or better yet, sucker some other people into using them).

I was refering to the feature finding it's way -- and being widely
implemented -- in the next C standard.
 
J

James Kuyper

You gave an example which, proves to you (though not to me) that
integers are needed. That example was based upon jump tables. You
accused me of failing to read that example. Yet my response to that
message directly refuted your argument about jump tables. That is the
basis for my conclusion, below, that you did not read my refutation.
The following is a copy of my original reply to you. It includes
every word of your reply (as evidenced by the absence of <snip>
markers). In that light, I cannot follow your 'did not read'
comment. I answered the question you posed. I included an example
showing why the standard was so written.

Oddly enough, the "did not read" comment was motivated by text that did
not appear anywhere in your original message, quoted below, but rather
in your later message, the one that I was directly replying to. That
might be why you couldn't find it?

....
In a separate answer somewhere in this thread I gave an example of
floating and integer representation of the number 3. Remember that
the idea is to have rapid execution. ...

Allowing the controlling expression to have a floating point type would
not prevent use of a jump when a controlling expression with an integer
type is provided (assuming that the particular cases used are such as to
make a jump table a reasonable implementation approach).
... Any such intermixing of
representation would require fairly extensive code to test and
convert the representation, before indexing a pointer to a jump
table with it. Or it would require two separate sets of code
generated.

I presume that you mean that it would sometimes have to generate
something other than a jump table. Any decent compiler already does
that. Jump tables should only be used when they provide a better results
than other possible approaches, with "better" depending, among other
things, upon whether you're compiling for maximum speed or smallest
executable, or some other criterion entirely.
... Any such is contrary to the spirit of C, and totally
unnecessary. At worst, avoidance requires anointing the switch
statement with a cast.

I have no idea what that last comment refers to.
 
J

James Kuyper

CBFalconer said:
James Kuyper wrote:
... snip ...

Among other things the present restrictions make it easy for the
compiler to examine the set of switch values (they are all listed
in the statement) ...

??? They would still all be listed in the statement. I don't see why
restricting it to an integer type would make it any easier to examine
that set than it would be if floating point types were allowed.
... and decide between generating if/else expressions
or a jump table. That decision can result in major savings in code
space and/or execution time.

As Keith has pointed out, a simplistic approach would be simply to not
bother checking whether a jump table would be an option, if the
controlling expression has a non-integral type.
I'm glad you have identified the fundamental cause of our
'argument', which really doesn't exist. It shows how much
difference one small missing word can cause.

Our argument was about whether or not the reason you gave was
sufficient. That we agree that a different reason is sufficient doesn't
change the fact that we still disagree about the your reason.
 
J

James Kuyper

James Kuyper wrote:
....
not appear anywhere in your original message, quoted below, but rather
[text which did not contain his original message]

My apologies; I decided to drop the redundant quotation of that message,
but forgot to remove the comment about it being "quoted below".
 
C

CBFalconer

James said:
.... snip ...


I have no idea what that last comment refers to.

switch ((int)float_value) {
case 0: whatever0(); break;
case 1: whatever1(); break;
default: break;
}

however (float_value == 0.9999999999) results in case 0. The cast
may generate considerable code.
 
J

James Kuyper

CBFalconer said:
James Kuyper wrote:
... snip ...

switch ((int)float_value) {
case 0: whatever0(); break;
case 1: whatever1(); break;
default: break;
}

however (float_value == 0.9999999999) results in case 0. The cast
may generate considerable code.

Well, now I know what you're referring to, but I don't see the
relevance. If someone wants to write:

switch(float_value)
{
case 0.0: whatever0(); break;
case 1.0: whatever1(); break;
default: break;
}

They would presumably be very disappointed with any proposed replacement
which didn't have the same semantics as

if(float_value == 0.0)
whatever0();
else if(float_value == 1.0)
whatever1();

So in what way does this "avoidance" mechanism substitute for the
desired functionality?

The whole point of wanting to pass a floating point value to switch is
the desire for a cleaner way of expressing complicated code than a long
chain of if-else statements (ignoring the issues with floating point
comparisons for equality that apply to any such code).

In this particular case, the if-else chain looks simpler than the switch
statement. However, in more complicated situations, the switch()
statement would be clearer, if it were allowed, for precisely the same
reason that a switch() statement with an integral controlling expression
is sometimes clearer than the corresponding chain of if-else statements.

Such code should not be written in the first place, because of issues
about floating point comparisons for equality, but that's a different
issue from the one you've raised about jump tables.
 
C

CBFalconer

James said:
CBFalconer wrote:
.... snip ...


Well, now I know what you're referring to, but I don't see the
relevance. If someone wants to write:

switch(float_value) { /* code edited, cbf */
case 0.0: whatever0(); break;
case 1.0: whatever1(); break;
default: break;
}

They would presumably be very disappointed with any proposed
replacement which didn't have the same semantics as

if (float_value == 0.0) whatever0();
else if (float_value == 1.0) whatever1();

So, by eliminating the possibility, you eliminate the complaint.
.... snip ...

Such code should not be written in the first place, because of
issues about floating point comparisons for equality, but that's
a different issue from the one you've raised about jump tables.

Floating point is ALWAYS an approximation. You can never assume
the value is exact. For a case table you need exact values. Thus
to have the option of a case table you need exact values.

In addition, conversion between float and integer is not trivial.
 
G

Guest

There is no exact representation for many numbers people write in
binary floating point.  When you write 0.1, you don't GET 0.1
exactly.  It would require an infinite number of bits to do that.

yes but
double d = 0.1;

has *an* exact value. It may not be 0.1 but it is exact


<snip>
 
J

James Kuyper

CBFalconer said:
So, by eliminating the possibility, you eliminate the complaint.

I'm not sure which possibility you're referring to. I'm not sure which
complaint you're referring to.
... snip ...

Floating point is ALWAYS an approximation.

Agreed - which is why I said "such code should not be written in the
first place".
.. You can never assume
the value is exact.

That depends upon the values involved, the code, and the implementation.
On most implementations I'm familiar with, sufficiently small integer
values (and "sufficiently small" includes some very large numbers)
divided by powers of 2.0 (negative powers included) can be relied upon
to be stored and compared exactly, assuming the number is within range
of the floating point type used. Also, if you do nothing with a floating
point value but move it around from one variable to another of the same
or greater precision, you can rely upon that value to be unchanged by
movement, and to compare to it's original value.

None of that's guaranteed by the standard, but I know of no reason why
it couldn't be; and it's sufficient to make exact comparison of floating
point values a reasonable thing to do, under some circumstances. Those
circumstances are sufficiently tricky to identify that it's a bad idea
to take advantage of them. That's the real reason why floating point
values should not be allowed as controlling expressions in switch()
statements. The feasibility (or lack thereof) of using a jump table to
implement such switch() statements is irrelevant, because there's no
need to use jump tables.
... For a case table you need exact values. Thus
to have the option of a case table you need exact values.

Yes, but there's no need to generate a case table from a switch
statement; it's always possible to implement it as a chain of if-else
statements. Allowing floating point values to be used as a controlling
expression would do nothing to prevent generation of a case table when a
program contains a switch statement with a controlling expression of
integer type.
In addition, conversion between float and integer is not trivial.

Neither is it rocket science. However, since there's no need to generate
case tables, an implementation that doesn't want to be bothered with
such a conversion need not even bother checking for the possibility that
a case table would be a legal way to implement a switch() statement, if
setting one up would require performing such a conversion.

Note: if the controlling expression of a switch() statement were allowed
to have a floating point type, and the values of the case expressions
happened to be such that they allowed efficient use of a jump table,
then the code generated by the compiler could NOT be equivalent to the
code you presented which I've quoted at the top of this message. That is
because doing so would produce incorrect behavior when the controlling
expression is not exactly equal to one of the case expressions, as
discussed above.

It would have to do something like the following (note that the goto
statements below are not proper C code; they are merely symbolic of what
the generated code does):

if(float_value >= min_case && float_value <= max_case)
{
int index = (int)floor((float_value - offset) / scale);

if(index * scale + offset == float_value)
goto jump_table[index];
}
goto default case;
 
R

Richard

James Kuyper said:
I'm not sure which possibility you're referring to. I'm not sure which
complaint you're referring to.


Agreed - which is why I said "such code should not be written in the
first place".

So a float which holds 2.0 is only an approximation?

Wow.
 
K

Kaz Kylheku

Floating point representations are exact. They don't change value all by
themselves.

I agree anyone is an idiot if they use equality comparison for the result of a
calculation with a floating point, but that doesn't make it inexact!

Chucky is right. Floating point representations are inexact. The fact that a
given floating point number is a rational number with a precisely determined
value doesn't change the fact that this floating-point number is an
approximation of some other number.

In the floating point representation, ranges of real numbers /alias/ to a
single floating point value. That's what makes it in exact.

So the value 1.0 not only represents the integer 1, but also the entire range
of real numbers from 1-epsilon to 1+epsilon, for some small epsilon.

Would you say that a sequence of digital samples are an exact representation
of the signal, because none of the bits are fuzzy?
 
K

Kaz Kylheku


Yes, and similarly:

void compute_roots(struct polynomial *, double *roots);

will also return exact values! These may not in fact be the roots of the given
formula, but they are exact representations of whatever it is they represent.

So nothing in the computer can be inexact, in fact!

It's all exact ones and exact zeros!

You people are sheer geniuses.
 
K

Keith Thompson

Kaz Kylheku said:
Chucky is right. Floating point representations are inexact. The
fact that a given floating point number is a rational number with a
precisely determined value doesn't change the fact that this
floating-point number is an approximation of some other number.

In the floating point representation, ranges of real numbers /alias/
to a single floating point value. That's what makes it in exact.

So the value 1.0 not only represents the integer 1, but also the
entire range of real numbers from 1-epsilon to 1+epsilon, for some
small epsilon.

You're talking about the *meaning* of a given floating-point value.
Such a meaning can be defined only in the context of a given
application.

Here's a trivial program using the floating-point value 1.0:

#include <stdio.h>
int main(void)
{
double d = 1.0;
printf("d = %g\n", d);
return 0;
}

The double value 1.0 here doesn't represent a range of values.
It represents the exact mathematical value of one, and nothing else.
In particular, it doesn't represent any real value other than one
whose nearest double representation happens to be 1.0. In this
program, I can't represent (even an approximation of) 1+epsilon for
sufficiently small epsilon -- but that's ok, because the program
doesn't need to represent such values.

How do I know this? Because I wrote the program, and I get to say
what it means.

If I wanted to represent something other than one, I might use
a different representation; for example, I might add a flag that
indicates whether the stored value is exact or not. Or I might
change my mind and decide to let 1.0 represent some non-trivial
range of real values.
Would you say that a sequence of digital samples are an exact
representation of the signal, because none of the bits are fuzzy?

Certainly not. In such a context, the floating-point values are
inexact approximations of some real-world real values. In a different
context, though, I might be more interested in the single exact
mathematical real value represented by a given floating-point
representation.

The approximate view is more common, but it's not the only possible
view. And even in that view, how many of the bits are fuzzy? The
precision might be limited by the accuracy of the measuring instrument
rather than by the representation of the type. A stored value of 1.0
might represent anything in the range 0.995 to +1.005, depending on
how the value was generated and how I choose to interpret its meaning.

A given floating-point value can represent a range of values near the
exact stored value. That range might be larger or smaller depending
on the precision the measurements and computations that produced it.
And the range might consist of a single real value.
 
B

Bartc

Kaz Kylheku said:
Yes. It's an approximation of a range of real numbers near 2.0.

So an integer 2 would be an approximation of the real numbers between 1.5
and 2.5?
 
B

Ben Bacarisse

Golden California Girls said:
Not quite, you would have to exclude the limit of either 1.5 or 2.5, but
otherwise that is exactly what Kaz is saying.

Why single out the reals? Why is 2.0 not an approximation to a region
of the complex plane? Why is 2 not an approximation of set of
surreal numbers? Any of that were not all daft enough, what is 'x' an
approximation of?

Whether a value in a C program is or is not approximate depends on the
intent of the programmer.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,738
Latest member
JinaMacvit

Latest Threads

Top