www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Floating point rounding modes: we should restrict them slightly

reply Don <nospam nospam.com> writes:
A quiz:
Conside a function
real foo() {....}
This is not pure, so that it may side-effects, but even so, are listings 
one and two equivalent?

LISTING ONE:

real x = foo(1) + foo(2) + foo(3);

LISTING TWO:

real x1 = foo(1);
real x2 = foo(2);
real x3 = foo(3);
real x = x1 + x2 + x3;

In C and C++ (and currently in D), they are NOT equivalent!
foo() is allowed to change the floating-point rounding mode, so it could 
change the meaning of '+'.
This is quite ridiculous. This is a 'freedom' that comes with a heavy 
price tag.
Actually doing this would make code nearly impossible to debug, and I 
don't think anyone is stupid enough to actually do this
...but as long as it's theoretically possible, it prevents the compiler 
from doing useful optimisations.
Disallowing this kind of obscure, dangerous nonsense is, I think, part 
of the core mission of D.

PROPOSAL:
Change the spec by adding the line to float.html:
"If the floating-point rounding mode is changed within a function, it 
must be restored before the function exits. If this rule is violated 
(for example, by the use of inline asm), the rounding mode used for 
subsequent calculations is undefined."

This slight tightening of semantics can have a significant benefit for 
all floating-point code.
Sep 10 2009
next sibling parent reply #ponce <aliloko gmail.com> writes:
Why not imposing a rounding mode (ex :  toward minus infinity) ?
And then disallowing to change it in assembly.

I mean, you can still do :
  round(x) as floor(x + 0.5L)
  ceil(x) as floor(x + 1.0L)

Iit would cost some precision with large floating-point number, but not much
since rounding such large numbers has few meaning.

It would clear up the rounding-mode mess a bit.
Sep 10 2009
next sibling parent Don <nospam nospam.com> writes:
#ponce wrote:
 Why not imposing a rounding mode (ex :  toward minus infinity) ?
 And then disallowing to change it in assembly.

You can't impose a rounding mode in a system language: rounding mode is a crucial feature in a few rare but very important cases. The trick is to allow those cases without polluting everything else. This tiny proposal gets rid of most the damage. However, my previous proposal for fixing the interaction of 'pure' with rounding modes and exception handling, would allow the rounding mode to be round-to-nearest almost everywhere, and changeable only in impure functions inside modules marked module(advancedfloatingpoint). This could give performance benefits everywhere outside those modules.
Sep 10 2009
prev sibling next sibling parent "Jimbob" <jim bob.com> writes:
"#ponce" <aliloko gmail.com> wrote in message 
news:h8adr2$23p3$1 digitalmars.com...
 Why not imposing a rounding mode (ex :  toward minus infinity) ?
 And then disallowing to change it in assembly.

 I mean, you can still do :
  round(x) as floor(x + 0.5L)
  ceil(x) as floor(x + 1.0L)

 Iit would cost some precision with large floating-point number, but not 
 much since rounding such large numbers has few meaning.

 It would clear up the rounding-mode mess a bit.

There's more to rounding modes than just converting / rounding to integer. Rounding affects all arithmetic operations, and whether an expresion evaluates to 0.97999 or .97998 can be important in some cases.
Sep 10 2009
prev sibling parent Lionello Lunesu <lio lunesu.remove.com> writes:
#ponce wrote:
 Why not imposing a rounding mode (ex :  toward minus infinity) ?
 And then disallowing to change it in assembly.
 
 I mean, you can still do :
   round(x) as floor(x + 0.5L)
   ceil(x) as floor(x + 1.0L)
 
 Iit would cost some precision with large floating-point number, but not much
since rounding such large numbers has few meaning.
 
 It would clear up the rounding-mode mess a bit.

Shouldn't ceil(1.0) return 1.0, and wouldn't floor(1.0 + 1.0L) return 2.0? L.
Sep 10 2009
prev sibling next sibling parent reply Rainer Deyke <rainerd eldwood.com> writes:
Don wrote:
 Change the spec by adding the line to float.html:
 "If the floating-point rounding mode is changed within a function, it
 must be restored before the function exits.

Even if that function is called 'fesetround'? -- Rainer Deyke - rainerd eldwood.com
Sep 10 2009
parent Don <nospam nospam.com> writes:
Rainer Deyke wrote:
 Don wrote:
 Change the spec by adding the line to float.html:
 "If the floating-point rounding mode is changed within a function, it
 must be restored before the function exits.

