суббота, 1 февраля 2014 г.

Вещественное в строку

Вещественное в строку



(с) Никита Культин, 2014.

Ниже приведена функция _si2st, которая преобразует single значение в строку. Идея алгоритма заключается в нормализации исходного числа (делает это функция _si2nsi) и последующем умножении нормализованного числа на 10 для получения очередной значащей цифры. Число и требуемый формат отображения (количество цифр целой и дробной частей), передаются функции через стек. Если количество цифр дробной части задано равным нулю, то формируется экспоненциальное представление. Если число такое, что отобразить его в формате с точкой нельзя, то тоже формируется экспоненциальное представление.

  
// ------------------------------------
// Преобразует single в СТРОКУ!
(с) Никита Культин, 2014.
 _si2st:
 // _mm - общее кол-во позиций
 // _nn - кол-во цифр дробной части
 // _pp - позиция точки
 // _kk - кол-во пробелов перед числом
 // _ff - флаг формата
 // _fs - флаг знака порядка
 // float_10 - DWORD, в котором 
 //            находится 0x41200000 - single значение 10
 // _buf - строковое представление числа

  mov ebx, [esp]+4
  mov eax, [ebx]
  mov r1, eax

  mov eax, [esp]+12 // кол-во цифр др. части
  mov _nn, eax

  mov eax, [esp]+8  // кол-во позиций
  test eax, eax
  jz a00
  dec eax
a00:  mov _mm, eax
  mov _kk, eax

  xor eax, eax
  mov _fs, eax   // флаг знака порядка
  mov _ff, eax     // флаг

  // чтобы узнать порядок числа, нормализуем его
  push offset r1  // исходное число
  push offset r2  // нормализованное число
  push offset p   // порядок нормализованного числа
  mov ebx, offset si2nsi
  call ebx

  add esp, 12

a0: cmp _mm, 0
    je a2           // вывод в Е формате
    //mov eax, _mm
    //test eax, eax
    //jz a2

    cmp _nn, 0       // вывод в m:n формате
    je a2
    //mov eax, _nn
    //test eax, eax
    //jnz a1

  // вывод в m формате
  //mov eax, _mm
  //cmp eax, 9
  //jb  a2
  //sub eax, 9
  //mov _kk, eax  // _kk = _mm -9
  //jmp a2

a1: // m:n формат
     // проверить знак порядка
  mov ecx, p
  cmp ecx, 0
  jge a9

  // p<0
  inc _fs
  neg ecx
  cmp ecx, _nn
  jg a2         // выводим в e-формате
  inc _ff
  xor eax, eax
  mov _pp,eax
  mov eax, _nn
  add eax, 2
  mov _mm,eax
   // число с отрицательным порядком приведем к числу
   // с порядком 0. Будем делить на 10
a8:test ecx, ecx
  jz a3
  fld r2
  fld float_10
  fdiv
  fstp r2
  dec ecx
  jmp a8

a9: // p>= 0
    // вывести можно?
    // т.е. (m > p+2+n) AND (n+p < 7), где 7 - кол-во значащих цифр для типа single
   mov eax, p
   add eax, _nn
   cmp eax, 6
   jg a2        // n+p > 7
   add eax, 2
//   выполняется условие m > p+2+n ?
//   cmp eax, _mm
//   ja  a2

   // вывод в m:n формате
   mov _mm,eax // общее кол-во цифр
   //xor eax, eax
   cmp _fs, 0  //
   jnz a4
   mov eax, p
a4:mov _pp, eax
   inc _ff
   jmp a3

a2: // значение такое, что в формате m:n отобразить нельзя
   mov eax, 8
   mov _mm, eax
   xor eax,eax
   mov _pp,eax
   mov _ff,eax

    //
a3: // вывод
    //

   lea edi, _buf

     // вычистить кол-во ведущих пробелов
     mov ecx, _kk
     sub ecx, _mm
     cmp _ff, 0   // e-формат?
     jnz a33
     sub ecx, 4   // кол-во позиций вывода порядка

a33:
     mov eax, 32     // пробел
a31: cmp ecx, 0
     jle a32
     stosb
     dec ecx
     jmp a31

   // При выделении целой части числа командой fist округление
   // по умолчанию выполняется "с избытком".
   // Нам необходимо отбросить дробную часть. Поэтому
   // установим режим округления "отбросить дробную часть".
   // Чтобы это сделать, надо установить 10 и 11 биты слова управления.
