简体   繁体   English

有理数结构的默认值

[英]Default value for Rational Number struct

I'm working on a simple math library for educational purposes and I've implemented a struct that represents a Rational Number . 我正在研究用于教育目的的简单数学库,并且已经实现了表示有理数struct 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. 当前,我正在实现一个RationalMatrix ,正如您可能已经猜到的那样,它将由RationalNumber类型的元素组成。

A useful matrix that I'm creating a static builder for is the Identity matrix. 我要为其创建静态生成器的一个有用矩阵是Identity矩阵。 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 ). 问题在于这将不起作用,因为我的RationalNumber的默认值不是0/1而是0/0 ,这是一种特殊的值( 不确定形式 )。

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 . 我认为以某种方式使RationalNumber的默认值等于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 . 如果RationalNumber是一个class ,这将很容易做到,但是当它是一个struct时,我想不出一种方法。 Am I missing something obvious or is there no way to avoid having 0/0 as my default value? 我是否遗漏了明显的东西,还是没有办法避免将0/0作为默认值?

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 . 我只是想知道是否有某种构造(我不知道)允许您在struct施加任意默认值。

EDIT : Typos 编辑 :错别字

EDIT 2 : Broaden scope of question 编辑2 :扩大问题范围

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. 好的,似乎没有办法根据我得到的输入和基于有限的C#知识得出的结论,在struct强加任意默认值。

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? 是出于某种原因还是因为没有人考虑指定用于定义默认值的选项而以这种方式实施?

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. 如果您不必区分不确定的0/0值和其他0 / N值,则可以将所有0 / N视为零。 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. 也就是说,所有零都相等,这是有意义的(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();
    }
}   

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. 技术原因是您的structSystem.ValueType的子类,并且System.ValueType() protected ,因此不能被覆盖。

The closest you can get is probably David Hefferman's solution: 您可以获得的最接近的大概是David 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; } 
}

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. 然后,您可以正常地在代码中引用Denominator ,并且特殊的存储格式将是透明的-您只能通过查看字段声明或通过检查默认构造函数的行为来知道。

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. 您可以执行一些操作,例如使用参数调用构造函数,或者创建RationalNumberFactory类为您生成零-但这些都不能解决遍历矩阵的每个元素而不仅仅是对角线的问题,因为您无法指定数组初始值设定项将使用的构造函数。

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. 实际上, new RationalNumber[100][100]约定不仅是编码的简写形式,而且比调用构造函数10,000次要快。 This is part of why System.ValueType() was made protected in the first place. 这就是为什么首先要protected System.ValueType()部分原因。 See: Why can't I define a default constructor for a struct in .NET? 请参阅: 为什么不能为.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. 因此,您可以将此作为对其有利的有力论据。

It would be nice to supply a default constructor for the struct: 为该结构提供默认的构造函数将是一件好事:

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

However this is not allowed. 但是,这是不允许的。 Nor can you have instance initialisers. 您也不能拥有实例初始化程序。

The answer simply is that you have to accept that the internal field must initalise to zero, but this does not mean that the behavior has to follow. 答案很简单,就是您必须接受内部字段必须初始化为零,但这并不意味着必须遵循行为。

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

(Note: I was actually very surprised to find you can't have static initalisers in structs!) (注意:我实际上很惊讶地发现结构中不能包含静态initalisers!)

It is good when possible to design structures so that any combination of field values will have defined semantics. 尽可能设计结构以使字段值的任何组合都具有定义的语义是很好的。 If this is not done, there will often be no way for the structure to prevent the construction of malformed instances by improperly-threaded code, and for such instances to cause improper behavior in code which is properly threaded. 如果不这样做,则结构通常将无法阻止通过不正确的线程代码构造格式不正确的实例,并且此类实例在正确执行线程的代码中导致不正确的行为。 For example, if a rational-type storage location had numerator and denominator values which were recognized as being definitely coprime, and said location was copied in one thread while its value was changed in another thread, the thread which did the copying could receive an instance where the numerator and denominator were not coprime, but the flag said they were. 例如,如果一个有理类型的存储位置具有被确定为绝对互质的分子和分母值,并且在一个线程中复制了该位置而在另一个线程中更改了它的值,则执行复制的线程可以接收一个实例分子和分母不是互质的地方,但是旗帜说它们是互质的。 Other code which received that instance could fail in weird and bizarre ways as a result of the broken invariant; 接收到该实例的其他代码可能会因不变量破损而以怪异而怪异的方式失败; such failure might occur someplace very distant from the non-threadsafe code which created the broken instance. 此类故障可能发生在与创建损坏实例的非线程安全代码相距很远的地方。

This situation may be remedied by using an immutable class object to hold the rational number, and having a rational-number value type which wraps a private reference to such an object. 这种情况可以通过使用不可变的类对象来保存有理数并具有将私有引用包装到该对象的有理数值类型来解决。 The wrapper type would use a default instance when its private reference is null, or the wrapped instance when it isn't. 当包装类型的私有引用为null时,包装类型将使用默认实例;否则,包装类型将使用包装的实例。 This approach could offer some potential efficiency improvements if the private reference was an abstract type and there were several derived types which satisfied different criteria. 如果私有引用是抽象类型,并且有多个衍生类型满足不同条件,则此方法可能会提高效率。 For example, one could have a derived RationalSmallInteger whose only field was an Int32 , and a RationalLongInteger whose only field was an Int64 (the the Denominator property of both such types would always return 1). 例如,可以有一个派生的RationalSmallInteger它的唯一字段是Int32 ,而一个RationalLongInteger它的唯一字段是Int64 (这两种类型的Denominator属性将始终返回1)。 One could have types where the denominator was non-zero but was validated as being coprime with the numerator or types where it was not; 一个可能具有分母非零但被验证为与分子互质的类型,或者不是分母。 the latter kind of type could hold an initially-null reference to an instance where the numerator and denominator were guaranteed to be coprime. 后一种类型可以保留对分子和分母被保证为互质的实例的初始为空的引用。 Such behavior could improve efficiency in cases like: 在以下情况下,这种行为可以提高效率:

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

The first statement would set the private field of r1 to refer to a RationalNumber.Int32by32Nonreduced instance. 第一条语句将r1的私有字段设置为引用RationalNumber.Int32by32Nonreduced实例。 The second would set r2's private field to point to that same instance. 第二个将设置r2的私有字段指向同一实例。 The third statement would generate a new Int32by32Reduced instance and store a reference to that in the former Int32by32Nonreduced instance, and also in r3's private field. 第三条语句将生成一个新的Int32by32Reduced实例,并将对该引用的引用存储在以前的Int32by32Nonreduced实例以及r3的私有字段中。 The fourth would fetch the aforementioned reference from the former Int32by32Reduced and store it into r4's private field. 第四个将从先前的Int32by32Reduced获取上述引用,并将其存储到r4的私有字段中。 Note that only one reduction operation would be necessary. 请注意,只需要执行一次还原操作。 By contrast, if RationalNumber were a struct which held its values internally, the fourth statement would have no way of re-using the result of the reduction performed by the third. 相比之下,如果RationalNumber是一个内部保留其值的结构,则第四条语句将无法重用第三条语句的减少结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM