AutoMath : documentation(1.28) and routines therein

The AutoMath documentation for the function trunc()

Returns the nearest integral value to the argument x that is not larger
than x in absolute value.

This definition which appears a few lines earlier for round() is incomplete (and in fact wrong)

Returns the rounded integral value of the argument x.

Documentation which is more consistent with that of trunc() (but still less than perfect)

Returns the nearest integral value to the argument x, returning that
value which is larger than x in absolute value for the half-way case.

Can we put the following qualifications for the mod family of functions somewhere on that page?

This function is for use with modular arithmetic and similar number theory
applications as used by ADA, MATLAB or Julia. The C, C++, C#, D or even
Fortran idea of a mod() function is a remainder function using truncated
arithmetic. Please write your own routines if you are porting C, C++, C#,
D or Fortran code to Chapel.

Will AutoMath be loosing a lot of its current routines soon. It seems way more populous that was publically proposed unless I have missed some issue in Github.

Please note Duck-Duck-Go finds the 1.27 documentation, not the 1.28. But, click the link and you end up at 1.28.

so chapel doesn't use tie to even by default when rounding to integers? Interesting, why is that?

For what is worth, at least in julia, haskell and python round(1.5) produces 2 and round(2.5) produces 2. Matlab and c++ use TiesAway,

I think the IEEE standard only lists what rounding to integer operations should be implemented (section 5.9). The rounding to nearest has two flavours, TiesAway (chapel current one) and TieToEven (Julia etc.), but I think nothing is specified (or at least I could not find it) about a default round to nearest for integer rounding. Indeed, for example rust doesn't seem to have a round function, but a several functions depending on the mechanism.

If I recall correctly, the selling point of TiesToEven is that TiesAway (or TiesToZero) would introduce a rounding bias when repeating operations.

But I see this is tangential to the original issue, apologies for derailing. Maybe this can be split into a different thread.

Chapel's round() function is that of the C library. And that ties-to-away.

If you use rint() and assume that the default rounding mode is ties-to-even, that will get you where you want to go.

There is talk of Chapel interfacing to roundtoeven() that exists in the newer versions of the C library. Using that routine will avoid relying on any default rounding mount.

I am not a fan of Julia and I am very wary of using Python for numerical work. There are too many number theory people using Python and they like to floor their divisions which breaks all of my finite element and finite volume codes. And Haskell is not really on my radar.

What we need is a module rounding which itself has submodules called

  1. towardsZero )(truncate)
  2. upwards (ceiling)
  3. downwards (floor)
  4. tiesToEven (actually toNearestTiesToEven)
  5. tiesToAway (actually toNearestiesToAway)

which implement the routing mode associated with that name, each of which has 3 routines

  • round(x) which returns the rounded value of x according to the mode
  • div(x, y) which implements a rounded division according to that mode
  • rem(x, y) which implements a remainder according to that mode

But that needs somebody with knowledge of how to use embedded assembler within embedded C within Chapel. And sufficient time. None of which I have in anything like a sufficient quantity.

1 Like

Rather than the use of a module, you can use a simpler approach using param enums

enum roundTiesTo
{
    moot = 0,
    even = 1,
    odd = 2,
    away = 3,
};

enum roundModeOf
{
    chopped = 0,
    up = 1,
    down = 2,
    closest = 3,
};

But I need to get some feedback from other actual users before I put that up for discussion.

I also gave a couple of thoughts to this yesterday. If I had to tackle the problem, my instinct would have been to have a record for each rounding mode and do something like the following.

record Downwards {}
const downwards = new Downwards();

record NearestTiesEven {}
const nearestTiesEven = new NearestTiesEven();

record TowardZero {}
const towardZero = new TowardZero();

// and the others

proc round(x, y, roundingMode = downwards) {
  // ...
}

proc round(x, y, roundingMode : NearestTiesEven) {
  // ...
}

// and the others

