Как преобразовать два байта в число с плавающей запятой - PullRequest
1 голос
/ 10 марта 2019

У меня есть несколько устаревших файлов, которые нужно добывать для данных.Файлы были созданы Lotus123 Release 4 для DOS.Я пытаюсь читать файлы быстрее, анализируя байты, а не используя Lotus для открытия файлов.

Dim fileBytes() As Byte = My.Computer.FileSystem.ReadAllBytes(fiPath)

'I loop through all the data getting first/second bytes for each value
do ...
    Dim FirstByte As Int16 = Convert.ToInt16(fileBytes(Index))
    Dim SecondByte As Int16 = Convert.ToInt16(fileBytes(Index + 1))
loop ...

Я могу получить целочисленные значения, например:

Dim value As Int16 = BitConverter.ToInt16(fileBytes, Index + 8) / 2

Но с плавающей точкойцифры сложнее.Только меньшие числа хранятся с двумя байтами.Большие значения занимают 10 байтов, но это другой вопрос.Здесь у нас только меньшие значения с двумя байтами.Вот несколько примеров значений.Я ввел значения байтов в Excel и использую = DEC2BIN (), чтобы преобразовать в двоичные числа, добавляя нули слева по мере необходимости, чтобы получить 8 бит.

First   Second
Byte    Byte        Value   First   Byte    2nd     Byte

7       241     =   -1.2    0000    0111    1111    0001
254     255     =   -1      1111    1110    1111    1111
9       156     =   -0.8    0000    1001    1001    1100
9       181     =   -0.6    0000    1001    1011    0101
9       206     =   -0.4    0000    1001    1100    1110
9       231     =   -0.2    0000    1001    1110    0111
13      0       =   0       0000    1101    0000    0000
137     12      =   0.1     1000    1001    0000    1100
9       25      =   0.2     0000    1001    0001    1001
137     37      =   0.3     1000    1001    0010    0101
9       50      =   0.4     0000    1001    0011    0010
15      2       =   0.5     0000    1111    0000    0010
9       75      =   0.6     0000    1001    0100    1011
137     87      =   0.7     1000    1001    0101    0111
9       100     =   0.8     0000    1001    0110    0100
137     112     =   0.9     1000    1001    0111    0000
2       0       =   1       0000    0010    0000    0000
199     13      =   1.1     1100    0111    0000    1101
7       15      =   1.2     0000    0111    0000    1111
71      16      =   1.3     0100    0111    0001    0000
135     17      =   1.4     1000    0111    0001    0001
15      6       =   1.5     0000    1111    0000    0110
7       20      =   1.6     0000    0111    0001    0100
71      21      =   1.7     0100    0111    0001    0101
135     22      =   1.8     1000    0111    0001    0110
199     23      =   1.9     1100    0111    0001    0111
4       0       =   2       0000    0100    0000    0000

Я надеюсь на простой метод преобразования.Или, может быть, это будет сложнее.

Я посмотрел на BCD : «BCD использовался во многих компьютерах с ранним десятичным числом и реализован в наборе команд машин, таких как IBM System / 360 series» и Код операции Intel BCD

Я не знаю, является ли это BCD или что это такое.Как мне преобразовать два бита в число с плавающей запятой?

Ответы [ 2 ]

3 голосов
/ 10 марта 2019

Я использовал информацию с сайта , на которую указал Эндрю Мортон в комментариях. В основном, сохраненное 16-разрядное количество состоит из 15-разрядного целого числа с дополнительным двоичным числом (когда lsb равно 0) или 12-разрядного целого числа с дополнительным двоичным числом плюс код обработки, указывающий масштабный коэффициент, который должен быть применен к этому целому числу (когда lsb это 1). Я не знаком с vb.net, поэтому предоставляю код ISO-C здесь. Программа ниже успешно декодирует все данные, представленные в вопросе.

Примечание. Я выполняю преобразование в 8-байтовый double в приведенном ниже коде, хотя вопрос предполагает, что первоначальное преобразование могло быть в 10-байтовом формате long double (80-битный формат расширенной точности математический сопроцессор 8087). Казалось бы, неплохо попробовать больше тестовых данных для достижения полного охвата восьми кодов масштабирования: большие целые числа, такие как 1 000 000 и 1 000 000 000; десятичные дроби, такие как 0,0003, 0,000005 и 0,00000007; и бинарные фракции, такие как 0,125 (1/8) и 0,046875 (3/64).

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef struct {
    uint8_t byte1;
    uint8_t byte2;
} num;

num data[] = 
{
    {  7, 241}, {254, 255}, {  9, 156}, {  9, 181}, {  9, 206}, {  9, 231},
    { 13,   0}, {137,  12}, {  9,  25}, {137,  37}, {  9,  50}, { 15,   2},
    {  9,  75}, {137,  87}, {  9, 100}, {137, 112}, {  2,   0}, {199,  13},
    {  7,  15}, { 71,  16}, {135,  17}, { 15,   6}, {  7,  20}, { 71,  21},
    {135,  22}, {199,  23}, {  4,   0}
};

int data_count = sizeof (data) / sizeof (data[0]);

/* define operators that may look more familiar to vb.net programmers */
#define XOR  ^
#define MOD  %