Even if that function is called 'fesetround'?

Obviously not. In fact, I'd hope to even disallow fesetround() since it's too easy to abuse, and instead have an RAII struct to restore the mode automatically. But that's just syntax sugar.
Sep 10 2009
prev sibling next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2009-09-10 04:24:38 -0400, Don <nospam nospam.com> said:

 PROPOSAL:
 Change the spec by adding the line to float.html:
 "If the floating-point rounding mode is changed within a function, it 
 must be restored before the function exits. If this rule is violated 
 (for example, by the use of inline asm), the rounding mode used for 
 subsequent calculations is undefined."
 
 This slight tightening of semantics can have a significant benefit for 
 all floating-point code.

I perfectly agree. I'm just wondering how to enforce it in the language. Later in the thread you say:
 ... have an RAII struct to restore the mode automatically. But that's 
 just syntax sugar.

But how do you prevent someone from writing: auto s = new roundingMode(...); The only way you can really make sure it's scoped to a specific function is to call it from another function: R performWithRoundingMode(R)(flag roundingMode, lazy R result) { flag oldRoundingMode = getRoundingMode(); setRoundingMode(roundingMode); try return result; finally setRoundingMode(oldRoundingMode); } auto i = performWithRoundingMode(TOWARDS_ZERO, 12 * 12); Here you can't escape the scoping requirements. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Sep 10 2009
parent Don <nospam nospam.com> writes:
Michel Fortin wrote:
 On 2009-09-10 04:24:38 -0400, Don <nospam nospam.com> said:
 
 PROPOSAL:
 Change the spec by adding the line to float.html:
 "If the floating-point rounding mode is changed within a function, it 
 must be restored before the function exits. If this rule is violated 
 (for example, by the use of inline asm), the rounding mode used for 
 subsequent calculations is undefined."

 This slight tightening of semantics can have a significant benefit for 
 all floating-point code.

I perfectly agree. I'm just wondering how to enforce it in the language.

Yes, it's not so obvious how to enforce it. However, in reality it's a rarely-used, system level feature, so relying on convention probably isn't too terrible in the short term.
 
 Later in the thread you say:
 
 ... have an RAII struct to restore the mode automatically. But that's 
 just syntax sugar.

But how do you prevent someone from writing: auto s = new roundingMode(...);

scope _should_ take care of that. I'm not sure that it does. Still, we just need to make it easy to do the right thing, and difficult to do the wrong thing.
 The only way you can really make sure it's scoped to a specific function 
 is to call it from another function:
 
     R performWithRoundingMode(R)(flag roundingMode, lazy R result)
     {
         flag oldRoundingMode = getRoundingMode();
         setRoundingMode(roundingMode);
         try
             return result;
         finally
             setRoundingMode(oldRoundingMode);
     }
 
     auto i = performWithRoundingMode(TOWARDS_ZERO, 12 * 12);
 
 Here you can't escape the scoping requirements.

Hmmm. Not ideal, but could be worse. But anyway, the important thing is to codify in the spec that the floating point state must not change randomly. For discussing syntax, I think we really need an interval arithmetic module, so that we can ensure it all works well.
Sep 10 2009
prev sibling next sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Don wrote:
<snip>
 LISTING ONE:
 
 real x = foo(1) + foo(2) + foo(3);
 
 LISTING TWO:
 
 real x1 = foo(1);
 real x2 = foo(2);
 real x3 = foo(3);
 real x = x1 + x2 + x3;
 
 In C and C++ (and currently in D), they are NOT equivalent!
 foo() is allowed to change the floating-point rounding mode, so it could 
 change the meaning of '+'.

This is just one aspect of it. You could also call a pure function, change the rounding mode, and then call the pure function again on the same arguments. The value returned would change, thereby violating the purity. There has been some talk about pure functions in relation to locales: http://d.puremagic.com/issues/show_bug.cgi?id=3057 http://www.digitalmars.com/d/archives/digitalmars/D/std.locale_85081.html Floating point settings are just another case of the same thing, except that currently violations in relation to the former are allowed. Maybe the right solution is some kind of hidden parameter mechanism.... Stewart.
Sep 11 2009
next sibling parent reply Don <nospam nospam.com> writes:
Stewart Gordon wrote:
 Don wrote:
 <snip>
 LISTING ONE:

 real x = foo(1) + foo(2) + foo(3);

 LISTING TWO:

 real x1 = foo(1);
 real x2 = foo(2);
 real x3 = foo(3);
 real x = x1 + x2 + x3;

 In C and C++ (and currently in D), they are NOT equivalent!
 foo() is allowed to change the floating-point rounding mode, so it 
 could change the meaning of '+'.

