 |
|
 |
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
{
[TestFixture()]
public class TestFraction
{
[Test()]
public void NaN()
{
Fraction frac=new Fraction(); Assert.AreEqual(Fraction.NaN,frac);
Assert.AreEqual(NumberFormatInfo.CurrentInfo.NaNSymbol, frac.ToString());
}
[Test()]
public void OneFifth()
{
Fraction frac = new Fraction(1,5); Assert.AreEqual("1/5", frac.ToString());
}
[Test()]
public void TwentyFive()
{
Fraction frac=new Fraction(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()
{
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); Assert.AreEqual("1/40",frac.ToString());
}
[Test()]
public void HalfFromFractionalTwo()
{
Fraction frac = new Fraction(1 / 2.0); Assert.AreEqual("1/2",frac.ToString() );
}
[Test()]
public void ThirdFromFractionalThree()
{
Fraction frac = new Fraction(1 / 3.0); Assert.AreEqual("1/3", frac.ToString());
}
[Test()]
public void QuarterFromFractionalFour()
{
Fraction frac = new Fraction(1 / 4.0); Assert.AreEqual("1/4",frac.ToString());
}
[Test()]
public void FifthFromFractionalFive()
{
Fraction frac = new Fraction(1 / 5.0); Assert.AreEqual("1/5", frac.ToString());
}
[Test()]
public void SixthFromFractionalSix()
{
Fraction frac = new Fraction(1 / 6.0); Assert.AreEqual("1/6",frac.ToString());
}
[Test()]
public void SeventhFromFractionalSeven()
{
Fraction frac = new Fraction(1 / 7.0); Assert.AreEqual("1/7",frac.ToString());
}
[Test()]
public void EigthFromFractionalEigt()
{
Fraction frac = new Fraction(1 / 8.0); Assert.AreEqual("1/8", frac.ToString());
}
[Test()]
public void NinethFromFractionalNine()
{
Fraction frac = new Fraction(1 / 9.0); Assert.AreEqual("1/9",frac.ToString());
}
[Test()]
public void TenthFromFractionalTen()
{
Fraction frac = new Fraction(1 / 10.0); Assert.AreEqual("1/10",frac.ToString());
}
[Test()]
public void FourthyNinethFromFractional49()
{
Fraction frac = new Fraction(1 / 49.0); 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"); 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");
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"; Assert.AreEqual("1/2", frac.ToString());
}
[Test()]
public void ImplicitCast2()
{
Fraction frac = new Fraction();
frac = "22.5"; Assert.AreEqual("45/2", frac.ToString());
}
[Test()]
public void ImplicitCast3()
{
Fraction frac = new Fraction();
frac = 10.25; Assert.AreEqual("41/4", frac.ToString());
}
[Test()]
public void ImplicitCast4()
{
Fraction frac = new Fraction();
frac = 15; 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
|
|
|
|
 |
|
 |
anhld
|
|
|
|
 |
|
|
 |
|
 |
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[^]
|
|
|
|
 |
|
 |
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.
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
{
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();
}
}
}
|
|
|
|
 |
|
 |
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();
}
|
|
|
|
 |
|
 |
Why not just encapsulate that method in a different method name?
ToMixedNumber()
|
|
|
|
 |
|
 |
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
|
|
|
|
 |
|
 |
An old coworker reported an error to me when doing a comparison of 14/5 and 20/1 would yeild the wrong result. For some stupid reason, I was doing a CrossReducePair (which is only safe when doing multiplication; comparisons are subtraction). Replace that method with this corrected version.
Sorry...
-- modified at 13:28 Wednesday 9th November, 2005
|
|
|
|
 |
|
 |
