Cast real(w) to uint(w)

Where is the code for implementing the conversion of the (obviously signed) floating point number to an unsigned integer. I want to experiment with blocking such an operation mainly because it makes no sense mathematically.

Thanks - Damian

Hi Damian —

For casts or coercions? For params or non-params?

-Brad

Casts and coercions of non-params. This:

param t = -45.0:uint(64);

is already rejected by the compiler.

Thanks - Damian

Sorry, need params too.

Casts of non-params are easiest, and I believe should be handled by
this line in modules/internal/ChapelBase.chpl:

  inline operator :(x:chpl_anyreal, type t:integral) do
    return __primitive("cast", t, x);

Coercions and params, I'd need to dig into the compiler a bit more to
refresh my memory, and can do so if someone else doesn't know offhand and
answers first, but possibly not until getting a bit further with this
month's release deadlines.

-Brad

Thanks for that. The param cases are not urgent. I forgot that code like

param weird = (-45.0):uint(w)

delivers total rubbish and needs to be intercepted when somebody makes a typo due to fat fingers or a finger-brain disconnect.

Looking at that code, would the easiest way to disable the use of uint(w) be to write that call as just

inline operator :(x:chpl_anyreal, type t:int(?w)) do
    return __primitive("cast", t, x);

or am I being too simplistic (or worse). Thanks for indulging me.

Hi Damian —

I believe your approach would do the trick. To get a useful error, you
might also want to declare a uint overload whose body was a
compilerError() call.

Alternatively, you could put a conditional within the existing operator
that calls isUnsigned on t and issues a compilerError if true.

Just for curiosity, is your rationale for wanting to disallow these casts
"reals might be negative, and uints can't be, so protect the user from
themselves?" If so, would you disallow int-to-uint casts on the same
argument?

-Brad

You suggest

It is a little more nuanced that that.

Myt justification is that IEEE 754 does not specify such a conversion and deliberately so.

But the real reason is I am working to eliminate the whole concept of IEEE 754 status flags (which Chapel does not support) and have the NaN payload capture the exceptional status. Just numerical experiments at this stage.

But one of the cases for which IEEE 754 status flags can be used is to identify bad integer conversions, e.g.

const big = 1.0e35:real(32);
const bad = big:int(32);

A real operation which is exceptional will return a NaN which makes identifying the error easy although you have to ask the status flag what the problem was. But when the return value is an integer there is no such equivalent to a NaN, at least as per IEEE 754. Many (most modern) CPUs' signed arithmetic return a result of (-(2**(w-1)), a.k.a. as the Indefinite integer, as the result of any problematic integer operations. I want my programs to assume that such a result implies a real-to-integer conversion error. or some other Damian error. Certainly the chance that I have screwed up my program is far higher than that I would actually deliberately compute -2147483648 or (-max(int(64))-1) or even -32768 if I was working with real(16) numbers.

No. Aussies do some crazy things but there is a limit. That would break a gazillion pieces of code since the dawn of computing, including some of my own like the nextup()/nextdown() code that I posted a few months ago here. I am only interested in such usage of the Indefinite Integer in an IEEE 754 context so I know I am on firm ground. That said, it would not get such a rule into the standard because it is outside the scope of IEEE 754 but it stands a chance of being regarded as acceptable practice. That said, I will bounce it off David Keaton at some stage to see what extra wisdom he can bring to the discussion.

This optional elimination of IEEE 754 status flags and replacement by some standardized NaN payload is being discussed currently and I want to experiment with its practical use and the identification of a real-to-integral conversion is a problem that needs to be addressed and I want to get feedback on my solution (and I use Chapel code to illustrate things) because it is very readable even by non-Chapel users (at least for the stuff for which I use it).

1 Like

Just to make it obvious

const x = -45.0;
.....
const g = x:uint(64);
...
writeln(g);

yields the value 18446744073709551571. Not really very useful. And with the letter u being next to an i, a fat fingered typist or a keyboard with too much accumulated dirt or crumbs from a year of lunch eaten at a desk can cause havoc.

I am not so sure that conversion of a floating point number to an unsigned integer is terribly useful. If the behaviour is really needed

const g = (x:int(64)):uint(64);

does the job with very little extra typing and the optimizer should ensure that there is no overhead.

Just a thought

Hi Damian —

Have you considered having the cast throw in the event the value is
negative rather than just not supporting the cast at all?

Also, correcting something dumb I said yesterday: We don't support
coercions from reals to uints, so I think the routine you've been looking
at should be the only case you need to worry about.

-Brad

You suggested

Have you considered having the cast throw in the
event the value is negative rather than just not
supporting the cast at all?

I am trying to deal with a really low level IEEE 754 exceptional case. This is an operation which translates to a single machine instruction of an x86-64. I prefer to keep it that way. I do not want to introduce overhead. Unless I do not understand what you mean, the notion of using a high level throw language feature to deal with this problem requires a run-time test and means extra code.