This is just one aspect of it. You could also call a pure function, change the rounding mode, and then call the pure function again on the same arguments. The value returned would change, thereby violating the purity.

I already presented a proposal for that. This proposal is primarily for functions which are not pure.
 There has been some talk about pure functions in relation to locales:
 
 http://d.puremagic.com/issues/show_bug.cgi?id=3057
 http://www.digitalmars.com/d/archives/digitalmars/D/std.locale_85081.html
 
 Floating point settings are just another case of the same thing, except 
 that currently violations in relation to the former are allowed.

There's a fundamental difference between them: the floating point settings are a hardware feature and it is IMPOSSIBLE to avoid them. You can choose not to use locale settings. Or, you can pass them as a parameter, which doesn't work for floating point settings. Please do not get sidetracked on pure functions, it is orthogonal to this issue.
Sep 13 2009
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Don wrote:
<snip>
 Floating point settings are just another case of the same thing, 
 except that currently violations in relation to the former are allowed.

There's a fundamental difference between them: the floating point settings are a hardware feature and it is IMPOSSIBLE to avoid them.

Actually, you _can_ avoid changing the floating point settings in the course of an average program. But still....
 You can choose not to use locale settings. Or, you can pass them as a 
 parameter, which doesn't work for floating point settings.

As long as it's the built-in arithmetic operators you're concerned about. But that said, wrapping every arithmetic operation in a function to purify it of dependence on floating point settings wouldn't be ideal.
 Please do not get sidetracked on pure functions, it is orthogonal to 
 this issue.

I'm not sure about this. ISTM arithmetic operators are essentially pure functions, and so the compiler may treat them as such. Stewart.
Sep 13 2009
parent reply Don <nospam nospam.com> writes:
Stewart Gordon wrote:
 Don wrote:
 <snip>
 Floating point settings are just another case of the same thing, 
 except that currently violations in relation to the former are allowed.

There's a fundamental difference between them: the floating point settings are a hardware feature and it is IMPOSSIBLE to avoid them.

Actually, you _can_ avoid changing the floating point settings in the course of an average program. But still....

The hardware dependency is still there, in every function which uses floating point. And the compiler must assume that dependency.
 
 You can choose not to use locale settings. Or, you can pass them as a 
 parameter, which doesn't work for floating point settings.

As long as it's the built-in arithmetic operators you're concerned about.

Of course. There's nothing else to floating point, other than the built-in operators. And this is the problem with unconstrained rounding modes: they influence EVERYTHING.
But that said, wrapping every arithmetic operation in a function 
 to purify it of dependence on floating point settings wouldn't be ideal.

It's worse than that. Every function which might call a function which might use floating point would have to pass the parameter. It doesn't work.
 Please do not get sidetracked on pure functions, it is orthogonal to 
 this issue.

I'm not sure about this. ISTM arithmetic operators are essentially pure functions, and so the compiler may treat them as such.

No, they always depend on the FP settings, and assuming they are pure functions is not permitted in C, C++, or D. C allows the rounding mode to change at any time. This proposal: Rounding mode changes must only affect callees, not callers. The pure function memoisation issue: Certain functions may be marked as 'pure', even though they use floating point (and are therefore dependent on the FP state). The compiler must be able to prevent memoisation of those functions in cases where the rounding mode may have changed. (Or, equivalently, the memoisation must include the FP state). I've provided very simple solutions for both of these. I'm getting pretty irritated that people are acting as though these are difficult problems and trying to come up with convoluted solutions. Impose minimal semantics and it becomes trivial.
Sep 14 2009
next sibling parent reply Rainer Deyke <rainerd eldwood.com> writes:
Don wrote:
 The pure function memoisation issue:
 Certain functions may be marked as 'pure', even though they use floating
 point (and are therefore dependent on the FP state). The compiler must
 be able to prevent memoisation of those functions in cases where the
 rounding mode may have changed. (Or, equivalently, the memoisation must
 include the FP state).

