2013-05-03 47 views
10

为了教育目的,我正在研究一个简单的数学库,并且我实现了代表Rational Numberstruct。很显示结构的核心领域基本代码:Rational数字结构的默认值结构

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; 
    } 

    ... 
} 

目前我正在执行一个RationalMatrix,正如你可能已经猜到了,会来弥补的RationalNumber类型元素。

我正在创建一个静态构建器的有用矩阵是标识矩阵。代码如下:

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); 
} 

的问题是,这是行不通的,因为我的RationalNumber默认值是不0/10/0这是一种特殊的值(Indeterminate form)。

显然一个解决办法很简单,它是简单地改变方法:

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); 
} 

但这似乎不知何故费工夫,因为我基本上是初始化整个阵列的两倍的值。我认为它会更优雅,使默认值RationalNumber等于0/1。如果RationalNumber是class,这很容易实现,但我想不出在struct时这样做的方法。我是否错过了一些显而易见的东西,或者有没有办法避免0/0作为我的默认值?

我想指出,我并不关心代码性能(如果这是我的瓶颈,那么我已经远远超过了我的目标)。我只是想知道是否有一些构造(我不知道)允许你在struct中强加任意的默认值。

编辑:错别字

编辑2:拓宽问题的范围

OK,看来是没有办法从我得到的输入强加在struct任意默认值,从我自己的结论基础上我有限的C#知识。

有人可以给我一个线索,为什么结构必须以这种方式表现?是出于某种原因还是以这种方式实施,因为没有人想过指定选项来定义默认值?

+1

非常恶心,但你可以将'denominator'改为'denominatorMinusOne'!所以,当'denominatorMinusOne == 0'这将意味着'分母== 1'。我觉得很难推荐这是一个好主意! – 2013-05-03 12:49:34

+0

@DavidHeffernan也许不是'denominatorMinusOne',因为这意味着不同的范围,但也许'denominatorXorOne'?即使这不是一个好主意,它也许是唯一的想法! – Rawling 2013-05-03 12:58:04

+0

你可以添加类似'private bool isInitialized;'并在您的方法中创造性地使用该属性。 – Arvo 2013-05-03 14:00:58

回答

0

这将是很好提供一个默认的构造函数的结构:

public RationalNumber() 
{ 
    this.numerator = 0; 
    this.denominator = 1; 
    this.isDefinitelyCoprime = false; 
} 

然而,这是不允许的。你也不能有实例初始化器。

答案只是你必须接受内部场必须起始为零,但这并不意味着行为必须遵循。

public struct Rational 
    { 
     private int _numerator; 
     private int _denominator; 
     public Rational(int numerator, int denominator) 
     { 
      // Check denominator is positive. 
      if(denominator < 0){ 
        denominator *= -1; 
        numerator *= -1; 
      } 
      _numerator = numerator; 
      _denominator = denominator== 0? -1: 
       denominator; 
     } 
     public int Numerator 
     { 
      get { return _numerator; } 
     } 
     public int Denominator 
     { 
      get { return 
       _denominator == 0?1: 
       _denominator == -1?0: 
       _denominator; } 
     } 
    } 

(注意:我实际上很惊讶地发现你不能在结构中有静态的initalisers!)

+3

从何时开始允许? – jure 2013-05-03 12:52:30

+2

-1这是行不通的。创建一个'RationalNumber'数组不会调用这个构造函数,因此不允许:*“结构不能包含显式无参数构造函数”* – 2013-05-03 12:53:53

+3

您的第二次编辑又错了。在结构中没有初始值设定项... – jure 2013-05-03 12:59:05

5

如果你不具备不确定的0/0和其他0/N值来区分,那么你可以把你所有的0/N为零。也就是说,所有零都是相等的,这是有意义的(0/2等于0/1),并且所有的零除以相等,所以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(); 
    } 
} 
+0

感谢您的想法。可悲的是,0/0与0/N完全不同,我必须在我的库中这样考虑,所以这不是一种选择。我认为现在最好的方法是遵循Arvo的建议,并创建一个布尔字段来标记结构是否被显式初始化。 – InBetween 2013-05-03 14:12:01