The idea is that if the rounding mode to use at each function call is known at compile time, then I think the compiler should already be able to pick what implementation to use and there should not be run-time overhead by having multiple.

I guess this is very similar to your idea with enums? Don't know how they compare.

Your idea is very, very similar to mine. Mine will appear less verbose in some cases, more verbose in others.

Part of the linguistic issue is the fact that rounding towards zero is called truncation. And some people want rounding and truncation to appear to be separate concepts. You also need that the usage of that rounding mode to have a consistent naming when it comes to implementing the remainder operation.
This is not helped that historically, as you rightly noticed, that C's libm used the the unqualified word round to mean "round to nearest with ties going to the adjacent integral value which is furthest away from zero". And yet IEEE 754 uses the unqualified concept of remainder to use "round to nearest with ties going to the adjacent integral value which is even". Do you argue for consistency that is a slave to the standard (which introduces a breaking change) or one which is backward compatible but plays fast and loose with the standard. Tough decisions. And then you have the whole issue that most programming languages used for numerical work use the word mod for a truncated remainder but many others, including Chapel, use this, for a floored remainder.

Achieving consistency in naming in an environment of backwards compatibility, standards compliance, mathematical correctness and tight nomenclature is not easy, if not close to impossible.

Hi Damian,

Thanks for pointing this out! I'll open a PR with the proposed change
to the round documentation.

