Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing floats as string! can I? Is it valid approach?

In order to check if difference between two float numbers is 0.01 I do this

if ((float1 - float.Parse(someFloatAsStringFromXML).ToString(), System.Globalization.CultureInfo.InvariantCulture)).ToString() == "0,01000977")

Is this type of "approach" is acceptable? Is there better way? How to?

p.s. I'm very new to c# and strong typing languages! So, if you have more than brief explanation, I would love to read it!

I forgot to mention, that numbers are "353.58" and "353.59". I have them as strings with dot "." not "," that is the reason why I use float.Parse

like image 808
featherbits Avatar asked Oct 27 '25 14:10

featherbits


2 Answers

Firtly, you should always compare numbers as their underlying types (float, double, decimal), NOT as strings.

Now, you might think that you can compare like so:

float floatFromXml = float.Parse(someFloatAsStringFromXML);

if (Math.Abs(float1 - floatFromXml) == 0.01)

My example works as follows:

Firstly, calculate the difference between the two values:

float1 - floatFromXml

Then take the absolute value of that (which just removes the minus sign)

Math.Abs(float1 - floatFromXml)

Then see if that value is equal to 0.01:

if (Math.Abs(float1 - floatFromXml) == 0.01)

And if you don't want to ignore the sign, you wouldn't do the Math.Abs() part:

if ((float1 - floatFromXml) == 0.01)

But that won't work for your example because of rounding errors!

Because you are using floats (and this would apply to doubles too) you are going to get rounding errors which makes comparing the difference to 0.01 impossible. It will be more like 0.01000001 or some other slightly wrong value.

To fix that, you have to compare the actual difference to the target difference, and if that is only different by a tiny amount you say "that'll do".

In the following code, target is the target difference that you are looking for. In your case, it is 0.01.

Then epsilon is the smallest amount by which target can be wrong. In this example, it is 0.00001.

What we are saying is "if the difference between the two numbers is within 0.00001 of 0.1, then we will consider it as matching a difference of 0.1".

So the code calculates the actual difference between the two numbers, difference, then it sees how far away that difference is from 0.01 and if it is within 0.00001 it prints "YAY".

using System;

namespace Demo
{
    class Program
    {
        void Run()
        {
            float f1 = 353.58f;
            float f2 = 353.59f;

            if (Math.Abs(f1 - f2) == 0.01f)
                Console.WriteLine("[A] YAY");
            else
                Console.WriteLine("[A] Oh dear"); // This gets printed.

            float target = 0.01f;
            float difference = Math.Abs(f2 - f1);
            float epsilon = 0.00001f; // Any difference smaller than this is ok.

            float differenceFromTarget = Math.Abs(difference - target);

            if (differenceFromTarget < epsilon)
                Console.WriteLine("[B] YAY"); // This gets printed.
            else
                Console.WriteLine("[B] Oh dear"); 
        }

        static void Main()
        {
            new Program().Run();
        }
    }
}

However, the following is probably the answer for you

Alternatively, you can use the decimal type instead of floats, then the direct comparison will work (for this particular case):

decimal d1 = 353.58m;
decimal d2 = 353.59m;

if (Math.Abs(d1 - d2) == 0.01m)
    Console.WriteLine("YAY"); // This gets printed.
else
    Console.WriteLine("Oh dear");
like image 148
Matthew Watson Avatar answered Oct 29 '25 04:10

Matthew Watson


You give an example of your two numbers

353.58
353.59

These numbers cannot be exactly represented in binary floating point format. Also, the value 0.01 cannot be exactly represented in binary floating point format. Please read What Every Computer Scientist Should Know About Floating-Point Arithmetic.

So, for example, when you try to represent 353.58 as a float, there is no float with that value and you get the closest float value which happens to be 353.579986572265625.

In my view you are using the wrong data type to represent these values. You need to be using a decimal format. That will allow you to represent these values exactly. In C# you use the decimal type.

Then you can write:

decimal value1 = 353.58m;
decimal value2 = 353.59m;
Debug.Assert(Math.Abs(value2-value1) == 0.01m);

Use decimal.Parse() or decimal.TryParse() to convert from your textual representation of the number into a decimal value.

like image 27
David Heffernan Avatar answered Oct 29 '25 04:10

David Heffernan