int main (void)
{
    int i;
    uint8_t b1, b2;
    uint16_t h, code;
    int32_t n;
    double r;
    for (i = 0; i < data_count; i++) {
        b1 = data[i].byte1;
        b2 = data[i].byte2;
        /* data word */
        h = ((uint16_t)b2 * 256) + b1;
        /* h<0>=1 indicates stored integer needs to be scaled */
        if ((h MOD 2) == 1) {
            /* extract scaling code in h<3:1> */
            code = (h / 2) MOD 8; 
            /* scaled 12-bit integer in h<15:4>. Extract, sign-extend to 32 bits */
            n = (int32_t)((((uint32_t)h / 16) XOR 2048) - 2048);
            /* convert integer to floating-point */
            r = (double)n;
            /* scale based on scaling code */
            switch (code) {
            case 0x0:  r = r * 5000;  break;
            case 0x1:  r = r * 500;   break;
            case 0x2:  r = r / 20;    break;
            case 0x3:  r = r / 200;   break;
            case 0x4:  r = r / 2000;  break;
            case 0x5:  r = r / 20000; break;
            case 0x6:  r = r / 16;    break;
            case 0x7:  r = r / 64;    break;
            };
        } else {
            /* unscaled 15-bit integer in h<15:1>. Extract, sign extend to 32 bits */
            n = (int32_t)((((uint32_t)h / 2) XOR 16384) - 16384);
            /* convert integer to floating-point */
            r = (double)n;
        }
        printf ("[%3d,%3d]  n=%08x  r=% 12.8f\n", b1, b2, n, r);
    }
    return EXIT_SUCCESS;
}

Вывод этой программы следующий:

[  7,241]  n=ffffff10  r= -1.20000000
[254,255]  n=ffffffff  r= -1.00000000
[  9,156]  n=fffff9c0  r= -0.80000000
[  9,181]  n=fffffb50  r= -0.60000000
[  9,206]  n=fffffce0  r= -0.40000000
[  9,231]  n=fffffe70  r= -0.20000000
[ 13,  0]  n=00000000  r=  0.00000000
[137, 12]  n=000000c8  r=  0.10000000
[  9, 25]  n=00000190  r=  0.20000000
[137, 37]  n=00000258  r=  0.30000000
[  9, 50]  n=00000320  r=  0.40000000
[ 15,  2]  n=00000020  r=  0.50000000
[  9, 75]  n=000004b0  r=  0.60000000
[137, 87]  n=00000578  r=  0.70000000
[  9,100]  n=00000640  r=  0.80000000
[137,112]  n=00000708  r=  0.90000000
[  2,  0]  n=00000001  r=  1.00000000
[199, 13]  n=000000dc  r=  1.10000000
[  7, 15]  n=000000f0  r=  1.20000000
[ 71, 16]  n=00000104  r=  1.30000000
[135, 17]  n=00000118  r=  1.40000000
[ 15,  6]  n=00000060  r=  1.50000000
[  7, 20]  n=00000140  r=  1.60000000
[ 71, 21]  n=00000154  r=  1.70000000
[135, 22]  n=00000168  r=  1.80000000
[199, 23]  n=0000017c  r=  1.90000000
[  4,  0]  n=00000002  r=  2.00000000
2 голосов
/ 11 марта 2019

Просто перевод VB.Net кода C от njuffa .
Оригинал structure заменен байтовым массивом, а числовой тип данных адаптирован к типам .Net.,Это все.

Dim data As Byte(,) = New Byte(,) {
    {7, 241}, {254, 255}, {9, 156}, {9, 181}, {9, 206}, {9, 231}, {13, 0}, {137, 12}, {9, 25}, 
    {137, 37}, {9, 50}, {15, 2}, {9, 75}, {137, 87}, {9, 100}, {137, 112}, {2, 0}, {199, 13}, 
    {7, 15}, {71, 16}, {135, 17}, {15, 6}, {7, 20}, {71, 21}, {135, 22}, {199, 23}, {4, 0}
}

Dim byte1, byte2 As Byte
Dim word, code As UShort
Dim nValue As Integer
Dim result As Double

For i As Integer = 0 To (data.Length \ 2 - 1)
    byte1 = data(i, 0)
    byte2 = data(i, 1)
    word = (byte2 * 256US) + byte1
    If (word Mod 2) = 1 Then
        code = (word \ 2US) Mod 8US
        nValue = ((word \ 16) Xor 2048) - 2048
        Select Case code
            Case 0 : result = nValue * 5000
            Case 1 : result = nValue * 500
            Case 2 : result = nValue / 20
            Case 3 : result = nValue / 200
            Case 4 : result = nValue / 2000
            Case 5 : result = nValue / 20000
            Case 6 : result = nValue / 16
            Case 7 : result = nValue / 64
        End Select
    Else
        'unscaled 15-bit integer in h<15:1>. Extract, sign extend to 32 bits
        nValue = ((word \ 2) Xor 16384) - 16384
        result = nValue
    End If
    Console.WriteLine($"[{byte1,3:D}, {byte2,3:D}]  number = {nValue:X8} result ={result,12:F8}")
Next
...