In answer to your question about the mod family of functions, mod
and % behave differently (and I think % might be closer to what
you're expecting). Brad is better at describing what is going on there,
so I'm hoping he'll chime in.

In answer to your question about AutoMath's contents - yes, we do intend
to include less routines in there. I've been prioritizing other efforts
so didn't get a chance to finish sorting which symbols will be included
by default, but I expect to get back to that in the next six months. I
expect you'll see a message from me on
Math module: if we split the module, what symbols should still be auto included? · Issue #18990 · chapel-lang/chapel · GitHub to get a final
decision on some of the ones there wasn't as clear an answer for.

Thanks,
Lydia

I understand why the mod functions behave differently and have no problems with that. Lots of languages jumble their naming and behavior.

Chapel is consistent in that the mod functions are used for modular integer arithmetic. That decision was made too long ago to change. With other common HPC languages like Fortran and C/C++ doing things very differently, it makes any porting exercise a bit more painful tedious than it could be ... but that is life. I do think that fact needs to be emphasized in documentation.

Apologies for the need for editing this post.

Yeah, that sounds reasonable to me. But I think the wording needs to be
a bit different then. Probably calling out that mod and % behave
differently, and describing what each does. I don't know that I think
our module documentation is the right place to talk about the equivalent
in other languages, though. Perhaps that's something that should go in
an interoperability or user's guide?

Lydia

Basically the mod routines need to emphasize

Be careful - this uses **floored** division (unlike C/C++ or Fortran)

That's all.

And because the % operator behaves like people expect, it needs nothing extra.

Luca,

The following is totally fictional at this stage. Your feedback is appreciated. I have some from users who are Geophysicist and Engineers, i.e. use Chapel to solve things like ODEs, PDEs, optimization, and finite element/volume problems but am waiting on more feedback locally.

Given:

enum roundMode
{
	chopped = 0, // what Mathematicians call truncation
	up = 1,      // what Mathematicians call ceiling or ceil
	down = 2,    // what Mathematicians call floor
	nearest = 3,
};

enum roundTiesTo
{
	moot = 0,
	even = 1,
	odd = 2,
	away = 3,
};

Why do I use moot - I really want to say notApplicable but is very long winded and does not really pass the rules by which we name variables. I could say none but that is a keyword so that will not work.

And assuming that, ... like you thought, ... the default rounding technique in the routines which follow is the mix of

roundMode.nearest + roundTiesTo.even

then the functionality to support all 2019 IEEE 754 (and one newer concept) explicit rounding requests could be

r = round(x)
r = round(x, roundTiesTo.away)
r = round(x, roundTiesTo.odd)
r = round(x, mode = roundMode.up)
r = round(x, mode = roundMode.down)

r = prune(x)

where , the last of those uses rounding Towards Zero, i.e. truncation (and is the equivalent of C's libm trunc() routine).

The prototype of the inline proc round() is

round(x : real(?w), param ties = roundTiesTo.even, param mode = roundMode.nearest)

so that the rounding used can be decided at compile time.

These are not as tight as I would like them to be and the 4th and 5th items need inline proc aliases which are just ceil(x) and floor(x) respectively, all in the name of a loose sense of backwards compatibility and succinctness.

The matching remainder operations are

(r, q) = frem(x, y) // default mode is roundToNearest + roundTiesTo.even

(r, q) = frem(x, y, roundTiesTo.away)
(r, q) = frem(x, y, roundTiesTo.odd)
(r, q) = frem(x, y, mode = roundMode.up)
(r, q) = frem(x, y, mode = roundMode.down)

(r, q) = fmod(x, y)

where again, the last of those uses truncation (and is just C's libm fmod() routine).

The matching division operations are

r = fdiv(x, y) // default mode is roundToNearest + roundTiesTo.even

r = fdiv(x, y, roundTiesTo.away)
r = fdiv(x, y, roundTiesTo.odd)
r = fdiv(x, y, mode = roundMode.up)
r = fdiv(x, y, mode = roundMode.down)

r = prune(x / y); // trivial

where truncated division is trivial and needs no separate routine.

!!!! BREAKING change (in the name of consistency):

... the meaning of the underlying libm routine round() changes

although the user can be trivially warned by the compile on this. There are so many other things that Chapel does differently to C/C++ and Fortran, that adding one more seems like a nothing, especially if there is a warning given by default.

I stopped using trunc() largely to stimulate conversation and because prune() is a simple English word, just like round().

I put in a breaking change again to stimulate conversation and make people think hare. Now, I could avoid the breaking change by making the default roundTiesTo different but then that causes a clash with the standard's default mode for the remainder operation (which again can be flagged easily by the compiler if it sees it).

I am not a fan of names like fdivfloor() and fdivceil() but there is a valid argument for them.

My 2c - i.e. personal opinion only (although some people agree with me).

1 Like

Brain fade - just changed ceiling -> ceil in preceding.

On the question about whether to support different to-int rounding options via an optional argument or by having a submodule per option, I lean more toward the optional argument because it makes the desired behavior more evident at the callsite and permits all the implementations to reside in a single routine in a single module (which should also make the documentation cleaner / more centralized as well).

I'd also probably lean toward an enum over a record to choose the rounding mode because it feels lighter-weight, permits compile-time reasoning, and more like a better match for the pattern of "choose one of these n options from the menu of choices". @lucaferranti, to address your implied question, the use of param in Damian's argument lists suggests that the value has to be known at compile-time (we could also provide non-param overloads for flexibility with an execution-time cost if there was interest, but I'm guessing there won't be).

But, it should be noted that I'm not a likely deep user of these routines, so I'm happy to defer to others who are.

@damianmoz, when you say:

With other common HPC languages like Fortran and C/C++ doing things very differently, it makes any porting exercise a bit more painful tedious than it could be ..

What difference w.r.t. C/C++ are you referring to? I feel like every time I go searching for a C mod() library function I don't find one. And I think you've agreed that Chapel % matches C/C++ %. Are you saying "Chapel mod() doesn't match C/C++ %?" (which I agree with).

And then:

I put in a breaking change again to stimulate conversation and make people think hare.

Am I understanding correctly that your motivation for having round() not behave like libm round() is to provide what humans may colloquially think of round() as doing? Or is it something else that I missed in catching up on this thread? Thanks.

On tangential topics:

Please note Duck-Duck-Go finds the 1.27 documentation, not the 1.28. But, click the link and you end up at 1.28.

I don't know the first thing about what it would take to have Duck-Duck-Go re-scan our site to update their cached information. A quick search suggests that they piggyback off of Bing, which also seems to still be showing cached 1.27 pages; and the Bing page suggests just sitting tight and waiting for the BingBot to re-crawl our page.

-Brad

1 Like
On the question about whether to support different to-int rounding
options via an optional argument or by having a submodule per option,
I lean more toward the optional argument because it makes the desired
behavior more evident at the callsite and permits all the implementations
to reside in a single routine in a single module (which should also make
the documentation cleaner / more centralized as well).

I agree. See my latest documentation ... tomorrow.

I would also probably lean toward an enum over a record to choose the 
rounding mode because it feels lighter-weight, permits compile-time reasoning,
and more like a better match for the pattern of "choose one of these
n options from the menu of choices".

I agree. See my latest documentation .... tomorrow.

@lucaferranti, to address your implied question, the use of param in Damian's argument lists suggests that the value has to be known at compile-time (we could also provide non-param overloads for flexibility with an execution-time cost if there was interest, but I'm guessing there won't be).

Sorry, I was trying to keep the post short. A non-param version will be written. Excuse my laziness.

What difference w.r.t. C/C++ are you referring to?  I feel like every
time I go searching for a C `mod()` library function I don't find one.

Please do a

man fmod

on your Linux machine. Do not look for modf.

And I think you've agreed that Chapel % matches C/C++ %.

Yes I agree.

Are you saying "Chapel `mod()` doesn't match C/C++ `%`?" (which I agree with).

Almost. The mod() in Chapel with floating point arguments which uses floored division does not agree with the fmod() in C (which has floating point arguments) which uses truncated division.

We just need to make the differences more obvious. And yes, silly me read the definition with floor of Chapel's mod() years ago but it did not sink in and I spennt ages tracking the problem. And I made the mistake multiple times.

And then you quote me correctly

I put in a breaking change again to stimulate conversation and
make people think here.

You then say

Am I understanding correctly that your motivation for having `round()` not
behave like `libm` `round()` is to provide what humans may colloquially
think of `round()` as doing?

Yes. We are on the same page - again (this is getting scary!!)

The main advantage of using round-to-even is that it is less biased from a statistically perspective than ties away from zero. Ties-to-Even is bankers or statistician's rounding. Ties Away from Zero is called commercial rounding for the complete picture.

I did say

I did not know what the appetite was for a **breaking** change.

IEEE 754's

a) default rounding rule is ties-to-even, and its
b) remainder operation uses ties-to-even rounding.

Hence why I suggested that a bare-bones (or a default variant of)round() routine should too. But a warning is needed (by default) if the compiler sees it to help people affected by this breaking change.

I will send an updated PDF document which addresses the holes I left in this discussion. Sorry, too many other things on my plate at the moment.

The choice of the name round() was made in the C library soooo long ago. And with so many variations of round-to-nearest in mathematics, the choice made was probably just done for convenience. Certainly the C library could not reverse that decision after such a long history.

On tangential topics:

Please note Duck-Duck-Go finds the 1.27 documentation, not the
1.28. But, click the link and you end up at 1.28.

Some of us are using this Ducky search engine to avoid Google data harvesting. I just mentioned it in passing because I was fishing with this for Chapel documentation on mod and noticed the issue.

I was about to say ... as promised. But I cannot upload a PDF.

[edit: I've uploaded Damian's PDF and modified the site's settings to permit users to going forward. -Brad]

round.pdf (31.0 KB)

This is a very interesting discussion!

Thanks Brad for clarifying my question, then I am also thinkin an enum approach could be better. A good thing about enums is that words like up, down, nearest, which are fairly common, don't get reserved (as it would in the record approach).

I have a question, is there a reason for having two separate enums? One for the rounding and one for the tie? In principle, one needs to specify the tie only for round to nearest, what would you think about having

enum roundMode
{
  chopped = 0, // what Mathematicians call truncation
  up = 1,      // what Mathematicians call ceiling or ceil
  down = 2,    // what Mathematicians call floor
  nearestTiesEven = 3,
  nearestTiesOdd = 4, 
  nearestTiesAway = 5
};

thoughts on this?

Here is some iteratively developed discussion logic that may help guide choices. It has seen some feedback by other users of floating point number crunching software, albeit a small sample.

What are we trying to do here? We want to provide the functionality for the General Operations of 5.3.1 of the 2019 IEEE 754 standard:

roundToImtegralTiesToEven,
roundToImtegralTiesToAway,
roundToIntegralTowardsPositive,
roundToIntegralTowardsNegative,
roundToIntegralTowardsZero, and
roundToIntegralExact

Let us eliminate the last for now. C/C++ implements that rint. Leave it at that. It is a very different beast anyway as it relies on the currently active rounding mode.

like most standards, correct language is not top of the priority list. In our programs, readability (which includes correct language) is top. I use Oxfords Thesaurus, The Economists style guide, Fowlers Modern English usage to help my choice of nomenclature. I then use the Yale Professor Strunks "The Elements of Style" as the definitive reference so my American English is tolerable. I check with Merriam-Webster when I am really desperate.

The first thing we notice is that the IEEE 754 names are unclear. The first two are rounding to the Nearest non-fractional floating point number between which a (potentially) fractional number will fall. The standard calls these non-fractional numbers an Integral floating point number, quite a different thing to what Chapel says is an integral number. In the event of a tie, there is a choice of two. The last three are directional roundings and there is no choice in which adjoining integral is chosen. The direction is mandated. So it is not the nearest integral we want in this case but the next integral value in some specified direction. The word next sounds too much like a keyword and is too close to Nearest, so a 7-letter word meaning next or subsequent or following is Ensuing. It is not too important as that name will diseppaer later. I also fixed the Americanism in the word Toward in the above.

So we want an enum type that can represent:

roundToNearestIntegralTiesToEven,
roundToNearestIntegralTiesToAway,
roundToEnsuingIntegralTowardsPositive,
roundToEnsuingIntegralTowardsNegative,
roundToEnsuingIntegralTowardsZero,

These are way too long for an identifier, especially when it will appear in some sort of mathematical expression as that are pretty well unreadable.

Use the first 7 letters as the enum name to give us global scope and avoid namespace clashes, and then drop the word Integral because rounding always implies Integral. So we now have, if we want a single enum to represent rounding modes:

roundTo.NearestTiesToEven,
roundTo.NearestTiesToAway,
roundTo.EnsuingTowardsPositive,
roundTo.EnsuingTowardsNegative,
roundTo.EnsuingTowardsZero,

Each of these, complete with the enum name are effectively an identifier. Still too long. So, what can we strip without compromising the meaning?? To get an idea of how they might appear in practice, we will exploit these in the context of the routine frem as well as with the routine round(). Why? Because such usage proves whether what we have is truly readable when we use rounding within an operation. So we have the rounding mode used in a remainder operation as an argument to the routine frem, as in

r = frem(x, y, some IEEE 754 rounding mode);

Let us drop "EnsuingTo" from the last 3 and drop the capital from Nearest.

roundTo.nearestTiesToEven, // 7+17 = 24 characters long
roundTo.nearestTiesToAway, // 7+17 = 24 characters long
roundTo.wardsPositive, // 7+13 = 20 characters long
roundTo.wardsNegative, // 7+13 = 20 characters long
roundTo.wardsZero, // 10+9 = 27 characters long

which yields:

(r, q) = frem(x, y, roundTo.nearestTiesToEven);
(r, q) = frem(x, y, roundTo.nearestTiesToAway);
(r, q) = frem(x, y, roundTo.wardsPositive);
(r, q) = frem(x, y, roundTo.wardsNegative);
(r, q) = frem(x, y, roundTo.wardsZero);

Even in the above simple assignments, we have 40 characters which is ugly and affecting readability. Those 24 character identifiers are basically unreadable. In an expression, it will be worse. We find a 16(20) characters is (almost) tolerable in terms of readability for an identifier to be used in a long mathematical expression.

So we definitely need a qualifier to handle the case of a tie, i.e when the fraction part is 0.5. Even stripping the To after Ties is insufficient. So, the top two must be split into two.

Handling the second pair which are right on the limit, we seek solace in using the number line used in floating point arithmetic. So, the ensuing integer in the Positive direction beyond a floating point number is just the integer ABOVE it, and that in the Negative direction is just that BELOW it. So

roundTo.nearest + needs a qualifier
roundTo.nearest + needs a qualifier
roundTo.above,
roundTo.below,
roundTo.wardsZero,

You could use up and down instead of above and below but there is too much risk that somebody will want to use the former pair for keywords. So we do not want to buy ourselves future namespace clash grief. Sorry, I left those up and down bombs in the earlier document hoping somebody would challenge their choice.

// !!! Aside
I would like to use rem by itself, but that might be too greedy if someone wants that keyword as an operator at some future stage. We could also use the long word remainder, although it may be too unwieldy for a function name which will often appear in long expressions.

Note that q in the above is related to the quotient, it is likely not the quotient, that being too hard to compute in a remainder operation of two floating point numbers in a general sense.
// !!! end Aside.

Let us address rounding towards zero by making it go away. Overload % for a remainder of two floating point numbers x and y using rounding towards Zero (which is just truncation as done in the C routine fmod for floating point numbers and as implemented in C/C++ sand Chapel for integers with the % operator) and we have

r = x % y; // compute the remainder of x and y using truncated division

and in the interim, while we are implementing that (new) operation, just use

r = fmod(x, y);

So we are left with identifiers for the rounding modes which are not overly long

roundTo.nearest + qualifier
roundTo.nearest + qualifier
roundTo.above,
roundTo.below,

Not perfect because nearest is an adjective while above and below are adverbs.

The qualifiers for use with the first two, along with the non IEEE 754 strategy of using the odd integral value nearest the tie, will be

roundTiesTo.even
roundTiesTo.away
roundTiesTo.odd

Lean and Mean. Let's try is

(r, q) = frem(x, y); // IEEE 754 Standard 5.3.1
(r, q) = frem(x, y, roundTiesToAway);
(r, q) = frem(x, y, mode = roundTo.above);
(r, q) = frem(x, y, mode = roundTo.below); // equivalent to Chapel's mod(x, y)

Note overly long-winded.

Now, does this work with the round routine, where round is a verb?

r = round(x); // to nearest, ties to even is the default
r = round(x, roundTiesTo.away); // to nearest, ties to away
r = round(x, roundTiesTo.odd); // to nearest, ties to odd
r = round(x, mode = roundTo.above); // round Towards Positive
r = round(x, mode = roundTo.below); // round Towards Negative

Seems to. The latter two should have the aliases of ceil and floor`, which again are both English language verbs.

What happened to our rounding Towards Zero, or truncation. While IEEE 754 specifies truncation as a kind of rounding, some mathematicians disagree. Let us not complicate things and keep it separate, i.e retain the status quo

r = trunc(x);

However, I have hated trunc ever since I saw it in Pascal many years ago. We could find a better English verb, say 5-letters like round, like say prune for this task, or do we just use the full word truncate and avoid the abbreviation.

r = prune(x);
r = truncate(x);

The former is also a softer sounding word in English like round. We could even use the word chop but this is a little bit loose, and again it has a harsh sound and reads poorly. Hmmm, maybe prune might be too radical!!!!

Finally, should the enumtype be called roundingTo or roundingMode because that is correct English. Or, because it is done as an optional argument, should it be roundModeIs. Or something else????

1 Like

Note that I've edited Damian's post above to add the PDF that he was hoping to add at that point. Sorry for the delay and trouble—users should be able to attach PDFs going forward.

-Brad

1 Like