Also you note

Thanks. So that means the inline operator routine seen above handles the param case too?

My punch line talking to IEEE 754 colleagues is that because IEEE 754 does not support conversion of a floating point type to an unsigned integral type, if we can rely on that being banned by a compiler, we can rely on the indefinite integer being the indicator of what amounts to a floating point exception. While that is a bit of a stretch for a standard that deals with floating point stuff, in that way, we can eliminate one of the very few disadvantages of removing IEEE 754 floating point status flags in their current form (which Chapel does not support anyway). Because the current IEEE 754 floating point status is often held in a per-thread register, their correct handling introduces side-effects for which an optimizing compiler must account; which I assume is why Chapel does not support them I believe? By eliminating those side-effects, an optimizer can more easier do its task

If I had to support the request to ban casting from floating point to unsigned integral in Chapel or C++ or my colleagues in C, I would say that if you, the programmer, really need to do this (because you can guarantee that a floating point datum is always non-negative) which cannot be done by static analysis, you can write (and still produce the same optimal code with)

const x = (45.0):int(32)):uint(32); // Chapel
auto const x = (unsigned int) ((int) 45.0); // C++
const unsigned int x = (unsigned int) ((int) 45.0); // C

Mind you, Professor Bjarne might tell me to use the new C++ concept feature to address this but that seems like overkill.

At this point in the IEEE 754 discussion, testing my thoughts in Chapel by banning a cast to an unsigned integral type seems the easiest path to take for someone (me) whose compiler writing brain cells are poor. I can both explain it simply and use IEEE 754 words to justify it.

I picked up a bug in a piece of someone else's existing 15+ year old C++ code by looking for this scenario yesterday. And myself and the programmer of that piece of code proved that there is a (very unlikely but still possible) scenario where this gives rubbish. Luckily, we had this problem addressed elsewhere later in the code so we knew we did not have rubbish data in geophysical maps that had been produced and sent to customers!

I hope the above make sense?

1 Like

My thought was that if you're planning to make the operation illegal ("a real cannot be converted to a uint on my branch"), you might not mind if it existed but had some error checking overhead. That error checking overhead could also be guarded by the --checks flag such that you only paid the overhead when you had execution-time checks enabled, but not in a --no-checks or --fast compilation.

No, that's handled elsewhere, and in saying that, I may have misunderstood: I thought one of your previous responses suggested that the current param conversion was OK since it was disabling conversions from negatives. On reflection, I suppose you'd probably also like to ban positive param real to uint conversions for symmetry, which I'd need to go looking for in the compiler.

I don't recall that it was anything that intentional. I suspect that we may have simply never had anyone request them, and that we didn't have the floating point knowledge/motivator/time to look into it ourselves. The use of thread-specific registers definitely sounds like it would add challenges to the implementation in a parallel setting, though. That's not to suggest they'd be unaddressable… just that it might take special care in an implementation where Chapel tasks shared threads, as in the default of Qthreads.

-Brad

Hi Damian —

I suspect that to throw a spanner in the works of param real -> uint
casts, this is probably the right place to start:

Specifically, I imagine that making this routine return false for that
type combination would prevent anything downstream from permitting it.
That's just a guess, though.

-Brad

My apologies if I was unclear. The current param conversion has the same problem. This

param x = (-45.0):uint(64)

yields an x of 18446744073709551571 which is not useful. Mind you, even my own fat fingers cannot cause that.

Thanks for that. I want to ban any param real to uint conversion. I will work with that code. Your suggestion looks like it will do the trick.

I thought that the underlying LLVM should handle most of that but sharing the values of the register between threads is a no-no so it is not something you need to address.The whole notion of IEEE 754 exception flags dates to 1985 when parallel issues and (parallel) vector lanes never existed. This discussion in IEEE 754 exists in the context of multiple threads, SIMD and out of order execution (and side-effects). I hope the final result is a new IEEE 754 mechanism (as a complete alternative to the old) where (if selected) the floating point status code is buried in the NaN result from a basic (hardware or software) arithmetic operation or some function return value. I will keep you posted as to the discussion.

Ah, I'd missed that the parenthesization was meaningful in your previous comments, and the one that had stuck in my head was:

-Brad

1 Like

Just had a look at Param::castAllowed(..). Scary stuff

As a step towards my changed variant, is the following return

  return
    (fromIntEtc && toIntEtc) ||
    (toEnum && (fromString || fromIntUint)) ||
    (fromEnum && toIntEtc) ||
    (fromEnum && toString) ||
    (fromBytes && bt->isCStringType()) ||
    (fromString && (toString || toBytes)) ||
    (fromIntEtc && (toString || toBytes));

equivalent to that which is there currently.