Bitwise Complement Operator ~

The tilde symbol is an operator which currently can apply to a bool or integral type, i.e. the bitwise complement. It has no meaning if applies to a real(w), and is in fact an error in that situation.

I would like to hijack the operator for use against a real(w) type to mean "approximatel;y the same as" or more precisel;y to mean ".. round to the nearest integer and where the fraction is 0.5, i.e. a tie, return the adjacent integer which is even". It that considered as stepping way outside the bounds of decency?

Hi Damian —

If the context for this question is a module of your own, I think that's completely fair game. If you're advocating for putting it into the set of operators that are automatically provided by the language, I find it intriguing and would want to discuss it more (like, is there a precedent for it? do we think this would cause cheers or jeers among users? why the adjacent even integer rather than always rounding towards or away from zero?)

-Brad

Contemplating the set of operators supported by the language.

I am expecting muffled jeers. Currently a ~ means bitwise complement or it is used to delete a class.

The IEEE 754 standard mandates 5 rounding modes be supported for a floating point number:

* Round To Integral Towards Positive (ceil (or ceiling in Fortran))
* Round To Integral Towards Negative (floor)
* Round To Integral Towards Zero (trunc)
* Round To Integral Ties To Away (round)
* Round To Integral Ties To Even (?????)
* Round To Integral Exact (rint)

The names in parentheses are the Chapel routines which currently implement this rounding operations. Here, the word integral means the operation returns a floating point number with no fractional component.

All, except for the 2nd last of these, have been supported in C/C++ and Chapel for years.

The 2nd last rounding mode is what we all get when we do arithmetic operations like +, -, * and /. It is the most unbiased of all rounding modes. This is because he default arithmetic rounding mode in IEEE 754 is "Round To Integral Ties To Even". Because of that, the routine rint has provided that sort of rounding as long as the user did not mess with the currently active rounding mode. But people associated with C/C++ have realised for ages that the current situation is inadequate to support this rounding mode properly.

Now, as you noted, people generally want the third and fourth rounding of floating point numbers. Note that the round functionality of Chapel (and C/C++) only rounds ties away from zero. Otherwise, it rounds to nearest, i.e. it rounds -5.2 to -5.0, the nearest integer, it round -5.7 to -6.0, the nearest integer, and the tied case, and only the tied case away from zero so -5.5 to -6.0. IEEE 754 has no rounding mode which always rounds away from zero in the same way that trunc always round towards zero. But that has nothing to do with this email.

If we are going to make Chapel the most IEEE 754 complete language on earth, .... sounds like a ChIUW 2022 paper title eh????, .... then there is a need to address the 2nd last explicit rounding. If you need more explanation of this rounding, let me know.

Note that since 2015, TS18661-4 has done just that and it now has the standardized name roundeven. This will appear in C23 as a standard function. But that naming convention disagrees with the naming convention of all the other routines for floating point rouding in Chapel (and C/C++) to date.

I have a hole in the wall my room from banging my head against the same as I try to think of better, i.e. more consistent and clear names, for the various modes. See my ramblings in #19024 in Github for more background if you want to waste 10 minutes of your life that you will never recover.

If I can hijack a that little ~ operator, that solves my problem and will not introduce any breaking changes. The proposal is a pure extension.

It's just bitwise complement in Chapel. C++ destructors use ~ in the declaration e.g. class C { ~C() { } } but in Chapel we use deinit for that.

I'm not inherently opposed to using ~ for something like this but I tend to think we should use a named function if it is not a very common operation. And I suspect that this one is something you are advocating we add for completeness rather than because your code will call it all the time. (Please correct me if I am wrong). So roundeven sounds OK to me. But:

Can you say more about this? I am not understanding why roundeven will cause a problem when combined with round.

As soon as you use roundeven, you highlight the current suboptimal naming of other rounding related routines like rint and round. Mind you with a long history, end-user demand for backwards compatibility makes change very difficult.

Asking people, they say rint is a very imprecise name for the task of rounding according to the currently active rounding mode, even though they ALL say that it is very easy to read remember and easily rolls of the tongue.

