While the difference is usually not noticeable, it's there and once used with pow and exp, it can yield to totally different results (that's how I discovered it).
import std.stdio;
alias S = float;
float shL = 0x1.1b95eep-4; // -0.069235
float shR = 0x1.9c7cfep-7; // -0.012588
F fun(F)(F x)
{
return 1.0 + x * 2;
}
S working()
{
S r = fun(shR);
S l = fun(shL);
return (r - l);
}
S failing()
{
return (fun(shR) - fun(shL));
}
unittest
{
writefln("work: %a", working);
writefln("fail: %a", failing);
assert(working == failing);
}
assembler:
.text._D3foo7workingFZf segment
assume CS:.text._D3foo7workingFZf
_D3foo7workingFZf:
push EBP
mov EBP,ESP
sub ESP,0Ch
mov -0Ch[EBP],EBX
mov EAX,GS:[00h]
mov ECX,_D3foo3shRf@TLS_IE
push [ECX][EAX]
call _D3foo10__T3funTfZ3funFNaNbNiNffZf@PC32
fstp float ptr -8[EBP]
mov EDX,GS:[00h]
mov EBX,_D3foo3shLf@TLS_IE
push [EBX][EDX]
call _D3foo10__T3funTfZ3funFNaNbNiNffZf@PC32
fstp float ptr -4[EBP]
fld float ptr -8[EBP]
fsub float ptr -4[EBP]
mov EBX,-0Ch[EBP]
leave
ret
nop
nop
nop
nop
nop
.text._D3foo7workingFZf ends
.text._D3foo7failingFZf segment
assume CS:.text._D3foo7failingFZf
_D3foo7failingFZf:
push EBP
mov EBP,ESP
sub ESP,010h
mov -010h[EBP],EBX
mov EAX,GS:[00h]
mov ECX,_D3foo3shRf@TLS_IE
push [ECX][EAX]
call _D3foo10__T3funTfZ3funFNaNbNiNffZf@PC32
mov EDX,GS:[00h]
mov EBX,_D3foo3shLf@TLS_IE
push [EBX][EDX]
fstp float ptr -0Ch[EBP]
call _D3foo10__T3funTfZ3funFNaNbNiNffZf@PC32
fld float ptr -0Ch[EBP]
fsubrp ST(1),ST
mov EBX,-010h[EBP]
leave
ret
nop
.text._D3foo7failingFZf ends
.text._D3foo10__T3funTfZ3funFNaNbNiNffZf segment
assume CS:.text._D3foo10__T3funTfZ3funFNaNbNiNffZf
_D3foo10__T3funTfZ3funFNaNbNiNffZf:
fld float ptr 4[ESP]
fadd ST(0),ST
fld1
faddp ST(1),ST
ret 4
nop
nop
nop
.text._D3foo10__T3funTfZ3funFNaNbNiNffZf ends
Comment #1 by ag0aep6g — 2016-07-31T10:57:03Z
I think this is due to D allowing floating points operations to be done at a higher precision than is stated in the source.
That is, `fun` may do its calculations with doubles or reals, and it may return a double or real in a register. The same applies to `working` and `failing`, but `working` puts the results from `fun` on the stack first, forcing a conversion to float, while `failing` returns directly, so the operands are not converted to float.
Note that the assert passes when you compile with -O, because then `working` is optimized to behave like `failing`.
Closing as INVALID. Feel free to reopen if you disagree.