Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Default value for Rational Number struct

Tags:

c#

struct

I'm working on a simple math library for educational purposes and I've implemented a struct that represents a Rational Number. Very basic code showing the core fields of the struct is:

public struct RationalNumber
{
    private readonly long numerator;
    private readonly long denominator;
    private bool isDefinitelyCoprime;
    private static RationalNumber zero = 0;

    public RationalNumber(long numerator, long denominator)
    {
        this.numerator = numerator;
        this.denominator = denominator;
        this.isDefinitelyCoprime = false;
    }

    ...
}

Currently I'm implementing a RationalMatrix which, as you've probably guessed, will be made up of RationalNumber typed elements.

A useful matrix that I'm creating a static builder for is the Identity matrix. Code is as follows:

public static RationalMatrix GetIdentityMatrix(int dimension)
{
    RationalNumber[,] values = new RationalNumber[dimension, dimension];

    for (int i = 0; i < dimension; i++)
       values[i, i] = 1;

    return new RationalMatrix(values);
}

The problem is that this will not work because the default value of my RationalNumber is not 0/1 but 0/0 which is a special kind of value (Indeterminate form).

Obviously one solution is straightforward and it is to simply change the method to:

public static RationalMatrix GetIdentityMatrix(int dimension)
{
    RationalNumber[,] values = new RationalNumber[dimension, dimension];

    for (int i = 0; i < dimension; i++)
       for (int j = i+1 ; j < dimension; j++)
       {
           values[i, i] = 1;
           values[i, j] = RationalNumber.Zero;
           values[j, i] = RationalNumber.Zero;
       }

       return new RationalMatrix(values);
}

But this somehow seems a waste of effort as I'm basically initializing the values of the whole array two times. I kind of think it would be more elegant to somehow make the default value of RationalNumber equal 0/1. This would be easy to do if RationalNumber were a class, but I can't think of a way to do it when it's a struct. Am I missing something obvious or is there no way to avoid having 0/0 as my default value?

I'd like to point out that I am not concerned at all about code performance (if this were to be my bottleneck then I'd be far past my goals already). I'm just curious to know if there is some construct (unknown to me) that allows you to impose arbitrary default values in a struct.

EDIT: Typos

EDIT 2: Broaden scope of question

OK, it seems there is no way to impose arbitrary default values in a struct from the input I'm getting and from my own conclusions based on my limited C# knowledge.

Can someone give me a clue as to why structs must behave this way? Is it for a reason or was it implemented this way because no one thought to specify the option to define default values?

like image 752
InBetween Avatar asked May 03 '13 12:05

InBetween


2 Answers

If you do not have to distinguish between the indeterminate 0/0 and other 0/N values, then you can treat all your 0/N as zero. That is, all zeros are equal which makes sense (0/2 equals 0/1), and also all divisions by zero are equal, so 1/0 == 2/0.

public struct RationalNumber : IEquatable<RationalNumber>
{
    private readonly long numerator;
    private readonly long denominator;

    public RationalNumber(long numerator, long denominator)
    {
        this.numerator = numerator;
        this.denominator = denominator;
    }

    public bool IsZero 
    { 
       get { return numerator == 0; }
    }

    public bool IsInvalid 
    { 
       get { return denominator == 0 && numerator != 0; }
    }

    public bool Equals(RationalNumber r)
    {
       if (r.IsZero && IsZero)
         return true;
       if (r.IsInvalid && IsInvalid)
         return true;
       return denominator == r.denominator && numerator == r.numerator;
    }

    public bool Equals(object o)
    {
       if (!(o is RationalNumber))
         return false;
       return Equals((RationalNumber)o);
    }

    public int GetHashCode()
    {
       if (IsZero) 
         return 0;
       if (IsInvalid)
         return Int32.MinValue;
       return ((float)numerator/denominator).GetHashCode();
    }
}   
like image 59
Anders Forsgren Avatar answered Nov 20 '22 02:11

Anders Forsgren


You cannot have a parameterless constructor that assigns default value. The technical reason is that your struct is a subclass of System.ValueType, and System.ValueType() is protected, so cannot be overridden.

The closest you can get is probably David Hefferman's solution:

/// <summary>
/// The denominator is stored in this altered form, because all struct fields initialize to 0 - and we want newly created RationalNumbers to be 0/1 more often than 0/0.
/// </summary>
private int _denominatorMinusOne;
public int Denominator 
{ 
    get { return _denominatorMinusOne + 1; } 
    set { _denominatorMinusOne = value -1; } 
}

Then you can just reference Denominator in your code normally, and the special storage format will be transparent - you would only be able to tell by looking at the field declaration, or by scrutinizing the default constructor behavior.

You could do things like call a constructor with parameters, or create a RationalNumberFactory class to produce zeros for you - but none of these would get around your issue of looping through every element of the matrix instead of just the diagonal, because you cannot specify the constructor that an array initializer will use.

In fact, the new RationalNumber[100][100] convention is not simply a coding shorthand, but it also runs faster than calling the constructor 10,000 times. This is part of why System.ValueType() was made protected in the first place. See: Why can't I define a default constructor for a struct in .NET?

Looping through every element of the matrix provides an advantage in clarity, but using the "weird" minus one solution not only reduces how much code you have to run, but provides improvements in performance. So you could take this as a strong argument in its favor.

like image 20
Superbest Avatar answered Nov 20 '22 02:11

Superbest