Once again Math.Log

  • Thread starter Thread starter Wernfried Schwenkner
  • Start date Start date
W

Wernfried Schwenkner

I've found the discussion about Math.Log and the error with

Math.Log(8,2)

on Google. Unfortunatly the full thread isn't on my news server, so I
can't reply.

The problem doesn't only depend on representation of floating numbers.
When You execute

int res = (int)Math.Log(8, 2);
if (res == 3)
{
Console.WriteLine("right");
}
else
{
Console.WriteLine("false");
}

You get the output "false". My first thoughts also where to the floating
representation. But if You debug the same lines of Code, the debugger
steps to the output of "right", the result res is "3".


Does the debugger use another Math-engine?
 
Wernfried Schwenkner said:
I've found the discussion about Math.Log and the error with

Math.Log(8,2)

on Google. Unfortunatly the full thread isn't on my news server, so I
can't reply.

The problem doesn't only depend on representation of floating numbers.
When You execute

int res = (int)Math.Log(8, 2);
if (res == 3)
{
Console.WriteLine("right");
}
else
{
Console.WriteLine("false");
}

You get the output "false". My first thoughts also where to the floating
representation. But if You debug the same lines of Code, the debugger
steps to the output of "right", the result res is "3".

Does the debugger use another Math-engine?

No - it's just it's formatting something "very close to 3" as "3".

Personally I never trust the debugger for that kind of thing - nor even
the built-in ToString methods. If you want to really see *exactly* what
value is in a double, use something like my DoubleConverter class:

http://www.pobox.com/~skeet/csharp/DoubleConverter.cs
 
No - it's just it's formatting something "very close to 3" as "3".
<snip>

I think his point is that when he runs the program without debugging, res
is not 3, but with debugging res is 3, ie. the program take different
routes.

And he's exactly right:

reproduce:
1. create a new winforms project in VS.NET 2003
2. add a single button to the form, double-click to add event handler
3. type in the following code:

Int32 i = (Int32)Math.Log(8, 2);
if (i == 3)
Text = "i == 3";
else
Text = "i != 3";

4. run program with Ctrl-F5 to start without debugger, click button,
caption of window becomes i != 3, close program
5. run program with F5 to start with debugger, click button, caption of
window becomes i == 3

This means that the result of that cast, or the log operation, differs
wether you use the debugger or not. Perhaps there's some slight
differences in the debug binaries of the .net runtime ?
 
Lasse Vågsæther Karlsen said:
<snip>

I think his point is that when he runs the program without debugging, res
is not 3, but with debugging res is 3, ie. the program take different
routes.

Ah - oops. Misread :(
And he's exactly right:

reproduce:
1. create a new winforms project in VS.NET 2003
2. add a single button to the form, double-click to add event handler
3. type in the following code:

Int32 i = (Int32)Math.Log(8, 2);
if (i == 3)
Text = "i == 3";
else
Text = "i != 3";

4. run program with Ctrl-F5 to start without debugger, click button,
caption of window becomes i != 3, close program
5. run program with F5 to start with debugger, click button, caption of
window becomes i == 3

This means that the result of that cast, or the log operation, differs
wether you use the debugger or not. Perhaps there's some slight
differences in the debug binaries of the .net runtime ?

Hmm... not sure. Certainly seems very odd.
 
Yep, the JIT compiler works a little different for executables that have
been started in the debugger. (attaching the debugger to an already running
executable doesn't change anything). For example, it doesn't inline function
calls, so you can always step into functions.
My guess is that an inlined version of Math.Log (outside the debugger) can
keep all internal results in 80-bit FPU registers, while a non-inlined
version has to return them as 64-bit double values, which leads to different
results.

i.e. if Log is defined like this:
public static double Log(double a, double newBase)
{
return (Math.Log(a)/Math.Log(newBase));
}

If Math.Log(a) and Math.Log(newBase) are inlined, the results of the calls
may stay in FPU registers (which are 80 bit long). On the other hand, if
they are not inlined, the results have to be returned as 64-bit-doubles.
That explains the loss of precision.

Niki
 
Niki Estner said:
Yep, the JIT compiler works a little different for executables that have
been started in the debugger. (attaching the debugger to an already running
executable doesn't change anything). For example, it doesn't inline function
calls, so you can always step into functions.
My guess is that an inlined version of Math.Log (outside the debugger) can
keep all internal results in 80-bit FPU registers, while a non-inlined
version has to return them as 64-bit double values, which leads to different
results.

i.e. if Log is defined like this:
public static double Log(double a, double newBase)
{
return (Math.Log(a)/Math.Log(newBase));
}

If Math.Log(a) and Math.Log(newBase) are inlined, the results of the calls
may stay in FPU registers (which are 80 bit long). On the other hand, if
they are not inlined, the results have to be returned as 64-bit-doubles.
That explains the loss of precision.

Agreed, and to back up your suppostion, from the Tool Developer's Guide,
section 11.1.3:

"Storage locations for floating point numbers (statics, array elements, and
fields of classes) are of fixed size. The supported storage sizes are
float32 and float64. Everywhere else (on the evaluation stack, as arguments,
as return types, and as local variables) floating point numbers are
represented using an internal floating-point type. In each such instance,
the nominal type of the variable or expression is either R4 or R8, but its
value may be represented internally with additional range and/or precision.
The size of the internal floating-point representation is
implementation-dependent, may vary, and shall have precision at least as
great as that of the variable or expression being represented. "

and furthermore:

"Note: The use of an internal representation that is wider than float32 or
float64 may cause differences in computational results when a developer
makes seemingly unrelated modifications to their code, the result of which
may be that a value is spilled from the internal representation (e.g. in a
register) to a location on the stack. "

Stu
 
"Note: The use of an internal representation that is wider than
float32 or float64 may cause differences in computational results when
a developer makes seemingly unrelated modifications to their code, the
result of which may be that a value is spilled from the internal
representation (e.g. in a register) to a location on the stack. "
<snip>

Nice explanation. It makes sense, just didn't think about this when I
looked at the original post.

I can see this as the source for future discussions like "what's the point
of using the debugger if the code behaves different" but anyone wanting to
use that point please bear in mind that the original post was about a cast
to Int32 from a double, relying on the floating point value to be exactly
like 3, and anyone that knows anything about floating point numbers knows
that the likelihood of that is 50/50, and there's only a 10% chance of
that.

:)
 
I can see this as the source for future discussions like "what's the point
of using the debugger if the code behaves different" but anyone wanting to
use that point please bear in mind that the original post was about a cast
to Int32 from a double, relying on the floating point value to be exactly
like 3, and anyone that knows anything about floating point numbers knows
that the likelihood of that is 50/50, and there's only a 10% chance of
that.

No, my post was not a question relying on floating point values. I know
the floating point representaion very well (wrote floating point
routines in assembler myself without having a FPP). My problem was the
different behaviour in debug and release mode. So the answers could
clearify all.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top