Click here to Skip to main content
Click here to Skip to main content

Fraction class in C#

By , 14 Feb 2005
 

Introduction

This article demonstrates how to remove the problem of limited precision when numbers such as 1/10, 1/7, 1/3, etc. are represented in a floating point variable type in C#. Yes, you guessed it right, this problem can be solved by storing such numbers in a fraction format. As a fraction object stores its numerator and denominator as integer variables, there is no loss of accuracy. For this purpose, I have written a simple C# class representing fractions. It can be used in various applications, e.g., equation solving, matrix transformations, etc.

Features of class

The class contains a variety of overloaded constructors and operators for certain situations. It also throws certain exceptions, e.g., when denominator is tried to assign a 0 value, when the result of an arithmetic exceeds the limit (range), etc. One more feature is the automatic normalization (simplification) of fractions. The class uses data type 'long integer' for storing numerator and denominator, hence its range is upper bounded by the range of Int64 in .NET framework.

Using the code

The class includes a variety of constructors, e.g., one that takes an integer like 123, one that takes a double like 156.25, one that takes a string that has all the above qualities (e.g., string can be "32/77" or "213" or "321.44"), one that takes values of numerator and denominator like 231 and 101, and, of course, a parameter-less constructor for a "zero" fraction stored as 0/1.

Fraction frac=new Fraction(); // we'll get 0/1
frac=new Fraction(1,5);       // we'll get 1/5
frac=new Fraction(25);        // we'll get 25/1
frac=new Fraction(9.25);      // we'll get 37/4
frac=new Fraction("6.25");    // we'll get 25/4

frac=new Fraction( System.Console.ReadLine() );
     // we can enter anything like "213" or 
     // "23/3" or "4.27"

Console.WriteLine( frac );
// displays the current value of frac1 object;

Operators overloaded (overloaded for fractions, integers and doubles) for Fraction object include:

  • Unary: - (Negation)
  • Binary +, -, *, /
  • Relational operators such as ==, !=, <, >, <=, >=.
Fraction frac=new Fraction("1/2"); // initialize a fraction with 1/2
Console.WriteLine( frac+2.5 );     // will display 3

Overloaded conversion further enhances the capabilities of the class. See how simple it is to work with fractions:

Fraction frac="1/2" // implicit cast from string to 
frac="22.5"         // implicit cast from string to fraction
frac=10.25          // implicit cast from double to fraction
frac=15             // implicit cast from integer/long to fraction

Finally, as an exercise, guess the output of the following code:

Fraction f=0.5;                 // initialize frac=1/2
Console.WriteLine( f-0.25 );    // Yes, you are right. "1/4" is displayed
Console.WriteLine( f+"1/4" );
   // not sure??? It will display "3/4" because "1/4" has 
   // been converted to fraction and then added to our frac object

Implementation details

