tags:

views:

674

answers:

8

I would like compute the number of non-zero and non-repeating (ie. 1.999999999) to the right of the decimal point. Eg:

x.xx = 2
x.xxx = 3
x.xxx000 = 3

I can do this by converting the number to a string but I was wondering if there is a faster way using math. Any ideas?

Thanks.

EDIT: Many people seem to think this is a fools errand because of the way numbers are represented in computers. However, allow me to explain the context of the problem. Suppose you are to write a method to randomly generate a floating point number. The number you generate must have a certain precision, therefore you must round the randomly generated number to the specified precision. For example if the precision is 2, your random number cannot be 0.012, it must be rounded to 0.01. The problem is you are not provided the precision, instead you are given the increment, which in the aforementioned case would be 0.01. Given 0.01 or 0.002 or any other such increment which is less than 1, you must find the precision.

Edit: Removed my incorrect usage of the term significant digits.

+1  A: 

The cheapo approach would be to:

  1. convert the number to a string
  2. find the first non-zero number from the right of the string by assessing the character a n = string length - 1 down to 0.
  3. Take the number found in step #2 and substract from that the index of the decimal point in this string.
David Andres
But that is dependant on how the number is printed (ie the formatiing that is currently applied to the stream). So you are counting the signifigant digits of the format. This is easier to lookup in the manual.
Martin York
Updated to reflect significant digits
David Andres
anotherCoder asked in his question how he could do this *without* using strings...
GRB
Then this solution won't help much. The interface for floats doesn't really provide much in the way of leg room for determining a float's precision. This solution is a bit hacky, I admit, but it ignores overflow scenarios and is relatively cheap memory- and computation-wise.
David Andres
A: 

You can read here about the different properties of floating point numbers.
http://www2.roguewave.com/support/docs/sourcepro/edition9/html/stdlibref/numeric-limits.html

Martin York
+1  A: 
fbrereto
+1 for admitting the flaws
GRB
This will fail for x0.x and x.x0x where x represents any number of digits.
Daniel Brückner
I think you're right about the second case, but the first case shouldn't be a problem because the number is multiplied first before it is checked. Your point remains valid.
fbrereto
You are right, the first case is handle correctly.
Daniel Brückner
A: 

In C# there is a function to round to a given number of decimal digits. You could increase the number of decimal digits until the difference between the number and the rounded number is small then a fixed threshold - maybe two or three times epsilon (the smallest possible positive value - for double this is 4.94065645841247e-324).

Double epsilon = 2 * Double.Epsilon;
Int32 significantDigits = 0;
while (Math.Abs(number - Math.Round(number, significantDigits)) > epsilon)
{
    significantDigits++;
}

This is just an untested suggestion and algorithms of this kind dealing with floating point numbers tend to behave quite unexpected - so this should be well tested before used.

Daniel Brückner
A: 

How about this?

int sigfigs (double value)
{
    int count = 0;
    for (; value!=floor(value); value *= 10)
          count++;
    return count;
}

This seems to work 99% of the time (except for some very weird corner cases, e.g. the number 30.22054 will return the correct value of 5, while 20.22054 returns 15)

GRB
The cases you are stumbling on are not weird corner cases. This is an artifact of the fact that floating point representations are not exact. For example, the floating point representation of `20.22054` does not equal the real number `20.22054`. The floating point representation of the real number `20.22054` turns out to be `20.220539093017578125`.
Jason
Well yeah, by 'weird' I meant 'annoying'. I knew it was because of floating point's inaccuracy, but the inability to know when you will hit a number with such a misrepresentation in memory makes it hard to use (frankly) any of these algorithms with confidence in their results.
GRB
+4  A: 

The number of significant digits is not something you compute by looking at the number. You compute it based on the number of significant digits in the other numbers you used to compute the number you're looking at, or by the precision of the instrument used to carry out your measurement.

Insignificant digits are neither displayed nor used, so the fact that your number x.xxx000 has six digits to the right of the decimal point means that all six of those digits are significant. The number 2.3 is not the same as the number 2.300.

The issue of whether to count zeros as significant comes into play when you have a number like 2300, where the number is an integer multiple of 10. Are there two significant figures, or four? Write it in scientific notation to be sure: 2.3 × 103 or 2.300 × 103.

The native numeric types of most languages, including C++, do not handle this notion of significant digits. In general, they don't even handle digits at all; they handle bits. If your implementation's float type is 32 bits, then all 32 of those bits are treated as significant all the time.

Rob Kennedy
You are correct, I used the term siginificant digits incorrectly. I apologize for not clearly stating my intent and question.
A: 

As others have indicated, the main concern here is that many decimal fractions aren't exactly representable in binary, and so simply storing a real number as a float may add the appearance of significant digits.

However, a fair approximation can be made if you're willing to get the answer wrong for a number with many, many significant digits. Here's an idea in C++. I don't claim that this is entirely portable -- it assumes an architecture using e.g. IEEE-759 floats. That should cover 99.9% of architectures you'll run into, though. It's also possible that this needs some work to support really, really small/large numbers.

#include <limits>
#include <cmath>

using std::numeric_limits;
using std::abs;
using std::floor;

unsigned significant_digits(double target);
unsigned significant_digits_right_of_decimal_point(double target)
{
    target=abs(target);
    return significant_digits(target-floor(target));
}

unsigned significant_digits(double target)
{
    // The number 0.0 is a special case.  How many significant digits should 0
    // have?
    if (target==0.0)
    {
        return 0;
    }

    // make sure our number is positive -- won't change number 
    // of significant digits
    target=abs(target);

    // significant digits don't depend on position of decimal point.
    // divide or multiply until we are between 0.1 and 1.0.
    // FIXME: dividing by 10 may lose some precision
    while (target<0.1)
    {
        target*=10;
    }
    while (target>=1.0)
    {
         target/=10;
    }

    // Multiply by 2 until we've got 1.0 or higher.  This shouldn't change the
    // mantissa.
    unsigned exponent=0;
    while (target<1.0)
    {
        ++exponent;
        target*=2.0;
    }

    // ok, now we know the exponent.  Figure out what the "round off" could be
    double epsilon=numeric_limits<double>::epsilon();

    // return our number back to where it had been, correcting epsilon along
    // the way.
    while (exponent>0)
    {
        --exponent;
        target/=2.0;
        epsilon/=2.0;
    }

    // now that we have an error bound, we can calculate the number of
    // significant digits.  First, shave off any leading '0's
    while (target<1.0)
    {
        target*=10;
        epsilon*=10;
    }

    // now, extract digits until nothing significant is left
    unsigned result=0;
    do 
    {
        ++result;
        target=target-floor(target);
        target*=10;
        epsilon*=10;
    } 
    while (target<=epsilon);
    return result;
 }
Managu
Oops, need to learn to read. Wrote a routine to calculate all significant digits. Oh well, quick fix...
Managu
A: 

locate the decimal point. move the decimal to the right of the first nonzero digit. count the number of places from the decimal point to the end of the number. this will be the exponent. drop all the zeros

Princess Jessabell