a32: fstcw cwr     // читать слово управления сопроцессора
   mov ax, cwr
   or ax, $0c00  // установить 10 и 11 биты
   mov cwr, ax
   fldcw cwr     // записать слово управления

    fld r2  // загрузить число в сопроцессор


//    Теперь не надо! 
//    // проверка на НОЛЬ!
//    FLDZ
//    FCOMp
//    fstsw ax       // результат проверки сохранить в ax
//    test ax, $4000 // значение равно нулю?
//    jnz  m0        // да!

    // Число положительное или отрицательное?
    fxam          // проверить число
    fstsw ax      // результат проверки передать в ax
    and ax, $0200 // выделить флаг c1.

    // с1 = 1 - число отрицательное; c1 = 0 - число положительное
    cmp ah, 2      // проверить c1
    jne m5
    mov eax, $2d  // в знаковой позиции минус
    fabs
    jmp m6
m5: mov eax, $20
m6: stosb          // знак: пробел или минус

    xor ecx, ecx


m0: fist n1   // n1 - целая часть
    mov eax, n1
    // в результате округления возможно, что
    // n1 = 10. Что НЕПРАВИЛЬНО!
    cmp eax, 10
    jl m06
    // Корректируем
    mov eax, 1
    inc p

m06:add eax, 48
    stosb
    // ставим точку?
    cmp ecx, _pp
    jne m01
    mov eax, 46   // точка
    inc ecx
    stosb
m01:inc ecx
    cmp ecx, _mm
    je m02
    fild n1
    fsub
    fld float_10
    fmul
    jmp m0

m02: mov eax, _ff
     cmp eax, 0
     jnz  m05
     mov eax, 69 // символ E
     stosb

     // знак порядка
     mov ecx, 43       // "+"
     mov eax, p       
     cmp eax, 0
     jge m03
     mov ecx, $2d     // "-"
     neg p
m03: mov eax, ecx
     stosb

    // цифры порядка
    mov eax, p
   // будем печатать две цифры
   //  cmp p, 10
   //  jb m04
   // порядок больше 10
     fild p
     fld float_10
     fdiv
     fistp n1
     mov eax, n1
     add eax, 48
     stosb

     fild p
     fld float_10
     fild n1
     fmul
     fsub
     fistp p

m04: mov eax, p
     add eax, 48
     stosb

m05: xor eax, eax
     stosb

     ret 12


//////////////////////////////////////////////////////////
// Нормализует single.
// (с) Никита Культин, 2014.
// Результат: нормализованное single и порядок
_si2nsi:

    mov ebx, [esp]+12
 mov eax, [ebx]
    mov _r, eax

    FINIT

    fld _r
    // число на вершине стека сопроцессора

    mov p, 0 // порядок

    // проверить значение на вершине стека:
    // - равно нулю?
    // - отрицательное?
    FLDZ
    FCOMp
    fstsw ax       // результат проверки сохранить в ax

    test ax, $4000 // значение равно нулю?
    jnz  k5        // да!

    test ax, $100  // значение отрицательное?
    jnz k0         // нет

    // отрицательное
    FCHS  // изменить знак

k0:
    FLD1     // множитель, учитывающий знак числа
    test ax, $100  // значение отрицательное?
    jnz k01        // нет
    FCHS

k01: // на вершине стека число, в st(1) 1 или -1
    FXCH st(1)
    // теперь на вершине стека число

    // больше 10?
k1: FLD float_10
    FCOM // сравнить значения

    fstsw ax      // результат проверки передать в ax
    test ax, $4500
    jz  k2   // меньше 10 - да! Проверить: больше 1?
    // мантисса больше 10
    FDIV
    inc p
    jmp k1

k2: FCOMP // выкинуть 10 с вершины стека (другой способ придумать не могу)

k3: // меньше единицы?
    FLD1
    FCOMP          // сравнить значения
    fstsw ax       // результат проверки передать в ax
    test ax, $4100
    jnz  k4        // меньше или равен 1 !
    fmul float_10
    dec p
    jmp k3

k4: // на вершине стека нормализованное число, в st(1) 1 или -1
    fmulp

k5: fstp _r

     // значения возвращаем через стек
     mov eax, _r
     mov ebx, [esp]+8
  mov [ebx], eax

     mov eax, p
     mov ebx, [esp]+4
  mov [ebx], eax

    ret

/////////////////////////////
// Преобразовать single в строку
   push 9;        //  кол-во цифр дробной части
   push 2;        //  кол-во позиций для вывода числа
   push offset r  // число
   mov ebx, offset si2st
   call ebx