The class uses simple mathematics to do all the work. Let us see some of these simple techniques:

  • To convert a double to a fraction, we keep multiplying the given double number with 10 until it is converted to an integer number.
  • To convert a given string to a fraction, we treat all the value before "/" as numerator and after "/" as denominator.
  • To normalize a fraction, we divide its numerator and denominator by their GCD (found by famous Euler's formula).
  • To add two fractions, we use simple school formula to get numerator and denominator of the resultant fraction and then normalize it:
    Numerator = frac1.Numerator*frac2.Denominator 
                + frac2.Numerator*frac1.Denominator;
    Denominator = frac1.Denominator*frac2.Denominator;
  • To overload arithmetic operators for integers and doubles, we first convert them to fractions and then perform the operation.

Applications

There are a lot of applications of Fraction class. An example is a matrix class, see my article on Matrix class in C#.

History

Version 2.0

  • Changed Numerator and Denominator from Int32 (integer) to Int64 (long) for increased range.
  • Renamed ConvertToString() to ToString().
  • Added the capability of detecting/raising overflow exceptions.
  • Fixed the bug that very small numbers, e.g. 0.00000001, could not be converted to fraction.
  • Fixed other minor bugs.

Version 2.1

  • Overloaded conversions from/to fractions.

Version 2.2 (changes by Marc Brooks and Jeffrey Sax).

  • Less overflows by finding the GCD for Add [Jeffery Sax] and Multiply [Marc C. Brooks]
  • Understands and handles NaN, PositiveInfinity, NegativeInfinity just like double [Marc C. Brooks]
  • Fixed several uses of int where long was correct [Marc C. Brooks]
  • Made value-type (struct) [Jeffery Sax]
  • Added ToInt32(), ToInt64() which throw for invalid (NaN, PositiveInfinity, NegativeInfinity) [Marc C. Brooks]
  • Removed redundant Value property [Jeffery Sax]
  • Added explicit conversion to Int32 and Int64 [Marc C. Brooks]
  • Better handling of exceptions [Marc C. Brooks]
  • Reorganized code, added XML doc and regions [Marc C. Brooks]
  • Proper implementations of Equals [Marc C. Brooks, Jeffery Sax]
  • Uses Math.Log(xx,2) and Math.Pow(xx,2) to get the best accuracy possible when converting doubles [Marc C. Brooks, Jeffery Sax]

Version 2.3 (changes by Marc Brooks and Jeffrey Sax)

  • Fixed double-to-fraction logic to use continued fraction rules to get best possible precision [bug fix for Syed Mehroz Alam, idea from Jeffery Sax]
  • Added static readonly values for NaN, PositiveInfinity, NegativeInfinity [idea from Jeffery Sax]
  • Moved comparisons into an implementation of IComparer [idea from Jeffery Sax]
  • No longer throws for NaN(s) involved in Add, Subtract, Multiply, Divide operations [idea from Jeffery Sax]
  • Added static readonly values for Zero, MinValue, MaxValue, Epsilon to better mirror double [Marc C. Brooks]
  • Added IsInfinity to better mirror double [Marc C. Brooks]
  • Added modulus and % operators [Marc C. Brooks]

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Syed Mehroz Alam
Software Developer
Pakistan Pakistan
Member

Syed Mehroz Alam, living in Karachi, Pakistan, is a developer focusing Microsoft technologies. He has completed his bachelors as a Computer Systems Engineer in 2006 and is currently pursuing a Masters degree in Computer Science. He loves to learn, discover and master all aspects of .NET and SQL Server. Mehroz has developed rich internet enterprise applications using Silverlight in addition to the traditional ASP.NET and Windows Forms applications. He has worked with all three components of SQL Business Intelligence Studio: SSIS, SSRS and SSAS for developing BI Solutions and Data warehouse. He loves to write complex TSQL queries and evaluate his skills by participating in various TSQL Challenges. His blog can be viewed at http://smehrozalam.wordpress.com.


Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
SuggestionRe: Square roots [modified]membermla15425 Jul '12 - 5:13 
Your program fails when taking a square root as an argument.  For example,
Mehroz.Fraction f1 = new Mehroz.Fraction(Math.Sqrt(2));
will cause an OverflowException.  So my suggestion is: allow square roots to be entered.  The "ToString" override in the Fraction class could be rewritten to return this (for the above example):
√2
Regards,
Mike


modified 25 Jul '12 - 13:45.

GeneralUnit tests for this class using NUnitmemberSyed Mehroz Alam16 Feb '10 - 2:07 
Got an email from Krzysztof Kniaz[^]. He has written the following NUnit tests to test this. Thanks Knaiz.
 
using System;
using System.Globalization;
using NUnit.Framework;
using Mehroz;
 
namespace TestFraction
{
    /// <summary>
    /// Testing Class
    /// </summary>
    [TestFixture()]
    public class TestFraction
    {
 
        [Test()]
        public void NaN()
        {
            Fraction frac=new Fraction(); // we'll get NaN
            Assert.AreEqual(Fraction.NaN,frac);
            Assert.AreEqual(NumberFormatInfo.CurrentInfo.NaNSymbol, frac.ToString());
        }
 

        [Test()]
        public void OneFifth()
        {
            Fraction frac = new Fraction(1,5);      // we'll get 1/5
            Assert.AreEqual("1/5", frac.ToString());
 
        }
 
        [Test()]
        public void TwentyFive()
        {
            Fraction frac=new Fraction(25);        // we'll get 25
            Assert.AreEqual("25", frac.ToString());
        }
 
        [Test()]
        public void Zero()
        {
            Fraction frac = new Fraction(0, 0);
            Assert.AreEqual("NaN", frac.ToString());
        }
 
        [Test()]
        public void OneFourthFromDecimal()
        {
            Fraction frac = new Fraction(0.25);
            Assert.AreEqual("1/4", frac.ToString());
        }
 
        [Test()]
        public void ThirtySevenFourthsFromDecimal()
        {
            Fraction frac = new Fraction(9.25);
            Assert.AreEqual("37/4", frac.ToString());
        }
 
        [Test()]
        public void LongMaxValDivbyZero()
        {
            Fraction frac = new Fraction(1, long.MaxValue);
            string compareTo = string.Format("1/{0}", long.MaxValue);
            Assert.AreEqual(compareTo, frac.ToString());
        }
 
        [Test()]
        public void LongMaxVal()
        {
            Fraction frac = new Fraction(long.MaxValue, 1);
            string compareTo = string.Format("{0}", long.MaxValue);
            Assert.AreEqual(compareTo,frac.ToString());
        }
 

        [Test()]
        public void TwoPlusOneIssue()
        {
            // the plus-one issue is because of twos-complement representing one more negtive value than
            // positive
            Fraction frac = new Fraction(long.MinValue + 1, 1);
            string compareTo = string.Format("{0}", long.MinValue + 1);
            Assert.AreEqual(compareTo, frac.ToString());
        }
 
        [Test()]
        public void LongMaxbyLongMax()
        {
            Fraction frac = new Fraction(long.MaxValue, long.MaxValue);
            Assert.AreEqual("1",frac.ToString());
        }
 
        [Test()]
        public void LongMinPlusOne()
        {
            Fraction frac = new Fraction(1, long.MinValue + 1);
            string compareTo = string.Format("-1/{0}", Math.Abs(long.MinValue + 1));
            Assert.AreEqual(compareTo,frac.ToString());
        }
 
        [Test()]
        public void LongMinByLongMin()
        {
            Fraction frac = new Fraction(long.MinValue + 1, long.MinValue + 1);
            Assert.AreEqual("1",frac.ToString());
        }
 
        [Test()]
        public void LongMaxByLongMinMinusOne()
        {
            Fraction frac=new Fraction(long.MaxValue, long.MinValue + 1);
            Assert.AreEqual("-1", frac.ToString());
        }
 
        [Test()]
        public void LongMinPlusOneByLongMax()
        {
            Fraction frac = new Fraction(long.MinValue + 1, long.MaxValue);
            Assert.AreEqual("-1", frac.ToString());
        }
 
        [Test()]
        public void OneFourthieth()
        {
            Fraction frac = new Fraction(0.025);    // we'll get 1/40
            Assert.AreEqual("1/40",frac.ToString());
        }
 
        [Test()]
        public void HalfFromFractionalTwo()
        {
            Fraction frac = new Fraction(1 / 2.0);  // we'll get 1/2
            Assert.AreEqual("1/2",frac.ToString() );
        }
 
        [Test()]
        public void ThirdFromFractionalThree()
        {
            Fraction frac = new Fraction(1 / 3.0);  // we'll get 1/3
            Assert.AreEqual("1/3", frac.ToString());
        }
 
        [Test()]
        public void QuarterFromFractionalFour()
        {
            Fraction frac = new Fraction(1 / 4.0);  // we'll get 1/4
            Assert.AreEqual("1/4",frac.ToString());
        }
 
        [Test()]
        public void FifthFromFractionalFive()
        {
            Fraction frac = new Fraction(1 / 5.0);  // we'll get 1/5
            Assert.AreEqual("1/5", frac.ToString());
        }
 
        [Test()]
        public void SixthFromFractionalSix()
        {
            Fraction frac = new Fraction(1 / 6.0);  // we'll get 1/6
            Assert.AreEqual("1/6",frac.ToString());
        }
 
        [Test()]
        public void SeventhFromFractionalSeven()
        {
            Fraction frac = new Fraction(1 / 7.0);  // we'll get 1/7
            Assert.AreEqual("1/7",frac.ToString());
        }
 
        [Test()]
        public void EigthFromFractionalEigt()
        {
            Fraction frac = new Fraction(1 / 8.0);  // we'll get 1/8
            Assert.AreEqual("1/8", frac.ToString());
        }
 
        [Test()]
        public void NinethFromFractionalNine()
        {
            Fraction frac = new Fraction(1 / 9.0);  // we'll get 1/9
            Assert.AreEqual("1/9",frac.ToString());
        }
 
        [Test()]
        public void TenthFromFractionalTen()
        {
            Fraction frac = new Fraction(1 / 10.0);  // we'll get 1/10
            Assert.AreEqual("1/10",frac.ToString());
        }
 
        [Test()]
        public void FourthyNinethFromFractional49()
        {
            Fraction frac = new Fraction(1 / 49.0);  // we'll get 1/49
            Assert.AreEqual("1/49",frac.ToString());
        }
 
        [Test()]
        public void SixFromIntSix()
        {
            Fraction frac = new Fraction(6); 
            Assert.AreEqual("6", frac.ToString() );
        }
 
        [Test()]
        public void FourFromIntFour()
        {
            Fraction frac = new Fraction(4);
            Assert.AreEqual("4",frac.ToString());
        }
 
        [Test()]
        public void Modulo2()
        {
            Fraction frac = new Fraction(6);
            Fraction divisor = new Fraction(4);
            frac %= divisor;
            Assert.AreEqual("2",frac.ToString() );
        }
 
        [Test()]
        public void FractionalDivision1()
        {
            Fraction frac = new Fraction(9,4);
            Fraction divisor = new Fraction(2);
            frac %= divisor;
            Assert.AreEqual("1/4",frac.ToString());
        }
 
        [Test()]
        public void FractionalDivision2()
        {
            Fraction frac = new Fraction(5, 12);
            Fraction divisor = new Fraction(1,4);
            frac %= divisor;
            Assert.AreEqual("1/6",frac.ToString());
        }
 
        [Test()]
        public void FromDecimal1()
        {
            Fraction frac = new Fraction(1.0);
            Assert.AreEqual("1",frac.ToString() );
        }
 
        [Test()]
        public void FromDecimal2()
        {
            Fraction frac = new Fraction(2.0);
            Assert.AreEqual("2",frac.ToString());
        }
 
        [Test()]
        public void FromDecimalMinus2()
        {
            Fraction frac = new Fraction(-2.0);
            Assert.AreEqual("-2",frac.ToString());
        }
 
        [Test()]
        public void FromDecimalMinus1()
        {
            Fraction frac = new Fraction(-1.0);
            Assert.AreEqual("-1",frac.ToString() );
        }
 
        [Test()]
        public void HalfFromDecimal()
        {
            Fraction frac = new Fraction(0.5);
            Assert.AreEqual("1/2",frac.ToString() );
        }
 
        [Test()]
        public void OneAndHalfFromDecimal()
        {
            Fraction frac = new Fraction(1.5);
            Assert.AreEqual("3/2",frac.ToString());
        }
 
        [Test()]
        public void LoopCheck()
        {
            for (int numerator = -100; numerator < 100; numerator++)
            {
 
                for (int denominator = -100; denominator < 100; denominator++)
                {
                    Fraction frac1 = new Fraction(numerator, denominator);
 
                    double dbl = (double)numerator / (double)denominator;
                    Fraction frac2 = new Fraction(dbl);
 
                    Assert.AreEqual(frac2,frac1);
                }
            }
        }
 

        [Test()]
        public void SixandQuarter()
        {
            Fraction frac = new Fraction("6.25");    // we'll get 25/4
            Assert.AreEqual("25/4", frac.ToString());
        }
 
        [Test()]
        public void AssignZero()
        {
            Fraction frac = new Fraction("0.5");
            frac = 0;
            Assert.AreEqual("0", frac.ToString() );
        }
 
        [Test()]
        public void AssignOne()
        {
            Fraction frac = new Fraction("0.5");
            frac = 1;
            Assert.AreEqual("1", frac.ToString() );
        }
 
        [Test()]
        public void PositiveInfinity()
        {
            Fraction frac = new Fraction("2");
            frac /= new Fraction(0);
            Assert.AreEqual(Fraction.PositiveInfinity, frac);
        }
 

        [Test()]
        public void NegativeInfinity()
        {
            Fraction frac = new Fraction("-1");
            frac /= new Fraction(0);
            Assert.AreEqual(Fraction.NegativeInfinity, frac);
        }
 
        [Test()]
        public void Addition()
        {
            Fraction frac = new Fraction("1/2");
            frac += new Fraction(2.5);
            Assert.AreEqual("3", frac.ToString());
        }
 
        [Test()]
        public void Deduction()
        {
            Fraction frac = new Fraction(0.5);
            frac -= new Fraction("1/4");
            Assert.AreEqual("1/4", frac.ToString());
        }
 
        [Test()]
        public void Equation1()
        {
            Fraction frac = new Fraction("1/2");
 
            //0.5 is not Fraction - no implicit conversion here
            Assert.IsFalse(frac.Equals(0.5));
        }
 

        [Test()]
        public void Equation2()
        {
            Fraction frac = new Fraction("1/2");
            Fraction frac1 = new Fraction(0.5);
 
            bool stmtm = frac==frac1;
 
            Assert.IsTrue(stmtm);
        }
 
        [Test()]
        public void Multiplication()
        {
            Fraction frac = new Fraction("1/3");
            frac *= 3;
            Assert.AreEqual("1", frac.ToString());
        }
 
        [Test()]
        public void Division()
        {
            Fraction frac = new Fraction("1/3");
            frac /= 15;
            Assert.AreEqual("1/45", frac.ToString());
        }
 

        [Test()]
        public void ImplicitCast1()
        {
 
            Fraction frac = new Fraction();
            frac = "1/2";            // implicit cast from string to fraction
            Assert.AreEqual("1/2", frac.ToString());
        }
 
        [Test()]
        public void ImplicitCast2()
        {
 
            Fraction frac = new Fraction();
            frac = "22.5";            // implicit cast from string to fraction
            Assert.AreEqual("45/2", frac.ToString());
        }
 
        [Test()]
        public void ImplicitCast3()
        {
 
            Fraction frac = new Fraction();
            frac = 10.25;            // implicit cast from double to fraction
            Assert.AreEqual("41/4", frac.ToString());
        }
 
        [Test()]
        public void ImplicitCast4()
        {
 
            Fraction frac = new Fraction();
            frac = 15;            // implicit cast from int/long to fraction
            Assert.AreEqual("15", frac.ToString());
        }
 

        [Test()]
        public void DoubleNan()
        {
            Fraction frac = new Fraction();
            frac = double.NaN;
            Assert.AreEqual(NumberFormatInfo.CurrentInfo.NaNSymbol,frac.ToString());
        }
 
        [Test()]
        public void DoublePositiveInfinity()
        {
            Fraction frac = new Fraction();
            frac = double.PositiveInfinity;
            Assert.AreEqual(NumberFormatInfo.CurrentInfo.PositiveInfinitySymbol,frac.ToString());
        }
 
        [Test()]
        public void DoubleNegativeInfinity()
        {
            Fraction frac = new Fraction();
            frac = double.NegativeInfinity;
            Assert.AreEqual(NumberFormatInfo.CurrentInfo.NegativeInfinitySymbol, frac.ToString());
        }
 
    }
}
 
Regards,
Syed Mehroz Alam
My Blog | My Articles

Computers are incredibly fast, accurate, and stupid; humans are incredibly slow, inaccurate and brilliant; together they are powerful beyond imagination. - Albert Einstein

GeneralThanks a million!memberanhldbk27 Oct '09 - 19:59 
Smile | :)
 