Don't forget that "uses floating point" is a transitive property. Any pure function that calls a pure-but-unmemoisable function is itself pure-but-unmemoisable. The "pure" annotation is now used for two related but different categories of functions with different properties, and the compiler needs some way to keep track of which is which across module boundaries. If the compiler can do that, then it can use the same mechanism to differentiate between pure and non-pure functions, and the "pure" annotation becomes redundant. Is there any real need to treat floating point state differently from other global state in regard to "pure"? -- Rainer Deyke - rainerd eldwood.com
Sep 14 2009
parent reply Don <nospam nospam.com> writes:
Rainer Deyke wrote:
 Don wrote:
 The pure function memoisation issue:
 Certain functions may be marked as 'pure', even though they use floating
 point (and are therefore dependent on the FP state). The compiler must
 be able to prevent memoisation of those functions in cases where the
 rounding mode may have changed. (Or, equivalently, the memoisation must
 include the FP state).

Don't forget that "uses floating point" is a transitive property. Any pure function that calls a pure-but-unmemoisable function is itself pure-but-unmemoisable.

This is incorrect. You've got it backwards. It's not "uses floating point" that's the issue, it's "may use non-default floating point". A memoisable floating point function can only use default floating point. So, if it calls a non-memoisable pure function, it'll be using it in the default state (even though that function can accept non-default state). The transitivity propagates in the reverse direction: memoisability is transitive, non-memoisability is not. Non-memoisable functions cannot call memoisable ones without first ensuring that everything is back into the default state. The "pure" annotation is now used for two
 related but different categories of functions with different properties,
 and the compiler needs some way to keep track of which is which across
 module boundaries.  If the compiler can do that, then it can use the
 same mechanism to differentiate between pure and non-pure functions, and
 the "pure" annotation becomes redundant.

It's true that you could provide a similar loophole for any specific global variable. But you'd need a *very* good reason.
 Is there any real need to treat floating point state differently from
 other global state in regard to "pure"?

Yes. Because it's all-pervasive. Unless you treat it specially, you cannot use floating point in any pure function. That creates a restriction which is so severe that 'pure' becomes useless. Something like the following template function could not be pure: T add(T a, T b) { return a + b; } I do not believe that any other global state has an influence which is in the same league.
Sep 14 2009
parent reply Rainer Deyke <rainerd eldwood.com> writes:
Don wrote:
 Rainer Deyke wrote:
 Don't forget that "uses floating point" is a transitive property.  Any
 pure function that calls a pure-but-unmemoisable function is itself
 pure-but-unmemoisable.

This is incorrect.

Then I must have misunderstood your proposal. Either you did a poor job of explaining it, or I did a poor job of trying to understand it, or it's not as simple as you make it out to be. Since I'm not really interested in discussing the details of floating point rounding modes and memoisation, I'll just leave it at that.
 Is there any real need to treat floating point state differently from
 other global state in regard to "pure"?

Yes. Because it's all-pervasive.

It only affects floating point operations. Other pieces of global state (e.g. the global locale) affect other operations. Once you make a special case for floating point mode, it's easy to argue that the special case should be extended to other pieces of global data. A general solution generally beats special cases. Or maybe I'm completely overthinking this.
 Unless you treat it specially, you
 cannot use floating point in any pure function. That creates a
 restriction which is so severe that 'pure' becomes useless. Something
 like the following template function could not be pure:
 
 T add(T a, T b) { return a + b; }

In the presence of structs and class executing arbitrary code in opPlus, you can't make this pure anyway. Unless you force opPlus itself to be pure, which is almost certainly too restrictive for the current definition of pure. -- Rainer Deyke - rainerd eldwood.com
Sep 14 2009
parent reply Don <nospam nospam.com> writes:
Rainer Deyke wrote:
 Don wrote:
 Rainer Deyke wrote:
 Don't forget that "uses floating point" is a transitive property.  Any
 pure function that calls a pure-but-unmemoisable function is itself
 pure-but-unmemoisable.


Then I must have misunderstood your proposal. Either you did a poor job of explaining it, or I did a poor job of trying to understand it, or it's not as simple as you make it out to be.

 Since I'm not really interested in discussing the details of floating
 point rounding modes and memoisation, I'll just leave it at that.

The description I just gave of it was The thing you missed is that the non-memoisable pure functions can only read the global state.
 
 Is there any real need to treat floating point state differently from
 other global state in regard to "pure"?


