Resolved This was working, but now it's causing a stack overflow. Please help!

EYEris

Member
Joined
Nov 28, 2021
Messages
6
Programming Experience
10+
OK, I have this class that represents a "fuzzy bit", or the closed unit interval [0,1].
My intent is to eventually make a "fuzzy byte" class, but first I was trying to create just a "fuzzy pair" of "fuzzy bits", representing values the interval [0,3].
I would later create an abstract base class they would derive from that could take any number of "bits", but one step at a time...

So, the first thing to note is when I instantiate a new Fuzzy.Bit, I still have no issues. I can work with all of them without bugs and all my unit tests pass.

I wasn't having any issues with my Fuzzy.Bit.Pair instances. They were also working and passing all tests.

I overloaded my ToString() for Fuzzy.Bit.Pair and refactored some of my code (This is where I went wrong).

Now, if/when I even try to create an uninitialized Fuzzy.Bit.Pair it gets stuck in a loop causing a stack overflow.

I know it begins with my static members of Fuzzy.Bit.Pair (ie Zero, One, Two, and Three), but the overflow happens while the Pair is creating its Bits.

I assume this has to do with implicit conversions, however, it was working before, so I'm not sure what I changed that broke it.
I have been fidgeting with this code all day and I can't figure out how to resolve this issue.

Any help would be greatly appreciated.

Fuzzy.Bit.cs:
using System;
public partial class Fuzzy {
    const int BASE = 2;
    static int GetPower(int e) =>
        (int)Math.Pow(BASE, e);
   
    [Serializable]
    public class Bit :
        IComparable, IComparable<Bit>, IComparable<decimal>, IComparable<bool>,
        IEquatable<Bit>, IEquatable<decimal>, IEquatable<bool>,
        IFormattable
    {
        private const decimal
            TWO = decimal.One + decimal.One,
            HALF = decimal.One / TWO;
        public static readonly Bit
                OFF = new Bit(decimal.Zero),
                 ON = new Bit(decimal.One);

        private decimal _value = 0M;

        public decimal CarryAmount = 0, BorrowAmount = 0;
        public decimal Clamp(decimal value) {
            while(value < 0) { value += 1; BorrowAmount += 1; }
            while(value > 1) { value -= 1;  CarryAmount += 1; }
            return value;
        }

        public decimal D_Value {
            get => _value;
            set => _value = Clamp(value);
        }
        public decimal R_Value => Math.Round(D_Value, 2);
        public bool B_Value => D_Value >= HALF;
        public int I_Value => B_Value ? 1 : 0;
        public decimal TrueValue => D_Value + CarryAmount - BorrowAmount;

        public Bit(decimal value) => D_Value = value;
        public Bit(bool value) => D_Value = value ? decimal.One : decimal.Zero;

        public int CompareTo(bool b) => B_Value.CompareTo(b);
        public int CompareTo(decimal d) => TrueValue.CompareTo(d);
        public int CompareTo(Bit B) => TrueValue.CompareTo(B.TrueValue);
        public int CompareTo( object o) => TrueValue.CompareTo(o);

        public bool Equals(bool b) =>   B_Value.Equals(b);
        public bool Equals(decimal d) => TrueValue.Equals(d);
        public bool Equals(Bit B) => TrueValue.Equals(B.TrueValue);
        public override bool Equals(object o) =>
            o as Bit != null && TrueValue == (o as Bit).TrueValue;
        public override  int GetHashCode() => TrueValue.GetHashCode();

        private string GenerateString(string trueValue, string roundValue, string carryAmt, string borrowAmt) =>
            trueValue +
            (TrueValue != D_Value ? " (" + roundValue +
            ( CarryAmount > 0 ? $", +{carryAmt}" : "") +
            (BorrowAmount > 0 ? $", - {borrowAmt}" : "") +
            ")" : "");
        public override string ToString() =>
            GenerateString(
                   TrueValue.ToString(),
                     R_Value.ToString(),
                 CarryAmount.ToString(),
                BorrowAmount.ToString()
            );
        public string ToString(string format) =>
            GenerateString(
                   TrueValue.ToString(format),
                     R_Value.ToString(format),
                 CarryAmount.ToString(format),
                BorrowAmount.ToString(format)
            );
        public string ToString(IFormatProvider formatProvider) =>
            GenerateString(
                   TrueValue.ToString(formatProvider),
                     R_Value.ToString(formatProvider),
                 CarryAmount.ToString(formatProvider),
                BorrowAmount.ToString(formatProvider)
            );
        public string ToString(string format, IFormatProvider formatProvider) =>
            GenerateString(
                   TrueValue.ToString(format, formatProvider),
                     R_Value.ToString(format, formatProvider),
                 CarryAmount.ToString(format, formatProvider),
                BorrowAmount.ToString(format, formatProvider)
            );

        public static implicit operator Bit(   bool b) => new Bit(b);
        public static implicit operator Bit(decimal d) => new Bit(d);

        public static implicit operator    bool(Bit B) => B.B_Value;
        public static implicit operator decimal(Bit B) => B.TrueValue;

        //public static explicit operator     Bit(int i) => new Bit(i);
        //public static explicit operator     int(Bit B) => B.I_Value + (int)(B.CarryAmout - B.BorrowAmount);

        public static bool operator  true(Bit B) => B.B_Value;
        public static bool operator false(Bit B) => !B.B_Value;
        public static bool operator ==(Bit B, decimal d) => B.TrueValue == d;
        public static bool operator !=(Bit B, decimal d) => B.TrueValue != d;
        public static bool operator  <(Bit B, decimal d) => B.TrueValue  < d;
        public static bool operator <=(Bit B, decimal d) => B.TrueValue <= d;
        public static bool operator  >(Bit B, decimal d) => B.TrueValue  > d;
        public static bool operator >=(Bit B, decimal d) => B.TrueValue >= d;

        public static Bit operator +(Bit A, Bit B) => A.TrueValue + B.TrueValue;
        public static Bit operator -(Bit A, Bit B) => A.TrueValue - B.TrueValue;

        [Serializable]
        public class Pair :
            IComparable,
            IComparable<Pair>, IComparable<Bit[]>, IComparable<decimal>, IComparable<int>,
            IEquatable <Pair>, IEquatable <Bit[]>, IEquatable <decimal>, IEquatable <int>,
            IFormattable
        {
            const int size = 2, limit = BASE * size - 1;
            public static readonly Pair
                 Zero = new Pair(new Bit[2] { 0, 0 }),
                  One = new Pair(new Bit[2] { 0, 1 }),
                  Two = new Pair(new Bit[2] { 1, 0 }),
                Three = new Pair(new Bit[2] { 1, 1 });

            public Bit[] BitArray = new Bit[2] { 0, 0 };

            public Bit this[int i] {
                get => BitArray[i];
                set => BitArray[i] = value;
            }
            public decimal D_Value =>
                BitArray[0].D_Value * GetPower(0) +
                BitArray[1].D_Value * GetPower(1);
            public decimal R_Value =>
                Math.Round(D_Value, 2);
            public int I_Value =>
                BitArray[0].I_Value * GetPower(0) +
                BitArray[1].I_Value * GetPower(1);
            public decimal TrueValue =>
                D_Value + CarryAmount - BorrowAmount;

            public Pair() =>
                BitArray = Zero;
            public Pair(Bit[] bitArray) =>
                BitArray = Clamp(bitArray);
            public Pair(int i) {
                while(i > 3) { i--;  CarryAmount++; }
                while(i < 0) { i++; BorrowAmount++; }
                switch(i) {
                     case 3: BitArray = Three; break;
                     case 2: BitArray =   Two; break;
                     case 1: BitArray =   One; break;
                    default: BitArray =  Zero; break;
                };
            }

            public int CompareTo(    int i) => I_Value.CompareTo(i);
            public int CompareTo(decimal d) => TrueValue.CompareTo(d);
            public int CompareTo(  Bit[] B) => CompareTo(new Pair(B).TrueValue);
            public int CompareTo(   Pair P) => CompareTo(P.TrueValue);
            public int CompareTo( object o) => TrueValue.CompareTo(o);

            public bool Equals(    int i) => I_Value.Equals(i);
            public bool Equals(decimal d) => TrueValue.Equals(d);
            public bool Equals(   Pair P) => TrueValue.Equals(P.TrueValue);
            public bool Equals(  Bit[] B) => BitArray.Equals(B);
            public override bool Equals(object o) =>
                o as Pair != null && TrueValue == (o as Pair).TrueValue;
            public override int GetHashCode() => TrueValue.GetHashCode();

            private string GenerateString(string trueValue, string roundValue, string carryAmt, string borrowAmt) =>
                trueValue +
                (TrueValue != D_Value ? " (" + roundValue +
                ( CarryAmount > 0 ? $", +{carryAmt}" : "") +
                (BorrowAmount > 0 ? $", -{borrowAmt}" : "") +
                ")" : "");
            public override string ToString() =>
                GenerateString(
                    TrueValue.ToString(),
                    R_Value.ToString(),
                    CarryAmount.ToString(),
                    BorrowAmount.ToString()
                );
            public string ToString(string format) =>
                GenerateString(
                    TrueValue.ToString(format),
                    R_Value.ToString(format),
                    CarryAmount.ToString(format),
                    BorrowAmount.ToString(format)
                );
            public string ToString(IFormatProvider formatProvider) =>
                GenerateString(
                    TrueValue.ToString(formatProvider),
                    R_Value.ToString(formatProvider),
                    CarryAmount.ToString(formatProvider),
                    BorrowAmount.ToString(formatProvider)
                );
            public string ToString(string format, IFormatProvider formatProvider) =>
                GenerateString(
                    TrueValue.ToString(format, formatProvider),
                    R_Value.ToString(format, formatProvider),
                    CarryAmount.ToString(format, formatProvider),
                    BorrowAmount.ToString(format, formatProvider)
                );

            //public static implicit operator Pair(decimal d) => new Pair(d);
            public static implicit operator Pair(    int i) => new Pair(i);
            public static implicit operator Pair(  Bit[] B) => new Pair(B);

            //public static implicit operator decimal(Pair B) => B.TrueValue;
            public static implicit operator   Bit[](Pair B) => B.BitArray;
            public static implicit operator     int(Pair B) => B.I_Value;

            public static bool operator ==(Pair B,     int i) => B.I_Value == i;
            public static bool operator ==(Pair B, decimal d) => B.D_Value == d;
            public static bool operator !=(Pair B,     int i) => B.I_Value != i;
            public static bool operator !=(Pair B, decimal d) => B.D_Value != d;
            public static bool operator  <(Pair B,     int i) => B.I_Value  < i;
            public static bool operator  <(Pair B, decimal d) => B.D_Value  < d;
            public static bool operator <=(Pair B,     int i) => B.I_Value <= i;
            public static bool operator <=(Pair B, decimal d) => B.D_Value <= d;
            public static bool operator  >(Pair B,     int i) => B.I_Value  > i;
            public static bool operator  >(Pair B, decimal d) => B.D_Value  > d;
            public static bool operator >=(Pair B,     int i) => B.I_Value >= i;
            public static bool operator >=(Pair B, decimal d) => B.D_Value >= d;

            public decimal CarryAmount = 0, BorrowAmount = 0;
            static Pair Clamp(Pair P) {
                while(P.BitArray[0].BorrowAmount > 0) {
                    P.BitArray[1] -= 1;
                    P.BitArray[0].BorrowAmount -= 1;
                }
                while(P.BitArray[0].CarryAmount > 0) {
                    P.BitArray[1] += 1;
                    P.BitArray[0].CarryAmount -= 1;
                }
                while(P.BitArray[1].BorrowAmount > 0) {
                    P.BorrowAmount += 1;
                    P.BitArray[1].BorrowAmount -= 1;
                }
                while(P.BitArray[1].CarryAmount > 0) {
                    P.CarryAmount += 1;
                    P.BitArray[1].CarryAmount -= 1;
                }
                return P;
            }
            public static Pair operator +(Pair A, Pair B) =>
                Clamp(new Bit[2]{ A[0] + B[0], A[1] + B[1] });
            public static Pair operator -(Pair A, Pair B) =>
                Clamp(new Bit[2]{ A[0] - B[0], A[1] - B[1] });
            }
        }
    }
}
 
Last edited:
Here are an example of my tests.
Tests:
// These all still work fine
public Fuzzy.Bit A = 0.8m, B = 0.2m, C, D, E, F, G;

C = 3;
D = A + B;
E = A + 1;
F = B - A;
G = E - F;

// These used to work
public Fuzzy.Bit.Pair W, X, Y, Z;

W = new Fuzzy.Bit[] { A, B };
X = Fuzzy.Bit.Pair.Three;
Y = X + W;
Z = X - W;

// Now, this causes a stack overflow
public Fuzzy.Bit.Pair P;
 
Last edited:
And this is the output I'm getting.

The last two calls repeat indefinitely, bouncing back and forth between lines 279 and 190
(Of my original code, I removed comments and things that weren't relevant from the above code).

Trace:
StackOverflowException: The requested operation caused a stack overflow.
System.Decimal.op_LessThan (System.Decimal d1, System.Decimal d2) (at <695d1cc93cca45069c528c15c9fdd749>:0)
EYEris.Assets.Logic.Fuzzy+Bit.Clamp (System.Decimal value) (at Assets/Lib/Logic/Fuzzy.Bit.cs:25)
EYEris.Assets.Logic.Fuzzy+Bit.set_D_Value (System.Decimal value) (at Assets/Lib/Logic/Fuzzy.Bit.cs:33)
EYEris.Assets.Logic.Fuzzy+Bit..ctor (System.Decimal value) (at Assets/Lib/Logic/Fuzzy.Bit.cs:47)
EYEris.Assets.Logic.Fuzzy+Bit.op_Implicit (System.Decimal d) (at Assets/Lib/Logic/Fuzzy.Bit.cs:114)
EYEris.Assets.Logic.Fuzzy+Bit+Pair..ctor (EYEris.Assets.Logic.Fuzzy+Bit[] bitArray) (at Assets/Lib/Logic/Fuzzy.Bit.cs:167)
EYEris.Assets.Logic.Fuzzy+Bit+Pair.op_Implicit (EYEris.Assets.Logic.Fuzzy+Bit[] B) (at Assets/Lib/Logic/Fuzzy.Bit.cs:279)
EYEris.Assets.Logic.Fuzzy+Bit+Pair..ctor (EYEris.Assets.Logic.Fuzzy+Bit[] bitArray) (at Assets/Lib/Logic/Fuzzy.Bit.cs:190)

Line 279 (line 194 in my code above) Corresponds to "BorrowAmount.ToString(formatProvider)" in Fuzzy.Bit.Pair.ToString(IFormatProvider provider)
Line 190 (line 125 above) Corresponds to "public Bit this[int i]" in Fuzzy.Bit.Pair.

I am unsure why these two lines cause a loop.
 
Look deeper into the full callstack. Find where the recurrence is happening.
 
Ok. I've isolated the recursion to the Pair(Bit[] bitArray) constructor and the Clamp(Pair P) method.

From what I can tell, when I attempt to declare a Fuzzy.Bit.Pair, it attempts to construct the static definitions for Pair.Zero, etc. These call the Pair(Bit[] bitArray) constructor, which then clamps the input. But Clamp takes bitArray and casts it to a new Pair, using the same constructor. So the bitArray just keeps getting passed to the constructor over and over again, not allowing Clamp to even do anything.

The purpose of the Clamp method is to take the carry and borrow amounts of the bits in the array and pass them upward to the carry and borrow amounts of the Pair, respectively (if that makes sense).

My questions is how do I resolve this? I have been looking at this code for too long. I know the fix is staring me in the face, but I can't figure it out. lol.
 
Well, if you were using source control, you could revert back to your working version and iteratively apply your changes. But that seems to be water under the bridge now.

I would start by removing all the implicit type conversions so that you are in full control of when any code is invoked, instead of trying to guess when the compiler is trying to help you.
 
I am unsure why these two lines cause a loop.
It's lucky you have a debugger then. Set a breakpoint and step through the code line by line, so you can see exactly what course execution takes and exactly what data is in use at the time.
 
It's lucky you have a debugger then. Set a breakpoint and step through the code line by line, so you can see exactly what course execution takes and exactly what data is in use at the time.
I was already doing this. I was very tired. I could see what it was doing, but couldn't understand why. lol.
Well, if you were using source control, you could revert back to your working version and iteratively apply your changes. But that seems to be water under the bridge now.

I would start by removing all the implicit type conversions so that you are in full control of when any code is invoked, instead of trying to guess when the compiler is trying to help you.
True, but so far, I've only started working on this one class as a sort of experiment. I'll probably make a repo for it later today.

I solved the recursion. It's as I said, the Clamp() method was continually casting the array to a new Pair(). I realized that I'm not actually trying to modify the Pair that's passed in, but rather the Pair that is calling the method.

This was all wrong.:
static Pair Clamp(Pair P) {
    while(P.BitArray[0].BorrowAmount > 0) {
        P.BitArray[1] -= 1;
        P.BitArray[0].BorrowAmount -= 1;
    }
    while(P.BitArray[0].CarryAmount > 0) {
        P.BitArray[1] += 1;
        P.BitArray[0].CarryAmount -= 1;
    }
    while(P.BitArray[1].BorrowAmount > 0) {
        P.BorrowAmount += 1;
        P.BitArray[1].BorrowAmount -= 1;
    }
    while(P.BitArray[1].CarryAmount > 0) {
        P.CarryAmount += 1;
        P.BitArray[1].CarryAmount -= 1;
    }
    return P;
}

Because I was passing a Bit[] to the Clamp method, it was attempting to cast it to a Pair, using the Pair(Bit[]) constructor. This constructor passes the Bit[] to the Clamp() method, causing the recursion.

This is what it should have been:
public Bit[] Clamp(Bit[] B) {
    while(P.BitArray[0].BorrowAmount > 0) {
        B[1] -= 1;
        B[0].BorrowAmount -= 1;
    }
    while(P.BitArray[0].CarryAmount > 0) {
        B[1] += 1;
        B[0].CarryAmount -= 1;
    }
    while(P.BitArray[1].BorrowAmount > 0) {
        BorrowAmount += 1;
        B[1].BorrowAmount -= 1;
    }
    while(P.BitArray[1].CarryAmount > 0) {
        CarryAmount += 1;
        B[1].CarryAmount -= 1;
    }
    return B;
}

OK, so now it's "working" again. Now that everything is hooked up, I realised that I have my Bit places swapped and so my math logic is messed up. lol.

But at least I'm making progress again. Thanks guys for your help! :)
 
Back
Top Bottom