+0

是的,如果你不得不分开这两个状态,你需要一点额外的信息。另外:我会采取在这一个来源的潜行高峰http://msdn.microsoft.com/en-us/library/microsoft.solverfoundation.common.rational(v=vs.93).aspx – 2013-05-03 14:18:09

+0

谢谢!不知道这已经在.NET Framework中实现了。我一定会检查出来的。无论如何,教育目标也是为了我自己。我可以练习一些C#代码,同时记住一些“基础”数学;) – InBetween 2013-05-03 14:29:39

0

如果可能的话,设计结构以便字段值的任意组合都具有定义的语义是很好的。如果没有这样做,那么结构通常就没有办法阻止不正确的线程代码构造格式错误的实例,并且这种情况会导致代码中出现不正确的行为,而这些行为在正确的线程中会发生。例如,如果一个理性类型的存储位置有分子和分母值,这些值被认为是明确的互质,并且所述位置被复制到一个线程中,而其值在另一个线程中被更改,则执行复制的线程可以接收实例分子和分母不是互质,但国旗说他们是。其他收到该实例的代码可能由于不变的不变而以奇怪和怪异的方式失败;这种失败可能发生在离创建破坏实例的非线程安全代码很远的地方。

这种情况可以通过使用一个不可变的类对象来保存有理数,并且具有封装了一个私人参考这样的对象的有理数值类型来补救。封装类型在其私有引用为空时使用默认实例,或者在封装类型不是时包装。如果私人参考是一个抽象类型,并且有几个派生类型满足不同的标准,这种方法可以提供一些潜在的效率改进。例如,一个可以具有衍生RationalSmallInteger其唯一字段是一个Int32RationalLongInteger其唯一字段是一个Int64(所述的Denominator这两种类型的属性将始终返回1)。可以有分母不是零的类型,但是被验证为与分子相互矛盾或者不相匹配;后一种类型可以保持一个最初为空的参考,其中分子和分母被保证是互质的。这种行为可以提高效率,在同类个案:

RationalNumber r1 = new RationalNumber(4,6); 
RationalNumber r2 = r1; 
RationalNumber r3 = r1.ReducedForm(); 
RationalNumber r4 = r2.ReducedForm(); 

第一条语句将设置R1的私有字段来指RationalNumber.Int32by32Nonreduced实例。第二个会将r2的专用字段设置为指向同一个实例。第三条语句将在R3的私人领域产生新的Int32by32Reduced实例,并在存储前Int32by32Nonreduced实例是一个参考,而还。第四个将从前面的Int32by32Reduced中获取上述参考,并将其存储到r4的私人领域。请注意,只需要一次还原操作。相反,如果RationalNumber是一个内部保存其值的结构,则第四个语句将无法重用第三个语句执行的结果。

1

你不能有一个参数的构造函数指派默认值。该技术的原因是,你structSystem.ValueType一个子类,System.ValueType()protected,因此不能被重写。

你可以得到的最接近的可能是大卫Hefferman的解决方案:

/// <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; } 
} 

然后,你可以参考Denominator在你的代码通常和特殊存储格式将是透明的 - 你只能够告诉由查看域声明,或者通过仔细检查默认的构造函数行为。

你可以做一些事情,比如调用一个带参数的构造函数,或者创建一个RationalNumberFactory类来为你产生零 - 但是这些都不会解决你循环矩阵的每一个元素而不是对角线的问题,因为你不能指定数组初始化器将使用的构造函数。

事实上,new RationalNumber[100][100]约定不是简单的编码简写,但它的运行速度比调用构造函数要快10,000倍。这首先是为什么System.ValueType()protected的一部分。请参阅:Why can't I define a default constructor for a struct in .NET?

循环遍历矩阵的每个元素都提供了清晰的优势,但使用“怪异”减号解决方案不仅可以减少您需要运行的代码量,还可以提高性能。所以你可以把它作为一个有利的论点。