It only affects floating point operations. Other pieces of global state (e.g. the global locale) affect other operations.

But you ALWAYS have a choice about that. You can rewrite functions which are independent of locale, or you can pass the locale as a parameter. With floating point, you don't have that option. BTW, global locales suck, big time. The idea that you can specify the formatting in the form of a locale is clearly the creation of someone who had never worked in an international environment. (And any locale which includes 'currency' is clearly the creation of an idiot). Once you make a
 special case for floating point mode, it's easy to argue that the
 special case should be extended to other pieces of global data.

No, because this is a hardware special case. All the other cases can be dealt with in software.
 Or maybe I'm completely overthinking this.
 
 Unless you treat it specially, you
 cannot use floating point in any pure function. That creates a
 restriction which is so severe that 'pure' becomes useless. Something
 like the following template function could not be pure:

 T add(T a, T b) { return a + b; }

In the presence of structs and class executing arbitrary code in opPlus, you can't make this pure anyway. Unless you force opPlus itself to be pure, which is almost certainly too restrictive for the current definition of pure.

You're right. Bad example. But seriously, people give sqrt(x) as the classic example of a pure function.
Sep 15 2009
parent reply Rainer Deyke <rainerd eldwood.com> writes:
Don wrote:
 The thing you missed is that the non-memoisable pure functions can only
 read the global state.

No, I saw that. But if a (pure) function calls another (pure) function that depends on global state, then the first (pure) function also depends on global state. int global_state; pure int f() { return global_state; } pure int g() { return f(); } int h() { global_state = 5; return g(); }
 But you ALWAYS have a choice about that. You can rewrite functions which
 are independent of locale, or you can pass the locale as a parameter.
 With floating point, you don't have that option.

True. Unless you add functions/operators that use a fixed rounding mode to the language.
 BTW, global locales suck, big time. The idea that you can specify the
 formatting in the form of a locale is clearly the creation of someone
 who had never worked in an international environment. (And any locale
 which includes 'currency' is clearly the creation of an idiot).

No argument there. -- Rainer Deyke - rainerd eldwood.com
Sep 15 2009
parent reply Don <nospam nospam.com> writes:
Rainer Deyke wrote:
 Don wrote:
 The thing you missed is that the non-memoisable pure functions can only
 read the global state.

No, I saw that. But if a (pure) function calls another (pure) function that depends on global state, then the first (pure) function also depends on global state. int global_state; pure int f() { return global_state; } pure int g() { return f(); } int h() { global_state = 5; return g(); }

Ah. The important thing that makes this work is that the global FP state has a clearly defined default. (round-to-nearest, full precision, no floating point exceptions enabled). Part of the implicit contract of calling a 'memoisable pure' function is that the global FP state is in the default state. No pure function can change the state. So once you're in a memoisable pure function, you can safely call any non-memoisable pure function, in the knowledge that the default mode is in effect.
 
 But you ALWAYS have a choice about that. You can rewrite functions which
 are independent of locale, or you can pass the locale as a parameter.
 With floating point, you don't have that option.

True. Unless you add functions/operators that use a fixed rounding mode to the language.

Yes, there's a few impractical ways it could be made to work (passing the rounding mode as an explicit parameter to every function is another option). Actually there's a related issue which I haven't mentioned: the floating point exception sticky flags are effectively a global variable, written to by every floating point operation which you perform. It can be covered in the same way: there's no guarantee that the flags will be correct in the presence of memoisation, so you need to be able to indicate to the compiler or runtime that the flags returned from this function call are important to you.
Sep 16 2009
parent reply Rainer Deyke <rainerd eldwood.com> writes:
Don wrote:
 Ah. The important thing that makes this work is that the global FP state
 has a clearly defined default. (round-to-nearest, full precision, no
 floating point exceptions enabled). Part of the implicit contract of
 calling a 'memoisable pure' function is that the global FP state is in
 the default state.

