RGF писал(а):гуру, приведите пожалуйста если не алгоритм, то хотя бы кусок кода, в котором происходит кодирование из широты/долготы в lonlatarr
Так получилось, что по работе потребовалось формировать sml-файлы из большой базы с координатами. Но, в силу специфики предприятия, средств для разработки серьезного ПО нет, а если и удастся разработать ПО на стороне, то по работе использовать его не будет возможности из-за политик безопасности. Для реализации задачи использовал "костыль" - VBA, встроенный в MS Excel.
Т.к. в самом VBA не имеется встроенного типа данных с плавающей точкой, аналога Extended, пришлось изобретать его... но прежде, пришлось постигнуть теорию представления чисел с плавающей точкой в машинном коде, которую напрочь забыл с институтской скамьи.
Результатом стал алгоритм, который описывает подробное преобразование из переменной типа Double, в код lonlatarr, который может стать интересным, осваивающим этот алгоритм с нуля. Естественно, никаких претензий на оптимальность алгоритма нет, над этим буду работать в перспективе, поэтому прошу меня не критиковать за
быдлокод медленную работу алгоритма.
Итак, алгоритм состоит из нескольких этапов:
1. Разложение числа с плавающей точкой в массив 80-байт, каждый байт которого является битом в бинарном представлении числа с плавающей точкой формата Extended;
2. Объединение двух битовых массивов конвертированных координат в один 192-байтовый массив;
3. Преобразование 192-байтового массива в строку lonlatarr
Собственно код:
- Код: Выделить всё
Option Explicit
Private Sub DblToByteArray(ByVal Dig As Double, ByRef Data() As Byte)
'=========================================================
' Процедура преобразования Double-числа в битовый массив
'=========================================================
Const CNT = 80
Dim tDATA(1 To CNT) As Byte
Dim D As Double
Dim i As Long
Dim j As Long
Dim A As Long
Dim Bt As Byte
Dim B As Double
Dim S As String
Dim Exp As Long
'===== Проверка знака числа =====
If Dig < 0 Then
Data(1) = 1
D = Abs(Dig)
Else
D = Dig
End If
'===== Проверка размерности числа =====
If D > 1 Then
'== Представление числа |X| > 1 ==
'== Разложение на биты целой части числа ==
A = Fix(D)
i = 1
Do While A > 1
tDATA(i) = A Mod 2
A = A \ 2
i = i + 1
Loop
tDATA(i) = 1
For j = 1 To i
Data(17 + i - j) = tDATA(j)
Next j
'== Вычисление значения экспоненты и разложение его на биты ==
Exp = 16384 + i - 2
i = 17 + i
j = 16
Do While Exp > 1 And j > 1
Data(j) = Exp Mod 2
Exp = Exp \ 2
j = j - 1
Loop
Data(j) = 1
'== Разложение на биты дробной части числа ==
A = Round((D - Fix(D)) * 1000000, 0)
Do While i <= 80
A = A * 2
If A > 1000000 Then
Data(i) = 1
A = Fix(A - 1000000)
Else
Data(i) = 0
End If
i = i + 1
Loop
ElseIf Dig <= 0.000001 Then
'== Представление числа |X|=0 (условно |X| <= 0.000001 )==
For i = 1 To 80
Data(i) = 0
Next i
Else
'== Представление числа 0<|X|<1 ==
'== Вычисление значения экспоненты и разложение его на биты ==
'== Вычисление смещения ==
A = Round((D - Fix(D)) * 1000000, 0)
j = 1
Do While A < 1000000
A = A * 2
j = j + 1
Loop
Data(17) = 1
A = Fix(A - 1000000)
'=========================
Exp = 16384 - (j + 1)
j = 16
Do While Exp > 1 And j > 1
Data(j) = Exp Mod 2
Exp = Exp \ 2
j = j - 1
Loop
Data(j) = 1
'== Разложение на биты дробной части числа ==
i = 18
Do While i <= 80
A = A * 2
If A > 1000000 Then
Data(i) = 1
A = Fix(A - 1000000)
Else
Data(i) = 0
End If
i = i + 1
Loop
End If
'== инверсия байтов ==
For i = 0 To 4
For j = 1 To 8
tDATA(j) = Data(i * 8 + j)
Next j
For j = 1 To 8
Data(i * 8 + j) = Data(79 - (i + 1) * 8 + j)
Next j
For j = 1 To 8
Data(80 - (i + 1) * 8 + j) = tDATA(j)
Next j
Next i
End Sub
Private Function ByteArrToBase64(ByRef XCoord() As Byte, ByRef YCoord() As Byte) As String
'============================================================
' Формирование строки lonlatarr из битовых массивов
'============================================================
Dim BinDATA(1 To 192) As Byte
Dim tB64()
Dim i As Long
Dim j As Long
Dim B64 As String
Dim A As Byte
tB64 = Array("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", _
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", _
"j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", _
"2", "3", "4", "5", "6", "7", "8", "9", "+", "/")
i = 1
'==== Составление единого массива 192 бит ====
For j = 1 To 80
BinDATA(i) = XCoord(j)
i = i + 1
Next j
For j = 1 To 80
BinDATA(i) = YCoord(j)
i = i + 1
Next j
'=============================================
'==== формирование строки lonlatarr ====
For i = 1 To 192 Step 6
A = 32 * BinDATA(i) + 16 * BinDATA(i + 1) + 8 * BinDATA(i + 2) + 4 * BinDATA(i + 3) + 2 * BinDATA(i + 4) + 1 * BinDATA(i + 5)
B64 = B64 & tB64(A)
Next i
'=======================================
ByteArrToBase64 = B64
End Function
Private Function CoordToBase64(ByVal XCoord As Double, ByVal YCoord As Double) As String
'============================================================
' Формирование кода lonlatarr из двух переменных типа Double
'============================================================
Dim X(1 To 80) As Byte
Dim Y(1 To 80) As Byte
DblToByteArray XCoord, X()
DblToByteArray YCoord, Y()
CoordToBase64 = ByteArrToBase64(X(), Y())
End Function
Private Sub CommandButton2_Click()
With Worksheets(1)
.Cells(2, 3).NumberFormat = "@"
.Cells(2, 3) = CoordToBase64(.Cells(2, 1), .Cells(2, 2))
End With
End Sub
В ходе экспериментов с разложением числа с плавающей точкой пришел к выводу, что озвученный в теории метода умножения дробной части числа на 2 до появления "1" в целой части дал огромную погрешность начиная с 40-50 бита в бинарном представлении, что приводило к отклонению от реальных координат почти на 50 метров, в результате реализовал целочисленный механизм преобразования дробной части, который дал точный результат (сравнительный пример см. во вложении, в расчетах формулами, где так же можно наглядно посмотреть разложение формулами кода lonlatarr в битовый массив и рабочий макрос формирования поля lonlatarr).
Единственное, что для меня осталось загадкой по итогам реализации алгоритма, так это то, почему для формирования lonlatarr используется 24 байта данных (4 из которых - мусор), а не 21 (1 байт - мусор), ведь 21 байт дают устойчивое представление в виде BASE64 кода, с меньшим количеством символов.
P.S. В ходе изучения теории преобразования чисел с плавающей точкой, пришел к выводу, что преобразовать из других типов в аналог Extended будет весьма затруднительным в силу того, что у Extended "1" до точки явная, в то время как у других типов она не явная и в битовое представление записывается лишь код дробной части нормализованного числа в бинарном представлении, плюс, характеристика у Extended составляет 15 бит, что значительно больше других типов. Озвученное не дает возможности использовать простое копирование байтов из памяти, например, средствами CopyMemory (что по-началу было опробовано) и добавлением в "хвост" нулевых байтов.