28

I guess another way of phrasing this question is what decimal place can you go to using a float that will only be between 0 and 1?

I've tried to work it out by looking at the MSDN. Which says the precision is 7 digits. I thought that meant it could only track changes of 0.0000001.

However if I do:

float test = 0.00000000000000000000000000000000000000000001f;
Console.WriteLine(test);

It writes out 9.949219E-44

If I add any more zeroes, it will output 0.

I'm pretty sure I'm missing something here as that degree of accuracy seems massively wrong. Mainly as a float is 32bits in size, and just from 0-1 at that level of accuracy contains 1e+44 possible numbers...

MatthewMcGovern
  • 3,466
  • 1
  • 19
  • 19
  • 4
    It's 7 *significant figures*. But you need to work take denormal numbers etc into account too. – Jon Skeet Jul 30 '13 at 14:30
  • 2
    Wikipedia articles to start from: [Single-precision floating-point format](http://en.wikipedia.org/wiki/Single-precision_floating-point_format) and [Subnormal number](http://en.wikipedia.org/wiki/Denormal_number). – Jeppe Stig Nielsen Jul 30 '13 at 14:32
  • 1
    Another follow-up: Lang spec on [floating point types](http://msdn.microsoft.com/en-us/library/aa691146%28v=vs.71%29.aspx) – Brad Christie Jul 30 '13 at 14:32
  • [this converter](http://www.h-schmidt.net/FloatConverter/) may help you become familiar with how a `float` really works. – harold Jul 30 '13 at 14:52
  • 3
    You're incorrectly assuming that a `float` has an absolute precision. Instead, its precision changes with its scale. – Russell Borogove Jul 30 '13 at 19:28

5 Answers5

31

How many unique values are there between 0 and 1 of a standard float?

This is not really the question you want an answer for, but the answer is, not including 0 and 1 themselves, that there are 2**23 - 1 subnormal numbers and 126 * 2**23 normal numbers in this range, for a total of 127 * 2**23 - 1, or 1,065,353,215.

But note that these numbers are not evenly distributed on the interval between 0 and 1. Using a "delta" of 1f / 1065353215f in a loop from 0f to 1f will not work for you.

If you want to step from 0.0 to 1.0 with eqally long steps of the (decimal) form 0.00...01, maybe you should use decimal instead of float. It will represent numbers like that exactly.

If you stick to float, try with 0.000001 (ten times greater than your proposed value), but note that errors can build up when performing very many additions with a non-representable number.

Also note: There are a few "domains" where you can't even count on the first seven significant decimal digits of a float. Try for example saving the value 0.000986f or 0.000987f to a float variable (be sure the optimization doesn't hold the value in a "wider" storage location) and write out that variable. The first seven digits are not identical to 0.0009860000 resp. 0.0009870000. Again you can use decimal if you want to work with numbers whose decimal expansions are "short".

Edit: If you can use a "binary" step for your loop, try with:

float delta = (float)Math.Pow(2, -24);

or equivalently as a literal:

const float delta = 5.96046448e-8f;

The good thing about this delta is that all values you encouter through the loop are exactly representable in your float. Just before (under) 1f, you will be taking the shortest possible steps possible for that magnitude.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • I perhaps did ask the wrong question then. I was asking about a float as it's for a parameter that's part of a framework so I cannot change it to Decimal. It seems for what I really need to use is just: 0.000001 but that cuts my potential values to too low. I may have to take another route like using ints and then normalising between 0 and 1. – MatthewMcGovern Jul 30 '13 at 15:26
  • @MatthewMcGovern Steps of the form `0.00...01` are just not good with a binary type like `float`. See my recent edit for a step that is a "round" number in binary. – Jeppe Stig Nielsen Jul 30 '13 at 15:35
  • 1
    "What Every Computer Scientist Should Know About Floating-Point Arithmetic" (http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#929) Does exactly what it says on the tin. – Jacob Jul 30 '13 at 16:16
  • 9
    If you calculate `0.000001*i` in the loop, rather than summing, you'll only have one rounding-error to deal with, rather than an accumulation. – BlueRaja - Danny Pflughoeft Jul 30 '13 at 19:41
  • @BlueRaja-DannyPflughoeft Good idea. Also related to the comment above by MatthewMcGovern himself where he talks about using an integer for the loop and "normalize". – Jeppe Stig Nielsen Jul 30 '13 at 19:45
17

It's 7 significant digits, that is, when writing it in exponential notation you ignore the exponent.

0.0000000000000000001234567 has the same number of significant digits as 12345670000000000000, just with a different exponent. That's the magic that allows floating-point numbers to store really small and really large numbers.

As for exactly how many possible numbers there are for a float in (0, 1) I cannot say exactly right now. You have a 23-bit mantissa, so 223 possible states of it. Then there is an 8-bit exponent and if I'm not terribly mistaken about half of its possible values will result in a number between 0 and 1. Which should leave you with about 223 + 7 = 230 possible values in that range. If anything that's perhaps an upper bound and not the exact value. I would need to consult documentation about the finer details to know exactly (and probably rethink the math above which might miss a few points).

Joey
  • 344,408
  • 85
  • 689
  • 683
  • Would I run into any problems if I was working with values `0 <= x >= 1` using steps of `0.00000000000000000000000000000000000000000001f`? – MatthewMcGovern Jul 30 '13 at 14:32
  • So can we determine the exact number of distinct values possible between 0 and 1 given this fact about significant digits? Would it be something like 10^7 times the number of possible negative exponents? – Mike Mertsock Jul 30 '13 at 14:33
  • When he asks for `0.00000000000000000000000000000000000000000001f` and gets `9.949219e-44f`, he's into the ***subnormal*** range for the `Single` type. His first 5 or 7 significant decimal figures are not correct in that case, because the number is subnormal. – Jeppe Stig Nielsen Jul 30 '13 at 14:36
  • 4
    If I count correctly, the number of positive subnormal numbers is `2**23 - 1`, while the number of positive normal numbers strictly under one is `126 * 2**23`. The total number of numbers in the open interval from zero to one is then `127 * 2**23 - 1`, or `1,065,353,215`. – Jeppe Stig Nielsen Jul 30 '13 at 14:44
  • That's where my rough knowledge right now breaks down, but apparently my upper bound of 2^30 is correct and probably close enough in that case. I'll leave this up here and if you want to write a factually-correct answer without any guesswork or approximations (which is beyond my knowledge right now) you can do so :-) – Joey Jul 30 '13 at 14:48
  • @EricPostpischil I figured out as you commented and have already updated my comment above. – Jeppe Stig Nielsen Jul 30 '13 at 14:49
13

I wrote this very silly program, and it gives the answer 1065353217, which is indeed just shy of 230 (1073741824). Subtract 2 from that number if you were looking for all the numbers not including 0 and 1. By the way, the smallest non-zero number appears to be 1.401298E-45.

class Program
{
    public unsafe static int Search()
    {
        int i = 0;
        float* f = (float*)&i;
        while (*f >= 0 && *f <= 1)
        {
            f = (float*)&i;
            i++;
        }

        return i;
    }

    static void Main(string[] args)
    {
        Console.WriteLine(Search());
        Console.ReadLine();
    }
}
Magnus Grindal Bakken
  • 2,083
  • 1
  • 16
  • 22
  • In my (corrected) comment to Joey's answer I get `1,065,353,215`, so we agree. – Jeppe Stig Nielsen Jul 30 '13 at 14:53
  • Hmm, I've just been experimenting with using such small values but I'm struggling to pull off what I need. It seems any multiplication will destroy the accuracy. Also, if the smallest number is `1.401298E-45`, why doesn't looping through and adding that `1,065,353,215` times equal 1? – MatthewMcGovern Jul 30 '13 at 15:20
  • 3
    @MatthewMcGovern -- the step size between neighboring floating point numbers changes with their scale, so there isn't a single floating point value to step by to hit them all. What are you actually trying to do? – Russell Borogove Jul 30 '13 at 19:26
  • @RussellBorogove: In short, fake Z-depth in a 2D Isometric game. I was trying to come up with a clever way to calculate the depth of each object, as there are potentially thousands in the X Y and Z axis and the depth parameter has to be a float between 0 and 1. I'm probably abandoning this idea and just jumping to 3D as it solves that problem and more instantly. – MatthewMcGovern Jul 31 '13 at 10:07
  • @MatthewMcGovern If I understand you correctly, you should be able to pretend the quantization is the reciprocal of 2^23. Near 1.0 this is correct, and near 0.0 the actual quantization is finer. – Russell Borogove Jul 31 '13 at 14:36
11

Positive floating-point values are ordered the same as their encodings. 0.0f is 0x00000000. 1.0f is 0x3f800000. So there are 0x3f800000 - 1 floating point values that lie strictly in between, or 1,065,353,215.

If you want to include the endpoints in your count, keep in mind that there are two encodings of zero.

Keep in mind, too, that floating-point values are not uniformly spaced. The difference between 1.0f and the next smaller number is 2**-24, whereas the difference between 0.0f and the next larger number is 2**-149. If you want to increment a float from 0 to 1 with uniform steps, the smallest step size you can use is 2**-24.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
0

Since float type has 4 byted-data, we can check all possible variants of all-4 bytes(0-255) with the code below and count how many of them are in range [0,1] - inclusive
So the answer will be 1065353218

P.S. Code execution can take up to 2-3 minutes depending on pc

public static void Main()
    {
        long count = 0;
        //byte checking sub/method
        void CheckThisBytes(byte ii, byte jj, byte kk, byte ll)
        {
            var data = new[] {ii, jj, kk, ll};
            var f = BitConverter.ToSingle(data, 0);
            //is f in range ?
            if (f >= 0.0 && f <= 1.0)
            {
                count++;
            }
        }
        const int max = 255;
        // generate all possible cases 
        for (var i = 0; i <= max; i++)
        {
            for (var j = 0; j <= max; j++)
            {
                for (var k = 0; k <=max; k++)
                {
                    for (var l = 0; l <= max; l++)
                    {
                        //check if current float is in range
                        CheckThisBytes((byte) i, (byte) j, (byte) k, (byte) l);
                    }
                }
            }
        }
        Console.WriteLine("\n Count:" + count);
        Console.ReadLine();
        //result will be  1065353218
    }
Ramin Rahimzada
  • 452
  • 6
  • 10