views:

145

answers:

4

I have some code that gets the leading value (non-zero) of a Double using normal math instead of String Math...

For Example:
0.020 would return 2
3.12 would return 3
1000 should return 1

The code I have at the moment is:

LeadingValue := Trunc(ResultValue * Power(10, -(Floor(Log10(ResultValue)))))

However when ResultValue is 1000 then LeadingValue ends up as 0.

What can I do to fix this problem I'm assuming is being caused by floating point errors?

Thanks.

+1  A: 

I'm curious as to why you can't / won't use String manipulation for this. Could you explain further?

The most obvious thing you can do to fix the problem is to convert the number to a String and find the first non zero digit.

awmross
String Math would slow it down a lot, this needs to be a fast routine.
HannesNZ
How much performance improvement is there? Just curious, as log, exp and FP in general is not fast either.
awmross
You are loosing the game at "convert the number to a string". As soon as the floating point number is represented as an mantissa + exponent, you've lost the precision.
gabr
@HannesNZ: floating point numbers cannot be represented exactly as decimal string values and vice versa. So either use strings for everything, or be prepared for rounding artefacts.
Jeroen Pluimers
A: 

A possible way to cheat around the rounding problems:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Math;

function LeadingValue(num: extended): integer;
const
  ExtendedResolution = 1E-19 * 1000; // from Math
begin
  num := Abs(num);
  Result := Trunc(num * Power(10, -(Floor(Log10(num)))) + ExtendedResolution);
end;

begin
  try
    Writeln(LeadingValue(0.02));
    Writeln(LeadingValue(3.12));
    Writeln(LeadingValue(1000));
    Writeln(LeadingValue(0.9999999999));
    Writeln(LeadingValue(1.0000000001));
    Writeln(LeadingValue(-0.02));
    Writeln(LeadingValue(-3.12));
    Writeln(LeadingValue(-1000));
    Writeln(LeadingValue(-0.9999999999));
    Writeln(LeadingValue(-1.0000000001));
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
gabr
A: 

if you divide instead of multiply, it fixes your test cases. Overall, it will yield better result as the "Power" result won't have a fractional value. I'm afraid it might not have a 100% case coverage, but unless a better solution is proposed, this should be better than what you have.

function Func(Value : Double) : Integer;
begin
  Result := Trunc(Value / Power(10, (Floor(Log10(Value)))))
end;
Ken Bourassa
A: 

You're not going to be able to do this with 100% accuracy using floating-point math. What you need to do essentially is to convert a binary floating-point value to a decimal string. Routines that do that accurately, like David Gay's dtoa.c, use high precision (integer) arithmetic.

Rick Regan