Looking at round, that name is devoid of precision. The perfect example of how that manifests itself in the real world is Chapel's own documentation. It exposes that imprecision to the point of being misleading. In fact, instead of the words it currently uses, it should read that the round routine mathematically rounds its argument x to the nearest integral value except when the fractional part of x is 0.5, i.e. a tie between its two integral neighbors, in which case it rounds to the larger of its two neighbors, i.e. away from zero.

Pushing that argument all to way to the ceil and floor routines results in the following consistent names:

a) roundAbove (or roundUp or roundPositive)
b) roundBelow (or roundDown or roundNegative)
c) trunc[ate] (or chop using ideas in the DLMF from the US NIST)
d) roundAway
e) roundEven
f) roundExact

The names of (d) and (f) are derived from their description by the same mechanical logic as roundeven uses. The first two follow the same logic but use Above to infer being More Positive and Below to infer being More Negative. Or you could just choose Up and Down as does the IEEE 754 standard for the same concepts

All but one of those very consistent names changes the status quo. which I am reticent to suggest.

Let's remember that rounding (in any form) is the approximation of one number with lots of significant digits by one with less significant digits, Instead of a near wholesale name change, let's maintain the status quo and like all the rest, choose a verb for the name. So, instead of roundeven, use the less precise word approx[imate], a name which is arguably no less precise than the word round. So you then have

a) ceil
b) floor
c) trunc
d) round
e) approx
f) rint

Remembering that the tilde is used mathematically to mean approximately, it is a tiny step to use ~
instead of approx.

a. ceil
b. floor
c. trunc
d. round
e. ~
f. rint

Just a thought.

There are lots of alternative approaches available to us.

For one thing, we could make round have a param enum argument that specifies the rounding mode, so you could write round(x, rounding.awayFromZero) or something.

Another thing I am not quite following about this that I'm not expecting that we will have a "currently active rounding mode" at least not in the sense that C has. I am wondering if that changes the situation at all.

But beyond all of that, why would we pick the shortest name ~ for the least used mode? If we think it is what people should be using, maybe we should make round behave as roundeven ? And add something like roundAway ?

I agree there are lots of approaches.

round(x, rounding.awayFromZero)

is awfully long winded for inclusion in a tight, readable formula.

You mentioned

Another thing I am not quite following about this that I am not expecting
that we will have a "currently active rounding mode" at least not in the
sense that C has. I am wondering if that changes the situation at all.

That's a bit radical. Also, it is not a C thing? Why violate the IEEE 754 standard? It really is not an option to leave it out and some people need it. I did not realise that was in the wind until it was raised as part of Math for 2.0. I have an issue prepared to request provision for the query of floating point rounding and exceptions and setting the former. Setting the latter is ugly and slow. Having Math for 2.0 explicitly kill off part of IEEE 754 compliance seems a really retrograde step to me. I was going to include this feature ion my ieee754 module to save others needing to do the work. But there are some compiler optimization issues I want to wrap my head around before spouting off in a Github issue.

You suggested

maybe we should make round behave as roundeven?

That is a seriously breaking change to the point of delivering engineering answers for things like yield limit that are plain wrong. I thought breaking changes were to be avoided????

I would be happy with the far more precise

roundabove
roundbelow
trunc
roundaway
roundeven
roundexact

and the deprecation of the current names. Could we use truncate or does that clash with the C library routine which rewrites a file to have zero length?

My suggestion of the use of ~ was to avoid make any change to the existing names, i.e. retain the current names, and just add the extra explicit rounding method on a real(w). I was trying to keep the boat rocking to a minimum.

While I generally use rounding towards zero and away from zero in my engineering formulae because they reflect what I want to achieve, other peoples' needs are different. The most unbiased form of rounding is rounding to even and people should be encouraged to use it more often as the default.

I was incorrect in the previous post. It is perfectly legitimate for an IEEE 754 compliant language to not support querying and updating rounding modes.

But IEEE 754 compliance must support querying and updating floating point exceptions. And once you do that, because the rounding modes are stored in the same or a near mirror systems register for just about every CPU hardware around, the cost of querying and changing the rounding modes is absolutely trivial.

For the names of roundeven vs ~, I can summarize my own viewpoint as, it would be better to use roundeven since it is used in other languages and it is not so common an operation to deserve an operator in the standard library. It doesn't really bother me that round is imprecise in comparison to roundeven.

For rounding modes, I have been thinking that if we do support them, we would do it in some way of decorating a block or function to indicate the desired rounding mode, so that this detail would be known at compile time vs. run time. In IEEE 754 terminology, I am saying that I think that ideally, Chapel should support associating a constant rounding direction attribute with a block, but that Chapel never needs to support a dynamic-mode rounding attribute.

In the IEEE 754 spec is 7.1 it talks about default exception handling which seems to be about having a reasonable result and setting some status flags. I am OK with having some facility to query these status flags. I don't like the way that setting and querying the status flags in C is not tied to particular blocks/computations (as far as I know). But I think the we would meet IEEE 754 7.1's requirements if we had a way to write a block where you can check for particular situations that result from instructions in that block (say, floating point underflow).

However what I like even less is the idea that floating point exceptions would lead to control flow changes in a program in a way that is not immediately obvious when looking at the program. In particular, I really don't like the idea of turning floating point exceptions into thrown errors (or what C++ would call exceptions). I understand that there is probably a place for that in the universe somewhere, but IMO it makes it basically impossible to optimize anything when we have semantics like this.

@mppf mentioned:

For rounding modes, I have been thinking that if we do support them, we would do it in some way
of decorating a block or function to indicate the desired rounding mode, so that this detail would be
known at compile time vs. run time. In IEEE 754 terminology, I am saying that I think that ideally,
should support associating a constant rounding direction attribute with a block, but that Chapel never
needs to support a dynamic-mode rounding attribute.

Constant rounding is no benefit, well to me and a few others at least. I have never seen a need.

What is needed is the ability to run a block of code, or more likely, a whole program, at a given rounding mode. That might take a day (say) to run. Then you run it the next day with a different rounding mode. And the day after you compare the results. Hopefully, the two are much the same. If not, you have to dig deeper and that means dynamically isolating various chunks so you need flexibility.

And with the new RISC-V specification now supporting round-to-away giving users two round-to-nearest modes that can be used to test algorithm robustness, I expect that a need to change rounding modes will grow.

Re-running code to do physical simulations with different rounding modes can also help isolate bifurcation points or places where closely spaced eigenvalues do naughty things.

And no, people cannot easily recompile a whole program because they may not have access to the source code.

In the IEEE 754 spec is 7.1 it talks about default exception handling which seems to be about having a
reasonable result and setting some status flags. I am OK with having some facility to query these status
flags. I don't like the way that setting and querying the status flags in C is not tied to particular
blocks/computations (as far as I know).

How would you tie it to blocks? Sounds interesting. And a decorator (or pragma) is not allowed, although a new language feature is.

Many people might use it while testing algorithm performance like:

use _754exceptions;

flags = _754exceptions.archive(_754exceptions.All)
{
   _754exceptions.clearall();
   ...
   statements
   ...
   const flags = _754exceptions.test(_754exceptions.All);
   ....
   process results and look at, or report on, the local copy of 'flags'
   ...
}
_754exceptions.restore(flags);

That looks pretty closely tied to the block to me.

Is that what you mean when you talk about:

I think the we would meet IEEE 754 7.1's requirements if we had a way to write a block where you can
check for particular situations that result from instructions in that block (say, floating point underflow).

That said, you also need a way to say

use _754exceptions;

// assume that the underflow and inexact flags have already been cleared
x = algorithm1(a, b, c)
if _754exceptions.test(_754exceptions.Underflow | _754exceptions.InExact) then
{
    x = algorithm2(a, b, c)
}

This can be rewritten in the earlier style, albeit in a more long winded fashion.

Note that I prefer to write a more robust algorithm in the first place, even it it takes longer to run. But a lot of people prefer the above method, i.e. run the quickest algorithm first and then go to a fallback if need be.

The exceptions for which you have to test may not even be param so even though the above has lots of things known at compile time, other scenarios may need to use run-time choices.

The _754exception module is only drafted. But I have rewritten a whole C fenv interface so that it is vastly more generic and is devoid of logic in assembler. This is for another project. Only a few lines of inline assembler to get at those system registers. Once I did the exception stuff, the rounding stuff was trivial. Porting the C code to Chapel I expect will not be too hard but I want to finish the C project first.

This _754exception module does assume that if I ask for a statement to be done in one place, I expect the compiler to honour my request which as you mention, might be an issue with LLVM.