anhld

GeneralAlso check out the Fraction class from ApachememberSire40427 Mar '09 - 3:03 
Thanks for this class! However, if you need to reduce fractions with a max denominator (accepting rounding errors), check out the Apache implementation:
 
Manual:
http://commons.apache.org/math/apidocs/org/apache/commons/math/fraction/Fraction.html[^]
 
Source (java, very easy to convert):
http://www.apache.org/dev/version-control.html[^]
 
--------------------
No one is perfect. Welll, there was this guy... but we killed him.
coolquotescollection.com

Generalgcd in binarymemberTheKing28 Mar '07 - 10:35 
Apart from being an excellent class I'd like to note that the GCD method is done in base 10 arithmatic. It would benefit greatly if done in binary using binary shifts doing the division. I've located a C++ algorithm on wikipedia to do exactly that http://en.wikipedia.org/wiki/Binary_GCD_algorithm[^]

GeneralSimple changememberDaenris24 Sep '06 - 13:26 
Hey, I needed a fraction class that would display things like "1 1/2" and such, instead of showing it as 3/2.
 
A quick change to the ToSting() function provides this. Certainly not the best solution, but it was quick and it does what I needed. Great class all around though.
 
            ///
            /// Get the value of the Fraction as a string, with proper representation for NaNs and infinites
            ///
            /// The string representation of the Fraction, or the culture-specific representations of
            /// NaN, PositiveInfinity or NegativeInfinity.
            /// The current culture determines the textual representation the Indeterminates
            public override string ToString()
            {
                if (this.m_Denominator == 1)
                {
                    return this.m_Numerator.ToString();
                }
                else if (this.m_Denominator == 0)
                {
                    return IndeterminateTypeName(this.m_Numerator);
                }
                else
                {
//UPDATE HERE TO SHOW INT + FRACTION FORM
                    if (this.m_Numerator > this.m_Denominator)
                    {
                        long div = this.m_Numerator / this.m_Denominator;
                        float rem = (float)this.m_Numerator / (float)this.m_Denominator - (float)div;
                        return div.ToString() + " " + new Fraction(rem).ToString();
                    }
                    else
                    {
                        return this.m_Numerator.ToString() + "/" + this.m_Denominator.ToString();
                    }
                }
            }