Then I'm not sure it makes to treat "memoisable" as a property of a (pure) function. It seems more like a property of the context from which the function is called. *All* pure function are memoisable if you always call them with the floating point rounding mode set to the default. Conversely, all pure functions can be called with an arbitrary rounding mode if you treat them an unmemoisable. Some function - those that don't perform any floating point calculation, directly or indirectly - are independent from floating point state, and could even memoised even when called with an arbitrary floating point state. But there is no good way to automatically detect these functions. -- Rainer Deyke - rainerd eldwood.com
Sep 16 2009
parent Don <nospam nospam.com> writes:
Rainer Deyke wrote:
 Don wrote:
 Ah. The important thing that makes this work is that the global FP state
 has a clearly defined default. (round-to-nearest, full precision, no
 floating point exceptions enabled). Part of the implicit contract of
 calling a 'memoisable pure' function is that the global FP state is in
 the default state.

Then I'm not sure it makes to treat "memoisable" as a property of a (pure) function. It seems more like a property of the context from which the function is called. *All* pure function are memoisable if you always call them with the floating point rounding mode set to the default. Conversely, all pure functions can be called with an arbitrary rounding mode if you treat them an unmemoisable.

That is correct.
 Some function - those that don't perform any floating point calculation,
 directly or indirectly - are independent from floating point state, and
 could even memoised even when called with an arbitrary floating point
 state.  But there is no good way to automatically detect these functions.

It sounds as though you never saw my proposal. My proposal was that, by analogy to module(system), specific modules should be marked, for example: module (advancedfloatingpoint, system) std.math; or: module (nondefaultfloat, system) std.math; All pure functions are memoisable except for pure advancedfloatingpoint functions when called from a function (pure or not) which is also in an advancedfloatingpoint module. This is a very tiny hole in the 'purity' rules, and is a tiny addition to the language, but it's enough to cover the practical uses of floating-point rounding and exception flags.
Sep 17 2009
prev sibling parent Jason House <jason.james.house gmail.com> writes:
Don Wrote:
 
 I've provided very simple solutions for both of these. I'm getting 
 pretty irritated that people are acting as though these are difficult 
 problems and trying to come up with convoluted solutions. Impose minimal 
 semantics and it becomes trivial.

FWIW, I've liked your proposals, but I don't feel qualified to comment. I never adjust rounding modes. I do like how I can be ignorant and get code that is better optimized. You've shown deep knowledge of floating point operation in the past, and I'm inclined to accept reasonable-sounding proposals from those that know way more than me.
Sep 14 2009
prev sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Mon, 14 Sep 2009 16:44:48 +0400, Jason House  
<jason.james.house gmail.com> wrote:

 Don Wrote:
 I've provided very simple solutions for both of these. I'm getting
 pretty irritated that people are acting as though these are difficult
 problems and trying to come up with convoluted solutions. Impose minimal
 semantics and it becomes trivial.

FWIW, I've liked your proposals, but I don't feel qualified to comment. I never adjust rounding modes. I do like how I can be ignorant and get code that is better optimized. You've shown deep knowledge of floating point operation in the past, and I'm inclined to accept reasonable-sounding proposals from those that know way more than me.

I second that. Nothing to add.
Sep 14 2009
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Don wrote:
 PROPOSAL:
 Change the spec by adding the line to float.html:
 "If the floating-point rounding mode is changed within a function, it 
 must be restored before the function exits. If this rule is violated 
 (for example, by the use of inline asm), the rounding mode used for 
 subsequent calculations is undefined."

But that doesn't allow for changing the rounding mode at a global level, then rerunning one's computation to determine how rounding affects their final result.
Sep 11 2009
next sibling parent reply Don <nospam nospam.com> writes:
Walter Bright wrote:
 Don wrote:
 PROPOSAL:
 Change the spec by adding the line to float.html:
 "If the floating-point rounding mode is changed within a function, it 
 must be restored before the function exits. If this rule is violated 
 (for example, by the use of inline asm), the rounding mode used for 
 subsequent calculations is undefined."

But that doesn't allow for changing the rounding mode at a global level, then rerunning one's computation to determine how rounding affects their final result.

It doesn't prevent that at all. You just need to change the rounding mode before running your computation (at the start of main(), for example), and then reset it after performing the computation. What it does do is prevent different terms within a single expression from interacting which other. eg double foo() { return x() + y(); } x() and y() can use whichever rounding modes they like, and if they are impure, they can affect one another, but under this proposal, they cannot change the meaning of the addition inside foo().
Sep 13 2009
parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2009-09-13 06:14:02 -0400, Don <nospam nospam.com> said:

 double foo() {
    return x() + y();
 }
 x() and y() can use whichever rounding modes they like, and if they are 
 impure, they can affect one another, but under this proposal, they 
 cannot change the meaning of the addition inside foo().

Problems still may occur if x and y do memoization however. You call x() with one rounding mode, it memoize the results, then call x() with another rounding mode and it reuses the previous results while it shouldn't. Basicaly, any memoization of x() should be discarded when you change the rounding mode. For instance the compiler should not reuse the value of x(), even if x is pure, in the following code: auto a = x(); setRoundingMode(...); auto b = x(); Basically, setRoundingMode should act as some sort of barrier for the optimizer, but how? -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Sep 13 2009
parent Don <nospam nospam.com> writes:
Michel Fortin wrote:
 On 2009-09-13 06:14:02 -0400, Don <nospam nospam.com> said:
 
 double foo() {
    return x() + y();
 }
 x() and y() can use whichever rounding modes they like, and if they 
 are impure, they can affect one another, but under this proposal, they 
 cannot change the meaning of the addition inside foo().

Problems still may occur if x and y do memoization however.

Yes, but this is an orthogonal issue, which I have discussed previously.
 Basicaly, any memoization of x() should be discarded when you change the 
 rounding mode.

 Basically, setRoundingMode should act as some sort of barrier for the 
 optimizer, but how?

It's not a big deal, because rounding mode is changed so rarely, so you don't need to exactly catch all the cases where memoisation can safely be used; you only need to disallow all of the ones where it is unsafe. I proposed two options: (1) by analogy to SafeD, allow rounding mode and float exception flags to be respected only in specially marked modules ('module(advancedfloatingpoint)' or similar). Memoisation should be disabled in such modules. (As a further optimisation, the compiler can re-enable memoisation when such functions are called from non-advancedfloatingpoint modules). (2) provide a runtime call to disable memoisation. You'd be obliged to call this every time you changed the rounding mode or exception status from the default. I favour (1) because it provides huge optimisation potential for modules with default floating point. The point of all my proposals on this topic, is that these features cause a lot of trouble if they're completely unrestrained in the way that C/C++ does it, but the semantics required to completely domesticate them are not burdensome, and can be done without loss of useful functionality.
Sep 13 2009
prev sibling next sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Walter Bright wrote:
 Don wrote:
 PROPOSAL:
 Change the spec by adding the line to float.html:
 "If the floating-point rounding mode is changed within a function, it 
 must be restored before the function exits. If this rule is violated 
 (for example, by the use of inline asm), the rounding mode used for 
 subsequent calculations is undefined."

But that doesn't allow for changing the rounding mode at a global level, then rerunning one's computation to determine how rounding affects their final result.

Ok, I understand now. This makes sense.
Sep 13 2009
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Don wrote:
 PROPOSAL:
 Change the spec by adding the line to float.html:
 "If the floating-point rounding mode is changed within a function, it 
 must be restored before the function exits. If this rule is violated 
 (for example, by the use of inline asm), the rounding mode used for 
 subsequent calculations is undefined."

I added it into float.html for D2.
Sep 13 2009
parent reply bearophile <bearophileHUGS lycos.com> writes:
Walter Bright:

 Don:
 PROPOSAL:
 Change the spec by adding the line to float.html:
 "If the floating-point rounding mode is changed within a function, it 
 must be restored before the function exits. If this rule is violated 
 (for example, by the use of inline asm), the rounding mode used for 
 subsequent calculations is undefined."

I added it into float.html for D2.

An important purpose of a not bug-prone language is remove as many undefined situations as possible. If you add that to D2 specs, can the compiler catch at compile time all cases of violations to that rule? D has something for the actions that must be done before the function exits, the scope(exit). Can something like that be used to automatically avoid undefined rounding situations? Bye, bearophile
Sep 13 2009
parent reply Walter Bright <newshound1 digitalmars.com> writes:
bearophile wrote:
 An important purpose of a not bug-prone language is remove as many
 undefined situations as possible.

That's right.
 If you add that to D2 specs, can
 the compiler catch at compile time all cases of violations to that
 rule?

Unlikely. But that has to be weighed against the former behavior being defined in a useless way.
 D has something for the actions that must be done before the function
 exits, the scope(exit). Can something like that be used to
 automatically avoid undefined rounding situations?

Using an RAII object to do it might be better.
Sep 13 2009
parent reply Don <nospam nospam.com> writes:
Walter Bright wrote:
 bearophile wrote:
 An important purpose of a not bug-prone language is remove as many
 undefined situations as possible.

That's right.
 If you add that to D2 specs, can
 the compiler catch at compile time all cases of violations to that
 rule?

Unlikely. But that has to be weighed against the former behavior being defined in a useless way.

We could get close, I think. We could catch all violations in SafeD. Disallow calling core.stdc.fenv functions from inside SafeD modules, and provide an RAII object in std.math for changing the rounding mode. Actually there's probably a few other C functions we want to prohibit in SafeD -- the infamous string functions, for example. I think the goal should be try to remove undefined behaviour from SafeD. Long term goal: "If a program uses no system modules other than those provided in the standard library, it should not be exposed to undefined behaviour." ?
 D has something for the actions that must be done before the function
 exits, the scope(exit). Can something like that be used to
 automatically avoid undefined rounding situations?

Using an RAII object to do it might be better.

I agree. I'd like to do something similar with the floating point exception state, too. Haven't worked out the details yet.
Sep 14 2009
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Don wrote:
 Walter Bright wrote:
 bearophile wrote:
 An important purpose of a not bug-prone language is remove as many
 undefined situations as possible.

That's right.
 If you add that to D2 specs, can
 the compiler catch at compile time all cases of violations to that
 rule?

Unlikely. But that has to be weighed against the former behavior being defined in a useless way.

We could get close, I think. We could catch all violations in SafeD. Disallow calling core.stdc.fenv functions from inside SafeD modules, and provide an RAII object in std.math for changing the rounding mode. Actually there's probably a few other C functions we want to prohibit in SafeD -- the infamous string functions, for example.

Yes, all the C functions will have to be gone through and separated. strlen() is safe, while strcpy() and printf() are not.
 I think the goal should be try to remove undefined behaviour from SafeD.
 Long term goal: "If a program uses no system modules other than those 
 provided in the standard library, it should not be exposed to undefined 
 behaviour." ?

Right!
Sep 14 2009
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Walter Bright:
 Yes, all the C functions will have to be gone through and separated. 
 strlen() is safe, while strcpy() and printf() are not.

For some of the removed functions better C-like ones can be provided: http://www.ddj.com/cpp/214502214 Bye, bearophile
Sep 14 2009
parent Don <nospam nospam.com> writes:
bearophile wrote:
 Walter Bright:
 Yes, all the C functions will have to be gone through and separated. 
 strlen() is safe, while strcpy() and printf() are not.

For some of the removed functions better C-like ones can be provided: http://www.ddj.com/cpp/214502214 Bye, bearophile

Just use D instead.
Sep 14 2009
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Brad Roberts wrote:
 Walter Bright wrote:
 strlen() is safe, while strcpy() and printf() are not.

You sure? Does running beyond the bounds of the array if there's no null termination count as safe some how? :)

Yes. Memory safety is defined as being free of memory corruption errors. Simply reading memory out of bounds does not corrupt memory. Note that it is ok for a memory safe program to generate a seg fault. printf() is not memory safe because of the %n format.
Sep 14 2009
parent Don <nospam nospam.com> writes:
Walter Bright wrote:
 Brad Roberts wrote:
 Walter Bright wrote:
 strlen() is safe, while strcpy() and printf() are not.

You sure? Does running beyond the bounds of the array if there's no null termination count as safe some how? :)

Yes. Memory safety is defined as being free of memory corruption errors. Simply reading memory out of bounds does not corrupt memory.

It does result in undefined behaviour, though. I don't see much difference. (Corrupting memory is a problem only because you read it again afterwards...)
 Note that it is ok for a memory safe program to generate a seg fault.

It'd be OK if it was guaranteed to generate a seg fault. But I don't think that's true here.
 
 printf() is not memory safe because of the %n format.

Sep 15 2009
prev sibling parent Brad Roberts <braddr puremagic.com> writes:
Walter Bright wrote:
 Don wrote:
 We could get close, I think. We could catch all violations in SafeD.
 Disallow calling core.stdc.fenv functions from inside SafeD modules,
 and provide an RAII object in std.math for changing the rounding mode.

 Actually there's probably a few other C functions we want to prohibit
 in SafeD -- the infamous string functions, for example.

Yes, all the C functions will have to be gone through and separated. strlen() is safe, while strcpy() and printf() are not.

You sure? Does running beyond the bounds of the array if there's no null termination count as safe some how? :)
Sep 14 2009