Thanks for this class! I needed something like this just the other day. I incorporated my needs and the changes suggested by Jeffery Sax.
It avoids most 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]
Reorganize code, add 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]
Due to limitations of the CodeProject message system, the XML docs have been stripped. If you want a copy of the revised class, just e-mail me
Here's the revised Fraction.cs class.
using System;
using System.Globalization;
namespace Mehroz
{
|
|
|
|
 |
|
 |
Thank you very much for your nice work. You saved me from rewriting all the code again (taking into account all the suggestions by Jeffrey). I wrote such a simple and in-efficient(in many cases) class because all I wanted to do, was to write a matrix class using fractions so that I can verify my answers to questions of linear algebra.
Congratulations on your nice work.
Thanks,
Syed Mehroz Alam
Email: smehrozalam@yahoo.com
Homepage: Programming Home
URL: http://www.geocities.com/smehrozalam/
|
|
|
|
 |
|
 |
Hi Marc,
Interesting implementation of 'indeterminates.' Maybe 'special values' would have been a better name. Infinities are quite determined. There are a few things you should be aware of, however:- Operations on
NaNs do not throw exceptions. Instead, so the IEEE-754 standard for binary floating-point arithmetic[^] states, the return value should be once again NaN.
On that same note, you need to handle the special case that NaN != NaN is true. In fact, this is the only logical operation with a NaN operand that doesn't return false.
- To be consistent with the rest of the Base Class Libraries, you may want to make these special values available as static readonly fields of the type. You may also want to add the related static methods defined in
Double, like IsPositiveInfinity, etc.
- Your implementation of
Equals still doesn't comply with standard practices, although this is not stated as clearly as it should. Equals is used for object equality, and so should return false if the object being compared is not of type Fraction. Instead, the code in your Equals method belongs in a static, type safe Compare method that behaves like the Compare method of the IComparer interface. All logical operators should call this same method, or a st.
See the Shared Source CLI[^] for details.
- The conversion from
Double can be made simpler by decoding the 64 bit floating-point number. The binary layout is fixed by the CLI standard as that of IEEE-754 double-precision numbers, so it is safe to do this. The way to do this would be to use BitConverter.DoubleToInt64Bits to obtain a usable binary representation, and then move some bits around. This method is guaranteed to work, and is much faster than using Math.Log and Math.Pow. You've added a lot of value to this class. Well done!
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein
Numerical components for C# and VB.NET
|
|
|
|
 |
|
 |
Jeffrey Sax wrote:
Operations on NaNs do not throw exceptions. Instead, so the IEEE-754 standard for binary floating-point arithmetic[^] states, the return value should be once again NaN.
Done. Changed Add and Multiply methods (used for all operations) to return NaN for operations involving left or right of NaN.
On that same note, you need to handle the special case that NaN != NaN is true. In fact, this is the only logical operation with a NaN operand that doesn't return false.
Done. Added a helper for comparing equality of two Fractions, made == and != call down to it. Double returns true from Equals if both values are NaN, so I do too.
Your implementation of Equals still doesn't comply with standard practices, although this is not stated as clearly as it should. Equals is used for object equality, and so should return false if the object being compared is not of type Fraction.
Done. The override now returns false for non-Fraction right-side, otherwise calls the new helper. Since Fraction is a value type, I wonder what object equality means in this case, so I just compare for same values after reduction.
Instead, the code in your Equals method belongs in a static, type safe Compare method that behaves like the Compare method of the IComparer interface. All logical operators should call this same method
Done. Added ICompare.CompareTo method. Since this is a value-type I made it handle the automatic conversions as appropriate. All the operator comparisons now use the type-safe CompareTo method (and internal helpers)
To be consistent with the rest of the Base Class Libraries, you may want to make these special values available as static readonly fields of the type
Done. Added Fraction.NaN, Fraction.PositiveInfinity and Fraction.NegativeInfinity. Also added MinValue, MaxValue and Epsilon. Also added Zero to deal with the issue of no-default constructor for structs (because it's a NaN otherwise).
You may also want to add the related static methods defined in Double, like IsPositiveInfinity, etc.
Those were already there. :grin: I did add IsInfinity
I also fixed the issues this code had with imprecise doubles (repeating decimals). I'm now using the repeating fraction logic I found here which does as good a job as possible and is relatively quick.
Here's the revised code:
using System;
using System.Runtime.InteropServices;
using System.Globalization;
namespace Mehroz
{
|
|
|
|
 |
|
 |
Very cool code.
Just one comment, is it possible to add some fuzzy logic to convert something like 0.166666666667 into 1/6?
Thanks!
Hardy
|
|
|
|
 |
|
 |
Overall, this is a nice implementation of basic fraction functionality. There is still room for improvement, however:- Conversion from Double should use base 2 rather than base 10. The 'ToString()' causes loss of precision in several ways, most notably due to the fact that it uses only 15 digits of precision.
Every double value within the range of Fraction is exactly representable by a Fraction, and so you should make this conversion exact. A round-trip cast expression like (double)(Fraction)x should return the original value x, or throw an OverflowException if x is out of range.
Ideally, you would also offer to find the fraction closest to the number, using Euclid's GCD algorithm backwards. You could put an upper limit on the size of the denominator, so that, for example, 0.333333334 gets converted to 1/3 rather than 166666667/500000000.
- Your addition method can cause overflow when that is not necessary. You should divide out any common factors of the denominators. This common factor will only be divided out in the normalization. You could do something like:
long gcd = GCD(frac1.Denominator, frac2.Denominator);
long denominator1 = frac1.Denominator / gcd;
long denominator2 = frac2.Denominator / gcd;
long iNumerator=frac1.Numerator*denominator2 + frac2.Numerator*denominator1;
long iDenominator=denominator1*denominator2*gcd;
return ( new Fraction(iNumerator, iDenominator) );
Similar improvements are possible for the other operators.
- You may want to consider making this a value type (
struct). The reasoning? We definitely have an object with value semantics here - it is just an alternate representation of a number. Moreover, it is small enough (16 bytes) so the by-value passing of value types shouldn't hinder performance.
It would also eliminate the need for methods like Duplicate and properties like Value, since you can use direct assignments.
- Your implementation of
Equals does not comply with the guidelines[^]. For example, your code throws an exception if the argument isn't of type Fraction. Equals should never throw an exception. There may well be more. Improving code is like ironing out all the wrinkles: you get some of the bigger ones first, and then take care of the progressively smaller ones.
Nice work!
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein
Numerical components for C# and VB.NET
|
|
|
|
 |
|
 |
Thank you for your valuable comments. I always need such comments to improve my programming skills. I admire your ideas and had it not been Marc there, I would have to re-write the code again. I thank you and Marc for your contribution to my class.
I agree with all of your suggestions except for one:
You have said to implement the class as a structure. The problem is that instance field members in a struct can not be initialized to any particular value and a fraction object having a denominator with 0 value is always a bad idea.
To illustrate my point, consider the following code
Struct Fraction
{
}
public static void Main()
{
Fraction frac=new Fraction(); Console.WriteLine( frac.ToString() ) Console.WriteLine( frac.ToDouble().ToString() ) Frac+=1 }
Please do inform me if you have a solution to this problem.
Thanks,
Syed Mehroz Alam
Email: smehrozalam@yahoo.com
Homepage: Programming Home
URL: http://www.geocities.com/smehrozalam/
|
|
|
|
 |
|
 |
Hi Syed,
Syed Mehroz Alam wrote:
You have said to implement the class as a structure. The problem is that instance field members in a struct can not be initialized to any particular value and a fraction object having a denominator with 0 value is always a bad idea.
You make a very good point. It is a strong recommendation that value types should have their natural, default value when uninitialized (or created using the default constructor). For fractions, this value would be 0.
However, I don't see too much wrong with implementing 0 as a special value, and giving it special treatment where required. Most operations would benefit from such special treatment anyway. I feel it would be perfectly acceptable to have the value of zero represented internally as "0/0", especially if you also implement the other special values (NaNs and infinities), as Marc did. The internal representation is not important for the users of your type.
As a 'justification,' you may be interested to know that the value of zero is a special value for the standard floating-point types as well. Floating-point numbers can be written as a sign times a value between 1 and 2 (the significand) times a power of two. Because the leading bit of the significand is always 1, it is implied and not explicitly set in the floating-point formats. This means, however, that the value of zero cannot be treated as most other numbers, because the 'leading' bit of the significand is zero.
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein
Numerical components for C# and VB.NET
|
|
|
|
 |
|