GeneralRe: Simple changememberDaenris24 Sep '06 - 13:33 
Okay... that's what I get for posting before thinking. The last version introduced potential rounding problems... but using the following instead solves that.
 
if (this.m_Numerator > this.m_Denominator)
                    {
                        long div = this.m_Numerator / this.m_Denominator;
                        Fraction rem = new Fraction(this.m_Numerator - (div*this.m_Denominator),this.m_Denominator);
                        return div.ToString() + " " + rem.ToString();
                    }

GeneralRe: Simple changememberdodiggitydag12 Oct '06 - 15:10 
Why not just encapsulate that method in a different method name?
 
ToMixedNumber()
GeneralA very good classmemberMina Momtaz13 May '06 - 22:33 
this is a very good class. and i used it in my graduation project but do u have the c++ version or do u know any other jordan gaussian class but in c++
 
Mina Momtaz
GeneralBug in CompareTo(Fraction right) method.memberMarc Brooks9 Nov '05 - 7:25 
An old coworker reported an errorConfused | :confused: to me when doing a comparison of 14/5 and 20/1 would yeild the wrong result. For some stupidMad | :mad: reason, I was doing a CrossReducePair (which is only safe when doing multiplication; comparisons are subtraction). Replace that method with this corrected version.
/// <summary>
/// Compares this Fraction to another Fraction
/// </summary>
/// <param name="right">The Fraction to compare against</param>
/// <returns>-1 if this is less than <paramref name="right"></paramref>,
///  0 if they are equal,
///  1 if this is greater than <paramref name="right"></paramref></returns>
public int CompareTo(Fraction right)
{
	// if left is an indeterminate, punt to the helper...
	if (this.m_Denominator == 0)
	{
		return IndeterminantCompare(NormalizeIndeterminate(this.m_Numerator), right);
	}
	
	// if right is an indeterminate, punt to the helper...
	if (right.m_Denominator == 0)
	{
		// note sign-flip...
		return - IndeterminantCompare(NormalizeIndeterminate(right.m_Numerator), this);
	}
	
	// they're both normal Fractions, quick-negate right and add (which is a subtraction)
	right.m_Numerator = -right.m_Numerator;
	Fraction difference = Add(this, right);
	
	if (difference.m_Numerator < 0)
		return -1;
	else if (difference.m_Numerator > 0)
		return 1;
	else
		return 0;
}
Sorry...
 
-- modified at 13:28 Wednesday 9th November, 2005

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 15 Feb 2005
Article Copyright 2004 by Syed Mehroz Alam
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid