ORNL / ORNL/cpp-proposals-pub

Type concerns about scaled(a, m) (conversion/mixed precision; composition of scaled())

Open
#249 9 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
HTML
Stars
29
Forks
26
PR merge metrics
No merged PRs in 30d

Description

Conversation with @mhoemmen where I initially brought this up:

Phil Miller 6:36 PM
There’s an interesting quirk to the folding of nested scaled applications - the product of the scaling factors will be computed in a different type than the underlying right-associated multiplication against the ElementType would produce. I think this is mostly fine and good, but I can come up with at least one example where this can lead to bad behavior. Consider mdspan<double,…> m; scaled(1<<20, scaled(1 << 20, m)) - the ProductScalingFactor is int, and its value will overflow, yielding UB, rather than scaling by 2^40 as it would appear it should, and readily could if the calculation were in double . Even unsigned 1u << 20 in both instances would produce wrong results, though not UB.

Mark Hoemmen 8:44 PM
[snip] We definitely need to give those proxy references more attention in the proposal. I would ideally like those expressions to behave as if they were typed out by hand in a for loop.

Phil Miller 10:35 PM
I think the wording as written gets you the behavior “as if they were typed out by hand in a for loop” - the multiplication will be left-associated, and so the product of all the scaling factors will be formed in whatever type they promote/convert to before they touch the element type. It’s not unreasonable that multiplication by a stack of integer factors that ultimately overflow an int should be something for users to avoid.
It feels weird to me, I think, because the scaled(b, scaled(a, m)) expression seems to naturally correspond to a right-associated explicit parenthesization of the same - e.g. (b * (a * m))
I just thought of another case that would potentially suffer from this, just barely less contrived. Consider a painfully naive LU factorization, that operates element-by-element rather than in blocks. With each value d along the diagonal, the remainder of the matrix will be scaled by its reciprocal 1/d. The product of those reciprocals could go subnormal or underflow, even if the values being scaled would be fine as actually divided step-wise. This doesn’t involve any type conversions, just the quirk of evaluation order.
The commonality between these is that multiple layers of scaled may be composed in settings where the product of the scaling factors runs up against numeric representation limits, where the value being scaled would not.

Mark Hoemmen 7:26 AM
"Multiple layers of scaled " is perhaps the key phrase here. The expression templates were Christian's idea, to reduce the number of parameters that BLAS functions have to take, and to make it possible to express alpha * x without needing users to supply a 1.0. It's a good idea, but it's caused a bit of divergence from the BLAS (P1673R8 has a discussion about this with regards to Triangle). The idea was always that scaled was a way to express setting a scaling factor, and that multiple instances of scaled in an expression are an antipattern. I'm more pessimistic about users' willingness to respect design intent.
It would be reasonable to expect scaled(b, scaled(a, m)) to parenthesize like b * (a * m[k]).

Mark Hoemmen
9:25
I'm more pessimistic about users' willingness to respect design intent.

Phil Miller 9:25 AM
In any case, I think even with mis-use, actual problems resulting from it will be rare
Lots of things have to go wrong together for it to work out badly

Mark Hoemmen 9:26 AM
C++ Standard Library design is a bit more like nuclear power plant control system design than library design

Phil Miller 9:30 AM
Eh, there are plenty of places where it’s perfectly well accepted that misuse can lead to things going wrong. NPP design review doesn’t really accept “results in undefined behavior”

Mark Hoemmen 9:31 AM
true -- on the other hand, i would not like scaled(b, scaled(a, x)) to be UB ; - )

Phil Miller 9:32 AM
The examples I’ve come up with have all been pretty extreme corner cases. The common cases of a few static factors or reasonable numbers from input data should be fine
And it’s all limited by only mattering when the left-association breaks, but the right-association would be fine
Something to think about, but my inclination is that it should probably be noted as a hazard, and left as is, because it is an important optimization

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the discussion about nested scaled() expressions and the P1673R8 discussion mentioned in the issue. Determine whether the proposal should change or document the evaluation and conversion behavior; done requires an explicit resolution for composed scaling factors.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.