Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 'http://tizzyt-archive.blogspot.com/2015/03/quick-and-dirty-net-bigdecimal.html
- Imports System.Numerics
- Public Structure BigDecimal
- Private ILen, MLen As Integer
- Private value As BigInteger
- Public Shared ReadOnly Zero As New BigDecimal(0)
- Public Shared ReadOnly ZeroPointOne As New BigDecimal(0.1)
- Public Shared ReadOnly ZeroPointFive As New BigDecimal(0.5)
- Public Shared ReadOnly One As New BigDecimal(1)
- Public Shared ReadOnly Two As New BigDecimal(2)
- Public Shared ReadOnly ThreePointTwo As New BigDecimal(3.2)
- Public Shared ReadOnly Four As New BigDecimal(4)
- Public Shared ReadOnly Five As New BigDecimal(5)
- Public Shared ReadOnly TwentyFive As New BigDecimal(25)
- Public Shared ReadOnly TwoHundredThirtyNine As New BigDecimal(239)
- Public Shared ReadOnly FiftySevenThousandOneHundredTwentyOne As New BigDecimal(57121)
- #Region "Properties"
- 'Property for number of digits in BigDecimal's integral part
- Public ReadOnly Property IntegralLength() As Integer
- Get
- Return ILen
- End Get
- End Property
- 'Property for number of digits in BigDecimal's mantissa part
- Public ReadOnly Property MantissaLength() As Integer
- Get
- Return ILen
- End Get
- End Property
- 'Property whether BigDecimal is one
- Public ReadOnly Property IsOne() As Boolean
- Get
- If ILen = 1 AndAlso MLen = 0 Then Return value.IsOne Else Return False
- End Get
- End Property
- 'Property whether BigDecimal is zero
- Public ReadOnly Property IsZero() As Boolean
- Get
- Return value.IsZero
- End Get
- End Property
- 'Property of the BigDecimal's sign (negative, zero, positive)
- Public ReadOnly Property Sign() As Integer
- Get
- Return value.Sign
- End Get
- End Property
- 'Property of whether BigDecimal is a power of two
- Public ReadOnly Property IsPowerOfTwo() As Boolean
- Get
- If MLen = 0 AndAlso value.IsPowerOfTwo _
- Then Return True _
- Else Return False
- End Get
- End Property
- 'Property of whether BigDecimal is Even
- Public ReadOnly Property IsEven As Boolean
- Get
- If MLen = 0 AndAlso value.IsEven _
- Then Return True _
- Else Return False
- End Get
- End Property
- #End Region
- #Region "Unsigned Integer Constructors"
- 'Construct a BigDecimal number from Byte
- Sub New(ByVal Num As Byte)
- value = New BigInteger(Num)
- ILen = Num.ToString.Length
- MLen = 0
- End Sub
- 'Construct a BigDecimal number from UShort
- Sub New(ByVal Num As UShort)
- value = New BigInteger(Num)
- ILen = Num.ToString.Length
- MLen = 0
- End Sub
- 'Construct a BigDecimal number from UInteger
- Sub New(ByVal Num As UInteger)
- value = New BigInteger(Num)
- ILen = Num.ToString.Length
- MLen = 0
- End Sub
- 'Construct a BigDecimal number from ULong
- Sub New(ByVal Num As ULong)
- value = New BigInteger(Num)
- ILen = Num.ToString.Length
- MLen = 0
- End Sub
- #End Region
- #Region "Signed Integral Constructors"
- 'Construct a BigDecimal number from Signed Byte
- Sub New(ByVal Num As SByte)
- Dim t As String = Num.ToString & "."
- value = New BigInteger(Num)
- If t.StartsWith("-") Then
- t = (t.Remove(0, 1) & ".").Trim("0"c)
- ILen = t.Length - 1
- Else
- ILen = t.Length - 1
- End If
- MLen = 0
- End Sub
- 'Construct a BigDecimal number from Short
- Sub New(ByVal Num As Short)
- Dim t As String = Num.ToString & "."
- value = New BigInteger(Num)
- If t.StartsWith("-") Then
- t = (t.Remove(0, 1) & ".").Trim("0"c)
- ILen = t.Length - 1
- Else
- ILen = t.Length - 1
- End If
- MLen = 0
- End Sub
- 'Construct a BigDecimal number from Integer
- Sub New(ByVal Num As Integer)
- Dim t As String = Num.ToString & "."
- value = New BigInteger(Num)
- If t.StartsWith("-") Then
- t = (t.Remove(0, 1) & ".").Trim("0"c)
- ILen = t.Length - 1
- Else
- ILen = t.Length - 1
- End If
- MLen = 0
- End Sub
- 'Construct a BigDecimal number from Long
- Sub New(ByVal Num As Long)
- Dim t As String = Num.ToString & "."
- value = New BigInteger(Num)
- If t.StartsWith("-") Then
- t = (t.Remove(0, 1) & ".").Trim("0"c)
- ILen = t.Length - 1
- Else
- ILen = t.Length - 1
- End If
- MLen = 0
- End Sub
- 'Construct a BigDecimal number from BigInteger
- Sub New(ByVal Num As BigInteger)
- Dim t As String = Num.ToString & "."
- value = Num
- If t.StartsWith("-") Then
- t = (t.Remove(0, 1) & ".").Trim("0"c)
- ILen = t.Length - 1
- Else
- ILen = t.Length - 1
- End If
- MLen = 0
- End Sub
- #End Region
- #Region "Decimal Constructors"
- 'Construct a BigDecimal number from Decimal
- Sub New(ByVal Num As Decimal)
- Parse(CStr(Num))
- End Sub
- 'Construct a BigDecimal number from Single
- Sub New(ByVal Num As Single)
- Parse(CStr(Num))
- End Sub
- 'Construct a BigDecimal number from Double
- Sub New(ByVal Num As Double)
- Parse(CStr(Num))
- End Sub
- 'Construct a BigDecimal number from Byte array
- Sub New(ByVal Num() As Byte)
- ILen = BitConverter.ToInt32({Num(0), Num(1), Num(2), Num(3)}, 0)
- MLen = BitConverter.ToInt32({Num(4), Num(5), Num(6), Num(7)}, 0)
- Dim VBytes(Num.Length - 9) As Byte
- Array.Copy(Num, 8, VBytes, 0, VBytes.Length)
- value = New BigInteger(VBytes)
- End Sub
- 'Construct a BigDecimal number from String
- Sub New(ByVal Num As String)
- Parse(Num)
- End Sub
- #End Region
- #Region "Main Parser"
- 'Parses and sets the attributes for BigDecimal
- Private Sub Parse(ByVal Num As String)
- Dim neg As Boolean = False
- Dim dec As Boolean = True
- Dim Str As String = ""
- For Each digit As Char In Num
- Select Case digit
- Case "-"c : neg = True
- Case "0"c To "9"c : Str &= digit
- Case "."c : If dec Then Str &= "." : dec = False
- End Select
- Next
- If Str.Contains(".") Then
- Str = Str.Trim("0"c)
- ILen = Str.IndexOf(".")
- MLen = Str.Length - ILen - 1
- If Str.EndsWith(".") Then Str = Str.Substring(0, Str.Length - 1)
- Else
- Str = (Str & ".").Trim("0"c)
- Str = Str.Substring(0, Str.Length - 1)
- ILen = Str.Length
- MLen = 0
- End If
- If Str = "." OrElse Str = "" Then
- Num = "0"
- Else
- If Str.StartsWith(".") Then Num = "0" & Str Else Num = Str
- If neg Then Num = "-" & Str
- End If
- value = BigInteger.Parse(Num.Replace(".", String.Empty))
- End Sub
- #End Region
- #Region "General Functions"
- 'Does a comparison between the current BigInteger and another returning the first difference
- Public Function CompareTo(ByRef other As BigDecimal) As Integer
- If other.value.Sign = value.Sign Then 'Compares decimal sign
- If other.ILen = ILen Then 'Compares the integral size
- If other.MLen = MLen Then 'Compares the mantissa size
- If other.value = value Then 'Compares the actual value
- Return 0 'the two are the same
- Else
- If other.value > value Then
- Return -6 'Other decimal value is greater
- Else
- Return 6 'Other decimal value is smaller
- End If
- End If
- Else
- If other.MLen > MLen Then
- Return -5 'Other decimal has a larger mantissa length
- Else
- Return 5 'Other decimal has a smaller mantissa length
- End If
- End If
- Else
- If other.ILen > ILen Then
- Return -4 'Other decimal has a larger integral length
- Else
- Return 4 'Other decimal has a smaller integral length
- End If
- End If
- Else
- Select Case value.Sign
- Case 0 'This decimal is zero
- Select Case other.value.Sign
- Case -1 'Other decimal is negative
- Return 3
- Case 1 'Other decimal is positive
- Return -1
- End Select
- Case -1 'This decimal is negative
- Select Case other.value.Sign
- Case 0 'Other decimal is zero
- Return -3
- Case 1 'Other decimal is positive
- Return -2
- End Select
- Case 1 'This decimal is positive
- Select Case other.value.Sign
- Case -1 'Other decimal is negative
- Return 2
- Case 0 'Other decimal is zero
- Return 1
- End Select
- End Select
- End If
- Return 7 'Shouldn't get this far, defaults to not same, Unknown error
- End Function
- 'Represents BigDecimal number as string
- Public Overrides Function ToString() As String
- If value.Sign = 0 Then Return "0"
- Dim AsString As String = value.ToString
- If value.Sign = -1 Then
- AsString = AsString.Remove(0, 1).PadLeft(MLen, "0"c).Insert(ILen, ".")
- If ILen = 0 Then Return "-0" & AsString Else Return "-" & AsString.Trim("."c)
- End If
- AsString = AsString.PadLeft(MLen, "0"c).Insert(ILen, ".")
- If ILen = 0 Then Return "0" & AsString Else Return AsString.Trim("."c)
- End Function
- 'Represents BigDecimal as string with a specified character for thousands delimiter
- Private Const ValDig As String = "-.0123456789" 'Valid characters for bigdecimal string
- Public Overloads Function ToString(ByVal Spacer As Char) As String
- If ValDig.Contains(Spacer) Then Throw New Exception("Invalid Character for use as spacer")
- Dim str As String = ToString()
- If Not Spacer = "" Then
- If value.Sign = -1 Then
- If MLen = 0 Then
- For i = str.Length - 3 To 2 Step -3 : str = str.Insert(i, Spacer) : Next
- Else
- For i = str.IndexOf(".") - 3 To 2 Step -3 : str = str.Insert(i, Spacer) : Next
- End If
- Else
- If MLen = 0 Then
- For i = str.Length - 3 To 1 Step -3 : str = str.Insert(i, Spacer) : Next
- Else
- For i = str.IndexOf(".") - 3 To 1 Step -3 : str = str.Insert(i, Spacer) : Next
- End If
- End If
- End If
- Return str
- End Function
- 'Returns the current BigDecimal represented as an array of bytes
- Public Function ToByteArray() As Byte()
- Dim RBytes As New List(Of Byte)
- RBytes.AddRange(BitConverter.GetBytes(ILen))
- RBytes.AddRange(BitConverter.GetBytes(MLen))
- RBytes.AddRange(value.ToByteArray)
- Return RBytes.ToArray
- End Function
- #End Region
- #Region "Basic Operators"
- 'Does addition on two BigDecimal numbers
- Public Shared Operator +(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As BigDecimal
- Dim L As Integer = Num1.MLen
- Dim D As Integer = 0
- If Num1.MLen > Num2.MLen Then
- D = Num1.MLen - Num2.MLen
- Num2.value = Pow10(Num2, D)
- Else
- L = Num2.MLen
- D = Num2.MLen - Num1.MLen
- Num1.value = Pow10(Num1, D)
- End If
- Return PowN10(BigInteger.Add(Num1.value, Num2.value).ToString, L)
- End Operator
- 'Does subtraction on two BigDecimal numbers
- Public Shared Operator -(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As BigDecimal
- Dim L As Integer = Num1.MLen
- Dim D As Integer = 0
- If Num1.MLen > Num2.MLen Then
- D = Num1.MLen - Num2.MLen
- Num2.value = Pow10(Num2, D)
- Else
- L = Num2.MLen
- D = Num2.MLen - Num1.MLen
- Num1.value = Pow10(Num1, D)
- End If
- Return PowN10(BigInteger.Subtract(Num1.value, Num2.value).ToString, L)
- End Operator
- 'Does multiplication on two BigDecimal numbers
- Public Shared Operator *(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As BigDecimal
- Dim D As Integer = 0
- If Num1.MLen >= Num2.MLen _
- Then D = Num1.MLen - Num2.MLen _
- Else D = Num2.MLen - Num1.MLen
- Dim O As Integer = Num1.MLen + Num2.MLen + D * 2
- Num1.value = Pow10(Num1, D)
- Num2.value = Pow10(Num2, D)
- Return PowN10(BigInteger.Multiply(Num1.value, Num2.value).ToString, O)
- End Operator
- 'Does division on two BigDecimal numbers
- Public Shared Operator /(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As BigDecimal
- Return Div(Num1, Num2)
- End Operator
- 'Does division on two BigDecimal numbers with specified precision (no rounding)
- Public Shared Function Div(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal,
- Optional ByVal Precision As Integer = -1) As BigDecimal
- If Num2.value.IsZero Then Throw New DivideByZeroException
- Dim P As Integer = (Num1.MLen * Num2.MLen) + Num1.ILen + Num2.ILen + 1
- Dim D As Integer = Math.Abs(Num1.MLen - Num2.MLen)
- If Precision > -1 Then P = Precision
- If Num1.MLen >= Num2.MLen Then
- Num1.value = Pow10(Num1, P)
- Num2.value = Pow10(Num2, D)
- Else
- Num1.value = Pow10(Num1, D + P)
- End If
- Return PowN10(BigInteger.Divide(Num1.value, Num2.value).ToString, P)
- End Function
- 'Raise BigDecimal to the power of an integer
- Public Shared Operator ^(ByVal Num1 As BigDecimal, ByVal Num2 As Integer) As BigDecimal
- Return Pow(Num1, Num2)
- End Operator
- 'Raise BigDecimal to power of integer with specified precision for negatives(no rounding)
- Public Shared Function Pow(ByVal Num1 As BigDecimal, ByVal Num2 As Integer,
- Optional ByVal Precision As Integer = -1) As BigDecimal
- If Num2 = 0 Then Return One
- If Num2 < 0 Then
- Num2 = Math.Abs(Num2)
- Dim L As Integer = Num1.MLen * Num2
- Dim R As BigDecimal = PowN10(BigInteger.Pow(Num1.value, Num2).ToString, L)
- If Precision = -1 Then Precision = R.ILen + R.MLen
- Return Div(One, R, Precision)
- Else
- Dim L As Integer = Num1.MLen * Num2
- Dim R As BigInteger = BigInteger.Pow(Num1.value, Num2)
- Return PowN10(R.ToString, L)
- End If
- End Function
- 'Does Modular arithmetic on two BigDecimal numbers
- Public Shared Operator Mod(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As BigDecimal
- If Num2.ILen > Num1.ILen Then Return Num1
- If Num2.value.IsZero Then Throw New DivideByZeroException
- Dim L As Integer = Num1.MLen
- Dim D As Integer = Math.Abs(Num1.MLen - Num2.MLen)
- If Num1.MLen >= Num2.MLen Then
- Num2.value = Pow10(Num2, D)
- Else
- L = Num2.MLen
- Num1.value = Pow10(Num1, D)
- End If
- Return PowN10(New BigDecimal(BigInteger.ModPow(Num1.value, 1, Num2.value)), L)
- 'Return PowN10(BigInteger.ModPow(Num1.value, 1, Num2.value).ToString, L)
- End Operator
- 'Does equal comparison on two BigDecimal numbers
- Public Shared Operator =(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As Boolean
- If Num1.value.Sign = Num2.value.Sign _
- AndAlso Num1.ILen = Num2.ILen _
- AndAlso Num1.MLen = Num2.MLen _
- AndAlso Num1.value = Num2.value _
- Then Return True _
- Else Return False
- End Operator
- 'Does greater than less than comparison on two BigDecimal numbers
- Public Shared Operator <>(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As Boolean
- If Num1.value.Sign = Num2.value.Sign _
- AndAlso Num1.ILen = Num2.ILen _
- AndAlso Num1.MLen = Num2.MLen _
- AndAlso Num1.value = Num2.value _
- Then Return False _
- Else Return True
- End Operator
- 'Does greater than comparison on two BigDecimal numbers
- Public Shared Operator >(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As Boolean
- If Num1.Sign > Num2.Sign OrElse Num1.ILen > Num1.ILen Then Return True
- Dim L As Integer = Num1.MLen
- Dim D As Integer = 0
- If Num1.MLen > Num2.MLen Then
- D = Num1.MLen - Num2.MLen
- Num2.value = Pow10(Num2, D)
- Else
- L = Num2.MLen
- D = Num2.MLen - Num1.MLen
- Num1.value = Pow10(Num1, D)
- End If
- If BigInteger.Subtract(Num1.value, Num2.value) > 0 Then Return True
- Return False
- End Operator
- 'Does less than comparison on two BigDecimal numbers
- Public Shared Operator <(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As Boolean
- If Num1.Sign > Num2.Sign OrElse Num1.ILen > Num1.ILen Then Return True
- If Num1.MLen = Num2.MLen Then
- Return Num1.value < Num2.value
- Else
- Dim L As Integer = Num1.MLen
- Dim D As Integer = 0
- If Num1.MLen > Num2.MLen Then
- D = Num1.MLen - Num2.MLen
- Num2.value = Pow10(Num2, D)
- Else
- L = Num2.MLen
- D = Num2.MLen - Num1.MLen
- Num1.value = Pow10(Num1, D)
- End If
- If BigInteger.Subtract(Num1.value, Num2.value) < 0 Then Return True
- End If
- Return False
- End Operator
- 'Does greater than or equal to comparison on two BigDecimal numbers
- Public Shared Operator >=(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As Boolean
- If Num1.Sign > Num2.Sign OrElse Num1.ILen > Num1.ILen Then Return True
- If Num1.MLen = Num2.MLen Then
- Return Num1.value >= Num2.value
- Else
- Dim L As Integer = Num1.MLen
- Dim D As Integer = 0
- If Num1.MLen > Num2.MLen Then
- D = Num1.MLen - Num2.MLen
- Num2.value = Pow10(Num2, D)
- Else
- L = Num2.MLen
- D = Num2.MLen - Num1.MLen
- Num1.value = Pow10(Num1, D)
- End If
- If BigInteger.Subtract(Num1.value, Num2.value) >= 0 Then Return True
- End If
- Return False
- End Operator
- 'Does less than or equal to comparison on two BigDecimal numbers
- Public Shared Operator <=(ByVal Num1 As BigDecimal, ByVal Num2 As BigDecimal) As Boolean
- If Num1.Sign > Num2.Sign OrElse Num1.ILen > Num1.ILen Then Return True
- If Num1.MLen = Num2.MLen Then
- Return Num1.value <= Num2.value
- Else
- Dim L As Integer = Num1.MLen
- Dim D As Integer = 0
- If Num1.MLen > Num2.MLen Then
- D = Num1.MLen - Num2.MLen
- Num2.value = Pow10(Num2, D)
- Else
- L = Num2.MLen
- D = Num2.MLen - Num1.MLen
- Num1.value = Pow10(Num1, D)
- End If
- If BigInteger.Subtract(Num1.value, Num2.value) <= 0 Then Return True
- End If
- Return False
- End Operator
- #End Region
- #Region "Misc"
- 'Multiplies a BigDecimal number by 10 raised to a specified exponent
- Private Shared Function Pow10(ByVal value As BigDecimal, ByVal exponent As Integer) As BigInteger
- If value.value = 0 OrElse exponent = 0 Then Return value.value
- Return BigInteger.Multiply(value.value, BigInteger.Pow(10, exponent))
- End Function
- 'Moves around the decimal value to mimic a BigDecimal multiplied by 10 to a negative power
- Private Shared Function PowN10(ByVal value As String, ByVal exponent As Integer) As BigDecimal
- If value.StartsWith("-") Then
- value = value.Remove(0, 1).PadLeft(exponent, "0"c)
- Return New BigDecimal("-" & value.Insert(value.Length - exponent, "."))
- End If
- value = value.PadLeft(exponent, "0"c)
- Return New BigDecimal(value.Insert(value.Length - exponent, "."))
- End Function
- 'Raises a bigdecimal to a negative integer power
- Private Shared Function PowN10(ByVal Num As BigDecimal, ByVal exponent As Integer) As BigDecimal
- If exponent = 0 Then Return One
- Dim EXP As BigDecimal = ZeroPointOne
- For i = 1 To Math.Abs(exponent) : EXP *= ZeroPointOne : Next
- Return Num * EXP
- End Function
- 'Finds the square root of a bigdecimal number given the precision, no decimals if not specified
- Public Shared Function Sqrt(ByVal Num As BigDecimal,
- Optional ByVal Precision As Integer = 0) As BigDecimal
- Dim ourGuess As BigDecimal = Div(Num, Two, Precision)
- While True
- Dim result As BigDecimal = Div(Num, ourGuess, Precision)
- Dim average As BigDecimal = Div((ourGuess + result), Two, Precision)
- If average = ourGuess Then Return average Else ourGuess = average
- End While
- Return Zero
- End Function
- 'Returns PI to the specified decimal place
- Public Shared Function Pi(ByVal Precision As Integer) As BigDecimal
- Precision += 4 'Increase precision internally to account for rounding
- Dim answer As BigDecimal = Zero
- Dim term5 As BigDecimal = ThreePointTwo
- Dim term239 As BigDecimal = Div(Four, TwoHundredThirtyNine, Precision)
- Dim term5m As BigDecimal
- Dim term239m As BigDecimal
- Dim n5 As Integer = 0
- Dim n239 As Integer = 0
- While True
- term5m = Div(term5, New BigDecimal(n5 * 2 + 1), Precision)
- If n5 Mod 2 = 0 Then answer += term5m Else answer -= term5m
- term5 = Div(term5, TwentyFive, Precision)
- n5 += 1
- If term5m.IsZero Then Exit While
- End While
- While True
- term239m = Div(term239, New BigDecimal(n239 * 2 + 1), Precision)
- If n239 Mod 2 = 0 Then answer -= term239m Else answer += term239m
- term239 = Div(term239, FiftySevenThousandOneHundredTwentyOne, Precision)
- n239 += 1
- If term239m.IsZero Then Exit While
- End While
- Return Div(answer, One, Precision - 4)
- End Function
- 'Returns Phi to the specified decimal place
- Public Shared Function Phi(ByVal Precision As Integer) As BigDecimal
- Return (One + Sqrt(Five, Precision)) * ZeroPointFive
- End Function
- 'Returns Tau to the specified decimal place
- Public Shared Function Tau(ByVal Precision As Integer) As BigDecimal
- Return Div(Pi(Precision + 1) * Two, One, Precision)
- End Function
- 'Returns Pythagoras' constant to the specified decimal place
- Public Shared Function PythagorasConst(ByVal Precision As Integer) As BigDecimal
- Return Sqrt(Two, Precision)
- End Function
- #End Region
- End Structure
Advertisement
Add Comment
Please, Sign In to add comment