Converting bytes to Extended Precision Floating Point not accurate enough - vb.net
I have some legacy files that need mined for data. The files were created by Lotus123 Release 4 for DOS. I'm trying to read the files faster by parsing the bytes rather than using Lotus to open the files. I have value records of 10 bytes each. They are 80 bit Extended Precision Floating Point.
Debug.Print(ConvertLongDouble80(New Byte() {0, 0, 0, 0, 0, 0, 0, 128, 255, 191})) ' Value = -1
Debug.Print(ConvertLongDouble80(New Byte() {205, 204, 204, 204, 204, 204, 204, 204, 251, 191})) ' Value = -0.1
Debug.Print(ConvertLongDouble80(New Byte() {10, 215, 163, 112, 61, 10, 215, 163, 248, 191})) ' Value = -0.01
Debug.Print(ConvertLongDouble80(New Byte() {59, 223, 79, 141, 151, 110, 18, 131, 245, 191})) ' Value = -0.001
Debug.Print(ConvertLongDouble80(New Byte() {44, 101, 25, 226, 88, 23, 183, 209, 241, 191})) ' Value = -0.0001
Debug.Print(ConvertLongDouble80(New Byte() {35, 132, 71, 27, 71, 172, 197, 167, 238, 191})) ' Value = -0.00001
Debug.Print(ConvertLongDouble80(New Byte() {182, 105, 108, 175, 5, 189, 55, 134, 235, 191})) ' Value = -0.000001
Debug.Print(ConvertLongDouble80(New Byte() {0, 0, 0, 0, 0, 0, 0, 128, 255, 63})) ' Value = 1
Debug.Print(ConvertLongDouble80(New Byte() {205, 204, 204, 204, 204, 204, 204, 204, 251, 63})) ' Value = 0.1
Debug.Print(ConvertLongDouble80(New Byte() {10, 215, 163, 112, 61, 10, 215, 163, 248, 63})) ' Value = 0.01
Debug.Print(ConvertLongDouble80(New Byte() {59, 223, 79, 141, 151, 110, 18, 131, 245, 63})) ' Value = 0.001
Debug.Print(ConvertLongDouble80(New Byte() {44, 101, 25, 226, 88, 23, 183, 209, 241, 63})) ' Value = 0.0001
Debug.Print(ConvertLongDouble80(New Byte() {35, 132, 71, 27, 71, 172, 197, 167, 238, 63})) ' Value = 0.00001
Debug.Print(ConvertLongDouble80(New Byte() {182, 105, 108, 175, 5, 189, 55, 134, 235, 63})) ' Value = 0.000001
Debug.Print(ConvertLongDouble80(New Byte() {188, 66, 122, 229, 213, 148, 191, 214, 231, 63})) ' Value = 0.0000001
Function ConvertLongDouble80(ByVal TenBytes As Byte()) As Double
'https://en.wikipedia.org/wiki/Extended_precision
'get 15 bit exponent; remove the first bit which is the negative sign
Dim arrExp As Byte() = New Byte() {TenBytes(8), (TenBytes(9) << 1) >> 1}
Dim Expo As UInt16 = BitConverter.ToUInt16(arrExp, 0)
'flag bits
Dim Bit63 As UInt16 = Convert.ToUInt16(TenBytes(7) >> 7)
Dim Bits63_62 As UInt16 = Convert.ToUInt16(TenBytes(7) >> 6)
'fractional values
TenBytes(7) = (TenBytes(7) << 1) >> 1
Dim Bits62_0 As UInt64 = BitConverter.ToUInt64(TenBytes, 0)
TenBytes(7) = (TenBytes(7) << 2) >> 2
Dim Bits61_0 As UInt64 = BitConverter.ToUInt64(TenBytes, 0)
If Bit63 = 0 and Bits62_0 = 0 Then
Return 0
Else
Const ExponentBias As Integer = 16383
Dim isNegative As Boolean = (TenBytes(9) And (1 << 7)) <> 0
Dim NegVal As Int16 = IIf(isNegative, -1, 1)
Dim expVal As Double = Math.Pow(2, Expo - ExponentBias)
Dim LBits62 As Int16 = Len(CStr(Bits62_0))
Dim mantissa As Double = CDbl("1." & CStr(Bits62_0))
Dim result As Double = NegVal * expVal * mantissa
Return result
End If
End Function
Value -1 converts to -1
Value -0.1 converts to -0.09708764513821
Value -0.01 converts to -0.00983011263306
Value -0.001 converts to -0.00119273528211
Value -0.0001 converts to -0.00009697388128
Value -0.00001 converts to -0.00000981589215
Value -0.000001 converts to -0.00000138095333
Value 1 converts to 1
Value 0.1 converts to 0.09708764513821
Value 0.01 converts to 0.00983011263306
Value 0.001 converts to 0.00119273528211
Value 0.0001 converts to 0.00009697388128
Value 0.00001 converts to 0.00000981589215
Value 0.000001 converts to 0.00000138095333
Value 0.0000001 converts to 0.00000009686278
What am I doing wrong. My values are not close enough. How do I fix this?
The problem is that you treat the fractional part, which represents the fraction of a binary number as a whole number. I changed your function by interpreting the bits 0 to 63 as an integer number which is 263 too big. It's too big by 263 because bit 63 should be bit 0 (instead of 10110101..., we should have 1.0110101..., i.e., the leading 1 has to move 63 positions to the right). I then subtract 63 the the exponent to take this into account. I didn't check the logic related to the flags handling.
Function ConvertLongDouble80(ByVal TenBytes As Byte()) As Double
'https://en.wikipedia.org/wiki/Extended_precision
' 80-bit extended precision format
' --------------------------------
' bit 0 to 63 fraction, bytes 0 to 7
' bit 64 to 78 exponent (bias 16383, bytes 8 to 9)
' bit 79 sign /
'get 15 bit exponent; remove the first bit which is the negative sign
Dim arrExp As Byte() = New Byte() {TenBytes(8), (TenBytes(9) << 1) >> 1}
Dim Expo As UInt16 = BitConverter.ToUInt16(arrExp, 0)
'flag bits
Dim Bit63 As UInt16 = Convert.ToUInt16(TenBytes(7) >> 7)
Dim Bits63_62 As UInt16 = Convert.ToUInt16(TenBytes(7) >> 6)
Dim fraction = BitConverter.ToUInt64(TenBytes, 0) ' 2 ^ 63 too big
If Bit63 = 0 And fraction = 0 Then
Return 0
Else
Const ExponentBias As Integer = 16383
Dim isNegative As Boolean = (TenBytes(9) And (1 << 7)) <> 0
Dim sign = If(isNegative, -1, 1)
Dim expVal As Double = Math.Pow(2, Expo - ExponentBias - 63)
Dim result As Double = sign * expVal * fraction
Return result
End If
End Function
Related
Receiving a float as 4 individual bytes then converting it back
I am receiving via UDP, 4 byte packets that represent what was a float (from C# code). I now need to turn these bytes back into the single so it's the same number as it was before I've done a bit of trial and error but I'm not even close to turning it into a single accurately. All I do know is that the number is between 0 and 1, and is in little-endian format. A few more things that may help When the float is 1 Byte(0) = 0, Byte(1) = 0, Byte(2) = 128, Byte(3) = 63 When the float is approximately 0.5 Byte(0) = 234, Byte(1) = 111, Byte(2) = 247, Byte(3) = 62 When the float is approximately 0.75 Byte(0) = 178, Byte(1) = 56, Byte(2) = 64, Byte(3) = 63
Trying to create an multidimensional array with 100 rows and 3 columns
VB beginner here. The array i need to create is 3 columns, 100 rows. The first column will be an integer that increases by two for every row. The second column will be a date that increases by 15 days. The third column is similarly like the second column but begins from another day. I have some codes below I tried but is still lost at what that will do. Very grateful if you can help me out on this. Private Sub AutopayPayPeriod() Dim row As Int32 Dim AutopayArray(0 To 10, 0 To 2) Dim RCN As Int32 = row Dim PayPeriodStart, PayPeriodEnd As Date Dim index As Int32 RCN = 1 PayPeriodStart = Format(#12/12/2015#, "Short Date") PayPeriodEnd = Format(#12/25/2015#, "Short Date") For index = 1 To AutopayArray.Length - 1 AutopayArray(0, 2) = {RCN, PayPeriodStart, PayPeriodEnd} PayPeriodStart = PayPeriodStart.AddDays(15) PayPeriodEnd = PayPeriodEnd.AddDays(15) RCN += 2 index += 1 Array.Resize(ByRef AutopayArray, (AutopayArray.Length+=1)) Next End Sub
A multidimensional array is the wrong data structure for this because you have different data types. Also, having a plain array in your code obscures the intent of the code since nobody can guess what the entries mean without proper documentation. What you should use is a properly typed Class or Structure, depending on how you are using it. In the following, I show the variant with a Class: Class PeriodInformation Public Property RCN As Integer Public Property StartDate As Date Public Property EndDate As Date End Class The second thing you might want to change is to use a List(Of PeriodInformation) instead of an array because adding and removing items is much simpler. If you stick to the array, resize it once at the beginning instead of every time. And finally, don't use string representations of dates. Use the actual dates. The code then look as follows: Private Function AddAutoPayPeriods(firstPayPeriodStart As Date, firstPayPeriodEnd As Date, firstPayPeriodRCN As Integer, numberOfPeriods As Integer) As List(Of PeriodInformation) Dim result As New List(Of PeriodInformation) For i As Integer = 1 To numberOfPeriods result.Add(New PeriodInformation With {.RCN = firstPayPeriodRCN, .StartDate = firstPayPeriodStart, .EndDate = firstPayPeriodEnd}) firstPayPeriodStart = firstPayPeriodStart.AddDays(15) firstPayPeriodEnd = firstPayPeriodEnd.AddDays(15) firstPayPeriodRCN += 2 Next Return result End Function We could then call this function like: Dim periods = AddAutoPayPeriods(#12/12/2015#, #12/25/2015#, 1, 10) And finally check what we've got by simply printing all elements: For Each period In periods Console.WriteLine($"{period.RCN}: {period.StartDate:d} - {period.EndDate:d}") Next Which prints 1: 12-Dec-15 - 25-Dec-15 3: 27-Dec-15 - 09-Jan-16 5: 11-Jan-16 - 24-Jan-16 7: 26-Jan-16 - 08-Feb-16 9: 10-Feb-16 - 23-Feb-16 11: 25-Feb-16 - 09-Mar-16 13: 11-Mar-16 - 24-Mar-16 15: 26-Mar-16 - 08-Apr-16 17: 10-Apr-16 - 23-Apr-16 19: 25-Apr-16 - 08-May-16
Another helpful class in .net is the DataTable. When dealing with rows and columns it may be a good fit. Private Function CreatePayPeriods(RCN As Integer, PayPeriodStart As Date, PayPeriodEnd As Date, NumberOfPeriods As Integer) As DataTable Dim dt As New DataTable() dt.Columns.Add("RCN", GetType(Integer)) dt.Columns.Add("Pay Period Start Date", GetType(Date)) dt.Columns.Add("Pay Period End Date", GetType(Date)) For i = 1 To NumberOfPeriods dt.Rows.Add(RCN, PayPeriodStart, PayPeriodEnd) PayPeriodStart = PayPeriodStart.AddDays(15) PayPeriodEnd = PayPeriodEnd.AddDays(15) RCN += 2 Next Return dt End Function To see the results Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click DataGridView1.DataSource = CreatePayPeriods(1, #1/02/2017#, #1/9/2017#, 100) End Sub
Please try with this method: Private Sub AutopayPayPeriod() Dim myArray(2) 'as colomns Dim myRows As New ArrayList 'as rows Dim PayPeriodStart = Format(#12/12/2015#, "Short Date") Dim PayPeriodEnd = Format(#12/25/2015#, "Short Date") For myIdx = 1 To 100 myArray = {(myIdx - 1) * 2 + 1, PayPeriodStart, PayPeriodEnd} PayPeriodStart = Format(CDate(PayPeriodStart).AddDays(15), "Short Date") PayPeriodEnd = Format(CDate(PayPeriodEnd).AddDays(15), "Short Date") myRows.Add(myArray) Next For myIdx = 1 To 100 Debug.Print(myRows.Item(myIdx - 1)(0) & ", " & myRows.Item(myIdx - 1)(1) & ", " & myRows.Item(myIdx - 1)(2)) Next End Sub The Result are as follows: 1, 12/12/2015, 12/25/2015 3, 12/27/2015, 1/9/2016 5, 1/11/2016, 1/24/2016 7, 1/26/2016, 2/8/2016 9, 2/10/2016, 2/23/2016 11, 2/25/2016, 3/9/2016 13, 3/11/2016, 3/24/2016 15, 3/26/2016, 4/8/2016 17, 4/10/2016, 4/23/2016 19, 4/25/2016, 5/8/2016 21, 5/10/2016, 5/23/2016 23, 5/25/2016, 6/7/2016 25, 6/9/2016, 6/22/2016 27, 6/24/2016, 7/7/2016 29, 7/9/2016, 7/22/2016 31, 7/24/2016, 8/6/2016 33, 8/8/2016, 8/21/2016 35, 8/23/2016, 9/5/2016 37, 9/7/2016, 9/20/2016 39, 9/22/2016, 10/5/2016 41, 10/7/2016, 10/20/2016 43, 10/22/2016, 11/4/2016 45, 11/6/2016, 11/19/2016 47, 11/21/2016, 12/4/2016 49, 12/6/2016, 12/19/2016 51, 12/21/2016, 1/3/2017 53, 1/5/2017, 1/18/2017 55, 1/20/2017, 2/2/2017 57, 2/4/2017, 2/17/2017 59, 2/19/2017, 3/4/2017 61, 3/6/2017, 3/19/2017 63, 3/21/2017, 4/3/2017 65, 4/5/2017, 4/18/2017 67, 4/20/2017, 5/3/2017 69, 5/5/2017, 5/18/2017 71, 5/20/2017, 6/2/2017 73, 6/4/2017, 6/17/2017 75, 6/19/2017, 7/2/2017 77, 7/4/2017, 7/17/2017 79, 7/19/2017, 8/1/2017 81, 8/3/2017, 8/16/2017 83, 8/18/2017, 8/31/2017 85, 9/2/2017, 9/15/2017 87, 9/17/2017, 9/30/2017 89, 10/2/2017, 10/15/2017 91, 10/17/2017, 10/30/2017 93, 11/1/2017, 11/14/2017 95, 11/16/2017, 11/29/2017 97, 12/1/2017, 12/14/2017 99, 12/16/2017, 12/29/2017 101, 12/31/2017, 1/13/2018 103, 1/15/2018, 1/28/2018 105, 1/30/2018, 2/12/2018 107, 2/14/2018, 2/27/2018 109, 3/1/2018, 3/14/2018 111, 3/16/2018, 3/29/2018 113, 3/31/2018, 4/13/2018 115, 4/15/2018, 4/28/2018 117, 4/30/2018, 5/13/2018 119, 5/15/2018, 5/28/2018 121, 5/30/2018, 6/12/2018 123, 6/14/2018, 6/27/2018 125, 6/29/2018, 7/12/2018 127, 7/14/2018, 7/27/2018 129, 7/29/2018, 8/11/2018 131, 8/13/2018, 8/26/2018 133, 8/28/2018, 9/10/2018 135, 9/12/2018, 9/25/2018 137, 9/27/2018, 10/10/2018 139, 10/12/2018, 10/25/2018 141, 10/27/2018, 11/9/2018 143, 11/11/2018, 11/24/2018 145, 11/26/2018, 12/9/2018 147, 12/11/2018, 12/24/2018 149, 12/26/2018, 1/8/2019 151, 1/10/2019, 1/23/2019 153, 1/25/2019, 2/7/2019 155, 2/9/2019, 2/22/2019 157, 2/24/2019, 3/9/2019 159, 3/11/2019, 3/24/2019 161, 3/26/2019, 4/8/2019 163, 4/10/2019, 4/23/2019 165, 4/25/2019, 5/8/2019 167, 5/10/2019, 5/23/2019 169, 5/25/2019, 6/7/2019 171, 6/9/2019, 6/22/2019 173, 6/24/2019, 7/7/2019 175, 7/9/2019, 7/22/2019 177, 7/24/2019, 8/6/2019 179, 8/8/2019, 8/21/2019 181, 8/23/2019, 9/5/2019 183, 9/7/2019, 9/20/2019 185, 9/22/2019, 10/5/2019 187, 10/7/2019, 10/20/2019 189, 10/22/2019, 11/4/2019 191, 11/6/2019, 11/19/2019 193, 11/21/2019, 12/4/2019 195, 12/6/2019, 12/19/2019 197, 12/21/2019, 1/3/2020 199, 1/5/2020, 1/18/2020
Another ways are: Private Sub AutopayPayPeriod() Dim myArray(2) 'as colomns Dim myRows As New Collection'as rows Dim PayPeriodStart = Format(#12/12/2015#, "Short Date") Dim PayPeriodEnd = Format(#12/25/2015#, "Short Date") For myIdx = 1 To 100 myArray = {(myIdx - 1) * 2 + 1, PayPeriodStart, PayPeriodEnd} PayPeriodStart = Format(CDate(PayPeriodStart).AddDays(15), "Short Date") PayPeriodEnd = Format(CDate(PayPeriodEnd).AddDays(15), "Short Date") myRows.Add(myArray) Next For myIdx = 1 To 100 Debug.Print(myRows.Item(myIdx)(0) & ", " & myRows.Item(myIdx)(1) & ", " & myRows.Item(myIdx)(2)) Next End Sub But the best way it to use table as Mary said
Cannot convert system.object[] to system.byte[], solutions and workarounds
Trying to create a function to simplify ratios (stored as arrays of length 2, with the index 0 being the numerator and index 1 being the denominator). This function gives me an error when the values passed in are object attributes. Here is the function: Function SimplifyRatio(Numerator, Denominator) Dim i As Integer Dim PNA() As Integer i = 0 PNA = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199} While i < PNA.Length() If Numerator Mod PNA(i) = 0 And Denominator Mod PNA(i) = 0 Then Numerator = Numerator / PNA(i) Denominator = Denominator / PNA(i) i = 0 End If i = i + 1 End While Return {Numerator, Denominator} End Function And here is the method containing the erroneous function call: Public Function CalculateRatio() Dim ratio(1) As Byte ratio = SimplifyRatio(Gear.Teeth, Pinion.Teeth) Return ratio End Function Any help would be appreciated, Yours Sincerely, KyuSiik
Rank numbers in a loop vb.net
I currently pull data from a database and rank them when i loop through them. Example of such numbers are 45, 45, 67, 99, 34, 65, 88, 22, 90, 90, 90, 23, 55, 46. These are a total of 14 numbers, I want to loop through and assign rank. Dim i As Integer() = {45, 45, 67, 99, 34, 65, 88, 22, 90, 90, 90, 23, 55, 46} Dim lastScore As Integer Dim position As Integer = 0 For Each i1 In i If Val(lastScore) <> Val(i1) Then position += 1 Console.WriteLine(position & vbCrLf) ElseIf Val(lastScore) = Val(i1) Then Console.WriteLine(position & vbCrLf) position += 1 End If lastScore = Val(i1) Next The current output of the code above is: 1, 1, 3, 4, 5, 6, 7, 8, 9, 9, 10, 12, 13, 14 Which is wrong. The expected output is supposed to be: 1, 1, 3, 4, 5, 6, 7, 8, 9, 9, 9, 12, 13, 14 How can I achieve this?
Here is an ugly code which creates the expected output: Dim i As Integer() = {45, 45, 67, 99, 34, 65, 88, 22, 90, 90, 90, 23, 55, 46} Dim lastScore As Integer Dim lastScorePosition As Integer Dim position As Integer = 1 For Each i1 In i If Val(lastScore) <> Val(i1) Then Console.Write(position & ",") lastScorePosition = position lastScore = Val(i1) Else Console.Write(lastScorePosition & ",") End If position += 1 Next
The expected result is not correct. I.e. why there is not rank 2? Simple ranking is achieved with relatively simple code: Sub Main() Dim i As Integer() = {45, 45, 67, 99, 34, 65, 88, 22, 90, 90, 90, 23, 55, 46} Dim lastScore As Integer Dim position As Integer Dim sb As New StringBuilder For Each i1 In i If Not lastScore = i1 Then position += 1 sb.Append(position & ", ") lastScore = i1 Next sb.Remove(sb.Length - 2, 2) Console.WriteLine(sb.ToString) Console.ReadLine() End Sub The output is: 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 9, 10, 11
String to double array
I need to take this string: Dim tmpTry As String = "10, 20, 30, 40, 50, 52, 20, 20, 10, 35, 3, 8, 47, 7, 2, 5, 55, 8, 0, 0, 6, 55, 0, 2, 12, 0, 0, 21, 14, 0, 3" And convert it to a double array: Dim arrNumOfVisits As Double() = New Double(tmpTry) {} How do i go about doing that? FYI the arrNumOfVisits goes into a ParamArray System.Collections.IEnumerable() David
Dim arrString As String() = tmpTry.Split(New Char() {" "C}) Dim arrNumOfVisits As Double() = New Double(arrString.Length) {} Dim i As Integer = 0 While i < arrString.Length arrNumOfVisits(i) = Double.Parse(arrString(i)) i += 1 End While The above code will do the trick, using regEx on this would be overkill. Never the less do try to learn the basic RegEx operations, here are my favorite cheat sheets: http://regexlib.com/CheatSheet.aspx?AspxAutoDetectCookieSupport=1 http://www.cheatography.com/davechild/cheat-sheets/regular-expressions/