Jump to page: 1 2 3
Thread overview
[Issue 360] New: Compile-time floating-point calculations are sometimes inconsistent
Sep 21, 2006
d-bugmail
Sep 21, 2006
d-bugmail
Sep 21, 2006
d-bugmail
Sep 21, 2006
d-bugmail
Sep 22, 2006
d-bugmail
Sep 22, 2006
d-bugmail
Sep 22, 2006
Walter Bright
Sep 22, 2006
Don Clugston
Sep 22, 2006
Dave
Sep 22, 2006
Walter Bright
Sep 23, 2006
Don Clugston
Sep 24, 2006
Walter Bright
Sep 24, 2006
Don Clugston
Sep 24, 2006
Walter Bright
Sep 25, 2006
Don Clugston
Sep 25, 2006
Walter Bright
Sep 25, 2006
xs0
Sep 25, 2006
Walter Bright
Sep 25, 2006
Sean Kelly
Sep 22, 2006
Bradley Smith
Sep 22, 2006
d-bugmail
September 21, 2006
http://d.puremagic.com/issues/show_bug.cgi?id=360

           Summary: Compile-time floating-point calculations are sometimes
                    inconsistent
           Product: D
           Version: 0.167
          Platform: PC
        OS/Version: Windows
            Status: NEW
          Severity: normal
          Priority: P2
         Component: DMD
        AssignedTo: bugzilla@digitalmars.com
        ReportedBy: digitalmars-com@baysmith.com


The following code should print false before it exits.

import std.stdio;

void main() {
        const float STEP_SIZE = 0.2f;


        float j = 0.0f;
        while (j <= ( 1.0f / STEP_SIZE)) {
                j += 1.0f;
                writefln(j <= ( 1.0f / STEP_SIZE));
        }

}

This problem does not occur when:
1. the code is optimized
2. STEP_SIZE is not a const
3. STEP_SIZE is a real


-- 

September 21, 2006
http://d.puremagic.com/issues/show_bug.cgi?id=360


bugzilla@digitalmars.com changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEW                         |RESOLVED
         Resolution|                            |INVALID




------- Comment #1 from bugzilla@digitalmars.com  2006-09-21 18:31 -------
The example is mixing up 3 different precisions - 32, 64, and 80 bit. Each involves different rounding of unrepresentable numbers like 0.2. In this case, the 1.0f/STEP_SIZE is calculated at different precisions based on how things are compiled. Constant folding, for example, is done at compile time and done at max precision even if the variables involved are floats.

The D language allows this, the guiding principle is that algorithms should be designed to not fail if precision is increased.

Not a bug.


-- 

September 21, 2006
http://d.puremagic.com/issues/show_bug.cgi?id=360





------- Comment #2 from digitalmars-com@baysmith.com  2006-09-21 18:46 -------
*** Bug 361 has been marked as a duplicate of this bug. ***


-- 

September 21, 2006
http://d.puremagic.com/issues/show_bug.cgi?id=360





------- Comment #3 from digitalmars-com@baysmith.com  2006-09-21 18:51 -------
Why are the expressions in the while and writefln statements calculated at different precisions?

Wouldn't the constant folding be done the same for both?


-- 

September 22, 2006
http://d.puremagic.com/issues/show_bug.cgi?id=360





------- Comment #4 from bugzilla@digitalmars.com  2006-09-21 20:17 -------
while (j <= (1.0f/STEP_SIZE)) is at double precision,
writefln((j += 1.0f) <= (1.0f/STEP_SIZE)) is at real precision.


-- 

September 22, 2006
http://d.puremagic.com/issues/show_bug.cgi?id=360





------- Comment #5 from clugdbug@yahoo.com.au  2006-09-22 02:25 -------
(In reply to comment #4)
> while (j <= (1.0f/STEP_SIZE)) is at double precision,
> writefln((j += 1.0f) <= (1.0f/STEP_SIZE)) is at real precision.

I don't understand where the double precision comes from. Since all the values are floats, the only precisions that make sense are float and reals.

Really, 0.2f should not be the same number as 0.2. When you put the 'f' suffix on, surely you're asking the compiler to truncate the precision. It can be expanded to real precision later without problems. Currently, there's no way to get a low-precision constant at compile time.

(In fact, you should be able to write real a = 0.2 - 0.2f; to get the
truncation error).

Here's how I think it should work:

const float A = 0.2;  // infinitely accurate 0.2, but type inference on A should return a float.

const float B = 0.2f; // a 32-bit approximation to 0.2
const real C = 0.2; // infinitely accurate 0.2
const real D = 0.2f; // a 32-bit approximation to 0.2, but type inference will
give an 80-bit quantity.


-- 

September 22, 2006
d-bugmail@puremagic.com wrote:
> ------- Comment #5 from clugdbug@yahoo.com.au  2006-09-22 02:25 -------
> (In reply to comment #4)
>> while (j <= (1.0f/STEP_SIZE)) is at double precision,
>> writefln((j += 1.0f) <= (1.0f/STEP_SIZE)) is at real precision.
> I don't understand where the double precision comes from. Since all the values
> are floats, the only precisions that make sense are float and reals.

The compiler is allowed to evaluate intermediate results at a greater precision than that of the operands.

> Really, 0.2f should not be the same number as 0.2.

0.2 is not representable exactly, the only question is how much precision is there in the representation.

> When you put the 'f' suffix
> on, surely you're asking the compiler to truncate the precision.

Not in D. The 'f' suffix only indicates the type. The compiler may maintain internally as much precision as possible, for purposes of constant folding. Committing the actual precision of the result is done as late as possible.

> It can be
> expanded to real precision later without problems. Currently, there's no way to
> get a low-precision constant at compile time.

You can by putting the constant into a static, non-const variable. Then it cannot be constant folded.

> (In fact, you should be able to write real a = 0.2 - 0.2f; to get the
> truncation error).

Not in D, where the compiler is allowed to evaluate using as much precision as possible for purposes of constant folding. The vast majority of calculations benefit from delaying rounding as long as possible, hence D's bias towards using as much precision as possible.

The way to write robust floating point calculations in D is to ensure that increasing the precision of the calculations will not break the result.

Early versions of Java insisted that rounding to precision of floating point intermediate results always happened. While this ensured consistency of results, it mostly resulted in consistently getting inferior and wrong answers.
September 22, 2006
Walter Bright wrote:
> d-bugmail@puremagic.com wrote:
>> ------- Comment #5 from clugdbug@yahoo.com.au  2006-09-22 02:25 -------
>> (In reply to comment #4)
>>> while (j <= (1.0f/STEP_SIZE)) is at double precision,
>>> writefln((j += 1.0f) <= (1.0f/STEP_SIZE)) is at real precision.
>> I don't understand where the double precision comes from. Since all the values
>> are floats, the only precisions that make sense are float and reals.
> 
> The compiler is allowed to evaluate intermediate results at a greater precision than that of the operands.
> 
>> Really, 0.2f should not be the same number as 0.2.
> 
> 0.2 is not representable exactly, the only question is how much precision is there in the representation.
> 
>> When you put the 'f' suffix
>> on, surely you're asking the compiler to truncate the precision.
> 
> Not in D. The 'f' suffix only indicates the type.

And therefore, it only matters in implicit type deduction, and in function overloading. As I discuss below, I'm not sure that it's necessary even there.
In many cases, it's clearly a programmer error. For example in
real BAD = 0.2f;
where the f has absolutely no effect.

The compiler may
> maintain internally as much precision as possible, for purposes of constant folding. Committing the actual precision of the result is done as late as possible.
> 
>> It can be
>> expanded to real precision later without problems. Currently, there's no way to
>> get a low-precision constant at compile time.
> 
> You can by putting the constant into a static, non-const variable. Then it cannot be constant folded.

Actually, in this case you still want it to be constant folded.
> 
>> (In fact, you should be able to write real a = 0.2 - 0.2f; to get the
>> truncation error).
> 
> Not in D, where the compiler is allowed to evaluate using as much precision as possible for purposes of constant folding. The vast majority of calculations benefit from delaying rounding as long as possible, hence D's bias towards using as much precision as possible.
> 
> The way to write robust floating point calculations in D is to ensure that increasing the precision of the calculations will not break the result.
> 
> Early versions of Java insisted that rounding to precision of floating point intermediate results always happened. While this ensured consistency of results, it mostly resulted in consistently getting inferior and wrong answers.

I agree. But it seems that D is currently in a halfway house on this issue. Somehow, 'double' is privileged, and don't think it's got any right to be.

    const XXX = 0.123456789123456789123456789f;
    const YYY = 1 * XXX;
    const ZZZ = 1.0 * XXX;

   auto xxx = XXX;
   auto yyy = YYY;
   auto zzz = ZZZ;

// now xxx and yyy are floats, but zzz is a double.
Multiplying by '1.0' causes a float constant to be promoted to double.

   real a = xxx;
   real b = zzz;
   real c = XXX;

Now a, b, and c all have different values.

Whereas the same operation at runtime causes it to be promoted to real.

Is there any reason why implicit type deduction on a floating point constant doesn't always default to real? After all, you're saying "I don't particularly care what type this is" -- why not default to maximum accuracy?

Concrete example:

real a = sqrt(1.1);

This only gives a double precision result. You have to write
real a = sqrt(1.1L);
instead.
It's easier to do the wrong thing, than the right thing.

IMHO, unless you specifically take other steps, implicit type deduction should always default to the maximum accuracy the machine could do.
September 22, 2006
Don Clugston wrote:
> Walter Bright wrote:
>> d-bugmail@puremagic.com wrote:
>>> ------- Comment #5 from clugdbug@yahoo.com.au  2006-09-22 02:25 -------
>>> (In reply to comment #4)
>>>> while (j <= (1.0f/STEP_SIZE)) is at double precision,
>>>> writefln((j += 1.0f) <= (1.0f/STEP_SIZE)) is at real precision.
>>> I don't understand where the double precision comes from. Since all the values
>>> are floats, the only precisions that make sense are float and reals.
>>
>> The compiler is allowed to evaluate intermediate results at a greater precision than that of the operands.
>>
>>> Really, 0.2f should not be the same number as 0.2.
>>
>> 0.2 is not representable exactly, the only question is how much precision is there in the representation.
>>
>>> When you put the 'f' suffix
>>> on, surely you're asking the compiler to truncate the precision.
>>
>> Not in D. The 'f' suffix only indicates the type.
> 
> And therefore, it only matters in implicit type deduction, and in function overloading. As I discuss below, I'm not sure that it's necessary even there.
> In many cases, it's clearly a programmer error. For example in
> real BAD = 0.2f;
> where the f has absolutely no effect.
> 
> The compiler may
>> maintain internally as much precision as possible, for purposes of constant folding. Committing the actual precision of the result is done as late as possible.
>>
>>> It can be
>>> expanded to real precision later without problems. Currently, there's no way to
>>> get a low-precision constant at compile time.
>>
>> You can by putting the constant into a static, non-const variable. Then it cannot be constant folded.
> 
> Actually, in this case you still want it to be constant folded.
>>
>>> (In fact, you should be able to write real a = 0.2 - 0.2f; to get the
>>> truncation error).
>>
>> Not in D, where the compiler is allowed to evaluate using as much precision as possible for purposes of constant folding. The vast majority of calculations benefit from delaying rounding as long as possible, hence D's bias towards using as much precision as possible.
>>
>> The way to write robust floating point calculations in D is to ensure that increasing the precision of the calculations will not break the result.
>>
>> Early versions of Java insisted that rounding to precision of floating point intermediate results always happened. While this ensured consistency of results, it mostly resulted in consistently getting inferior and wrong answers.
> 
> I agree. But it seems that D is currently in a halfway house on this issue. Somehow, 'double' is privileged, and don't think it's got any right to be.
> 
>     const XXX = 0.123456789123456789123456789f;
>     const YYY = 1 * XXX;
>     const ZZZ = 1.0 * XXX;
> 
>    auto xxx = XXX;
>    auto yyy = YYY;
>    auto zzz = ZZZ;
> 
> // now xxx and yyy are floats, but zzz is a double.
> Multiplying by '1.0' causes a float constant to be promoted to double.
> 
>    real a = xxx;
>    real b = zzz;
>    real c = XXX;
> 
> Now a, b, and c all have different values.
> 
> Whereas the same operation at runtime causes it to be promoted to real.
> 
> Is there any reason why implicit type deduction on a floating point constant doesn't always default to real? After all, you're saying "I don't particularly care what type this is" -- why not default to maximum accuracy?
> 
> Concrete example:
> 
> real a = sqrt(1.1);
> 
> This only gives a double precision result. You have to write
> real a = sqrt(1.1L);
> instead.
> It's easier to do the wrong thing, than the right thing.
> 
> IMHO, unless you specifically take other steps, implicit type deduction should always default to the maximum accuracy the machine could do.

Great point.
September 22, 2006
Don Clugston wrote:
> Walter Bright wrote:
>> Not in D. The 'f' suffix only indicates the type.
> 
> And therefore, it only matters in implicit type deduction, and in function overloading. As I discuss below, I'm not sure that it's necessary even there.
> In many cases, it's clearly a programmer error. For example in
> real BAD = 0.2f;
> where the f has absolutely no effect.

It may come about as a result of source code generation, though, so I'd be reluctant to make it an error.


>> You can by putting the constant into a static, non-const variable. Then it cannot be constant folded.
> 
> Actually, in this case you still want it to be constant folded.

A static variable's value can change, so it can't be constant folded. To have it participate in constant folding, it needs to be declared as const.


> I agree. But it seems that D is currently in a halfway house on this issue. Somehow, 'double' is privileged, and don't think it's got any right to be.
> 
>     const XXX = 0.123456789123456789123456789f;
>     const YYY = 1 * XXX;
>     const ZZZ = 1.0 * XXX;
> 
>    auto xxx = XXX;
>    auto yyy = YYY;
>    auto zzz = ZZZ;
> 
> // now xxx and yyy are floats, but zzz is a double.
> Multiplying by '1.0' causes a float constant to be promoted to double.

That's because 1.0 is a double. A double*float => double.

>    real a = xxx;
>    real b = zzz;
>    real c = XXX;
> 
> Now a, b, and c all have different values.
> 
> Whereas the same operation at runtime causes it to be promoted to real.
> 
> Is there any reason why implicit type deduction on a floating point constant doesn't always default to real? After all, you're saying "I don't particularly care what type this is" -- why not default to maximum accuracy?
> 
> Concrete example:
> 
> real a = sqrt(1.1);
> 
> This only gives a double precision result. You have to write
> real a = sqrt(1.1L);
> instead.
> It's easier to do the wrong thing, than the right thing.
> 
> IMHO, unless you specifically take other steps, implicit type deduction should always default to the maximum accuracy the machine could do.

It is a good idea, but isn't that way for the reasons:

1) It's the way C, C++, and Fortran work. Changing the promotion rules would mean that, when translating solid, reliable libraries from those languages to D, one would have to be very, very careful.

2) Float and double are expected to be implemented in hardware. Longer precisions are often not available. I wanted to make it practical for a D implementation on those machines to provide a software long precision floating point type, rather than just making real==double. Such a type would be very slow compared with double.

3) Real, even in hardware, is significantly slower than double. Doing constant folding at max precision at compile time won't affect runtime performance, so it is 'free'.
« First   ‹ Prev
1 2 3