Преобразовать очень большое число в строку 10 базы - PullRequest
6 голосов
/ 01 марта 2012

Целое число измеряет 4 байта.В моем примере у меня есть цифры размером 1 МБ.Как я могу преобразовать их в читаемое человеком десятичное число fast ?

Число присутствует в uint[] массиве, содержащем Size элементов.

Ответы [ 4 ]

10 голосов
/ 03 марта 2012

Я думал о твоей проблеме.У меня нет закодированного решения, но вот подход:

Прежде всего, давайте предположим, что без потери общности, у вас есть набор из 2 n битов.(Если у вас меньше 2 n битов, добавьте битовый массив начальными нулями, пока вы не сделаете это. Очевидно, что это никогда не удваивает размер массива.) В вашем случае вы говорите, что имеетемиллион уинт, так что это 2 25 бит.

Давайте также предположим, что каждая коллекция из 2 k + 1 битов может быть равномерно разделена на две коллекции битов,левый и правый наборы, каждый с 2 ​​ k битами.

Таким образом, каждый набор битов или подколлекция имеет «размер», который является точной степенью двойки.Наименьшая возможная коллекция содержит один бит и не может быть далее подразделена.

Во-вторых, давайте предположим, что у вас аналогично имеется неизменное представление числа в десятичной форме, и что опять же, без потери общности, есть 2 d десятичные цифры в строке.Если их меньше 2 d , снова вставьте начальные нули.Опять же, каждая десятичная коллекция размером больше единицы может быть разделена на две коллекции, каждая из которых имеет половину размера.

Теперь мы набросаем рекурсивный алгоритм:

DigitCollection Convert(BitCollection bits)
{
    if (bits.Size == 1)
        return bits[0] ? DigitCollection.SingleOne : DigitCollection.SingleZero;
    DigitCollection left = Convert(bits.Left);        
    DigitCollection right = Convert(bits.Right);
    DigitCollection power = MakePowerOfTwo(bits.Right.Size);
    return Add(Multiply(left, power), right);
}

Теперь нам нужны методы Add,Умножьте и MakePowerOfTwo.Как мы увидим через некоторое время, нам также понадобятся Subtract и два оператора Shift для быстрого умножения на степени десяти.

Сложение и вычитание легко.Ясно, что если более длинная коллекция содержит n цифр, то методы сложения и вычитания могут быть реализованы, чтобы занять O (n) времени.

Операторы FullShift и HalfShift делают новые коллекции цифр из старых, чтобы ускорить умножение на степени десяти.Если коллекция цифр размером 2 d + 1 состоит из вложенных коллекций (X1, X2), каждая из которых имеет размер 2 d , тогда коллекция "сдвинутых наполовину" содержит 2 d + 2 элементов и состоит из ((2 d начальных нулей, X1), (X2, 2 d конечных нулей)).Полная смена состоит из ((X1, X2), (2 d + 1 конечных нулей)).

Их, очевидно, очень дешево построить.

Умножение - это то, где мы сталкиваемся с большими проблемами .Предположим без ограничения общности, что мы умножаем вместе две DigitCollections, каждая из которых содержит ровно 2 d цифр.Мы можем использовать алгоритм Карацубы:

DigitCollection Multiply(DigitCollection x, DigitCollection y)
{
    // Assume x and y are the same digit size.
    if (x and y are both single digits)
        return the trivial solution;
    // Assume that z2, z1 and z0 are all the same digit size as x and y.
    DigitCollection z2 = Multiply(x.Left, y.Left);
    DigitCollection z0 = Multiply(x.Right, y.Right);
    DigitCollection z1 = Subtract(
        Multiply(Add(x.Left, x.Right), Add(y.Left, y.Right)),
        Add(z2, z0));
    return Add(Add(FullShift(z2), HalfShift(z1)), z0);
}

Каков порядок этого алгоритма?Предположим, что есть n = 2 d цифр.Что такое O (Multiply (n))?Мы повторяем три раза, каждый с проблемой вдвое меньше цифр.Все остальные операции сложения, вычитания и сдвига не более O (n).Таким образом, у нас есть рецидив:

T(n) = 3 T(n/2) + O(n)

, который имеет простое решение с помощью основной теоремы: этот алгоритм O (n 1 / lg 3 ), что около O (n 1,58 ).

А как насчет MakePowerOfTwo?Это легко, учитывая то, что у нас уже есть.Мы используем идентификацию:

2 2n + 1 = 2 n x 2 n + 2 n x 2 n

и напишите алгоритм:

DigitCollection MakePowerOfTwo(int p)
{
    if (p == 0) return DigitCollection.SingleOne;
    DigitCollection power = MakePowerOfTwo(p / 2);
    power = Multiply(power, power);
    if (p % 2 != 0)
        power = Add(power, power);
    return power;
}

В нем преобладают вычисления умножения, равно как и O (n *)1085 * 1.58 ).

И теперь мы можем видеть, что при исходном вызове Convert также преобладает умножение.

Так что с этим алгоритмом, если у вас есть 2 d двоичные цифры для преобразования, вы можете ожидать, что для этого потребуется примерно O (2 1,58 d ) шагов.В вашем случае у вас есть 2 25 бит для преобразования, так что для этого потребуется около 777 миллиардов вычислений.

Ключевым фактом здесь является то, что в этом алгоритме доминирует стоимость умножения . Если вы можете уменьшить стоимость умножения до менее чем O (n 1.58 ), то вы получите огромных выигрышей. На вашем месте я бы изучал улучшения алгоритмов десятичного умножения над Карацубой.

3 голосов
/ 02 марта 2012

Возможно, вам удастся сэкономить некоторое время, выполнив более одной цифры за раз. если вы сделаете это, скажем, 100 000 за раз, скорее всего, это будет происходить, по крайней мере, немного быстрее, чем за 10 раз.

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

Вполне возможно, что вы можете сделать его рекурсивным и значительно ускорить его - получить грубый квадратный корень из числа, округленный до ближайшего показателя степени 10. div и mod по этому числу, и отправить результаты обратно к той же функции. Имейте в виду, я не уверен, как вы будете эффективно работать с div или модом такого размера, но если вы можете понять это (и не исчерпать память), это все равно будет более эффективным по времени чем деление его на цифру за раз.

Альтернативная версия: откажитесь от десятичных дробей - поскольку число будет слишком велико, чтобы иметь смысл для любых реальных читателей-людей, в любом случае - и переведите вещь в шестнадцатеричный формат. Все еще технически читаемо, но вы можете визуализировать его байт за один раз и сэкономить себе целую партию душевной боли.

3 голосов
/ 02 марта 2012

Я не знаю, будет ли это быстрее, но вот пример в Delphi, который я написал давно, чтобы обрабатывать большие целые числа как строки (ОЧЕНЬ быстро и грязно) - это было для 128-битного uint, но вы могли бы его расширитьнеопределенно

Function HexToBinShort(hex:integer):string;
begin
  case hex of
    0:  result:='0000';  //convert each hex digit to binary string
    1:  result:='0001';  //could do this with high-nybble and low nybble
    2:  result:='0010';  //of each sequential byte in the array (mask and bit shift)
    3:  result:='0011';  //ie: binstring:=binstring + HexToBinShort(nybble[i])
    4:  result:='0100';  //but must count DOWN for i (start with MSB!)
    5:  result:='0101';
    6:  result:='0110';
    7:  result:='0111';
    8:  result:='1000';
    9:  result:='1001';
    10: result:='1010';
    11: result:='1011';
    12: result:='1100';
    13: result:='1101';
    14: result:='1110';
    15: result:='1111';
  end;
end;

Затем возьмите сцепленную двоичную строку и добавляйте степени двойки каждый раз, когда вы видите '1'

Function BinToIntString(binstring:string):string;
var i, j : integer;
var calcHold, calc2 :string;
begin
  calc2:=binstring[Length(binstring)];   // first bit is easy 0 or 1
  for i := (Length(binstring) - 1) downto 1 do begin       
    if binstring[i] = '1' then begin   
       calcHold:=generateCard(Length(binstring)-i);
       calc2 := AddDecimalStrings(calcHold, calc2);
    end;
  end;
  result:=calc2;
end;

generateCard, который используется для создания десятичного строкового представления 2 ^i (для i> 0)

Function generateCard(i:integer):string;
var j : integer;
var outVal : string;
begin
  outVal := '2';
  if i > 1 then begin
    for j := 2 to i do begin
      outVal:= MulByTwo(outVal);
    end;
  end;
  result := outVal;
end;

и MulByTwo умножает десятичную строку на две

Function MulByTwo(val:string):string;
var i : integer;
var carry, hold : integer;
var outHold : string;
var outString :string;
var outString2 : string;
begin
  outString:= StringOfChar('0', Length(val) + 1);
  outString2:= StringOfChar('0', Length(val));
  carry :=0;
  for i := Length(val) downto 1 do begin
    hold := StrToInt(val[i]) * 2 + carry;
    if hold >= 10 then begin
      carry := 1;
      hold := hold - 10;
    end else begin
      carry := 0;
    end;
    outHold := IntToStr(hold);
    outString[i+1] := outHold[1];
  end;
  if carry = 1 then begin
    outString[1] := '1';
    result := outString;
  end else begin
    for i := 1 to length(outString2) do outString2[i]:=outString[i+1];
    result := outString2;
  end;
end; 

И наконец - AddDecimalStrings ... ну, это добавляет две десятичные строки:

Function AddDecimalStrings(val1, val2:string):string;
var i,j :integer;
var carry, hold, largest: integer;
var outString, outString2, bigVal, smVal, outHold:string;
begin
  if Length(val1) > Length(val2) then begin
    largest:= Length(val1);
    bigVal := val1;
    smVal := StringOfChar('0', largest);
    j:=1;
    for i := (largest - length(val2) +1) to largest do begin
      smVal[i] := val2[j];
      j:=j+1;
    end;
  end else begin
    if length(val2) > Length(val1) then begin
      largest:=Length(val2);
      bigVal:=val2;
      smVal := StringOfChar('0', largest);
      j:=1;
      for i := (largest - length(val1) +1) to largest do begin
        smVal[i] := val1[j];
        j:=j+1;
      end;
    end else begin
      largest:=length(val1);
      bigVal:=val1;
      smVal:=val2;
    end;
  end;
  carry:=0;
  outString:=StringOfChar('0', largest +1);
  outString2:=StringOfChar('0', largest);

  for i := largest downto 1 do begin
    hold := StrToInt(bigVal[i]) + StrToInt(smVal[i]) + carry;
    if hold >=10 then begin
      carry:=1;
      hold := hold - 10;
    end else begin
      carry:=0;
    end;
    outHold:= IntToStr(hold);
    outString[i+1]:=outHold[1];
  end;

  if carry = 1 then begin
    outString[1] := '1';
    result := outString;
  end else begin
    for i := 1 to length(outString2) do outString2[i]:=outString[i+1];
    result := outString2;
  end;  
end;

Эти функции позволяют выполнять основную арифметику с почти произвольно большими целыми числами в виде строк.Конечно, вы попадаете в другую стену, когда число цифр слишком велико, чтобы индексировать массив.

Вот, например, деление на два (полезно для перехода в другую сторону ...).Я не обрабатываю нечетные числа здесь.

Function DivByTwo(val:string):string;
var i : integer;
var hold : integer;
var outHold : string;
var outString, outString2 :string;
begin
  outString:=StringOfChar('0',Length(val));

  for i := Length(val) downto 1 do begin
    if StrToInt(val[i]) mod 2 = 0 then begin
      hold:= Math.Floor(StrToInt(val[i]) / 2);
      outHold:= IntToStr(hold);
      outString[i]:=outHold[1];
    end else begin
      hold:= Math.Floor((StrToInt(val[i]) - 1) / 2);
      outHold:=IntToStr(hold);
      outString[i]:= outHold[1];
      if i <> Length(val) then begin
        hold:= StrToInt(outString[i+1]) + 5;
        outHold:= IntToStr(hold);
        outString[i+1] := outHold[1];
      end;
    end;
  end;
  outString2:=StringOfChar('0',Length(val)-1);
  if (outString[1] = '0') and (length(outString) > 1) then begin
    for i := 1 to length(outString2) do outString2[i]:=outString[i+1];
    result:=outString2;
  end else begin
    result:=outString;
  end;
end;

РЕДАКТИРОВАТЬ: Я только что попробовал это с 9-битной двоичной строкой, и это до смешного медленно!Не удивительно, правда.Это совершенно неоптимизированный код, в котором есть много низко висящих фруктов, чтобы ускорить процесс.Тем не менее, я не могу не чувствовать, что это та проблема (или масштаб), которую вы, вероятно, захотите написать, хотя бы частично, в полностью оптимизированной сборке.Отдельные операции невелики, но они должны выполняться много раз - это означает, что нужно просить сборку.Многопоточность, безусловно, может быть использована и здесь.

0 голосов
/ 03 марта 2012

Благодаря всем вам, я нашел способ, основанный, главным образом, на идее J ..., который предложил преобразовать число в 10 основанных чисел, складывая степень 2 каждый раз, когда есть 1.Но вместо десятичной (человеческой десятичной системы) я использую систему на основе 1000000000000000000 (10 ^ 18).Таким образом, каждая цифра имеет не только 10 возможностей (0 ... 9), но на самом деле 10 ^ 18!Это вписывается в 64-битное число, которое мы затем конвертируем .ToString()

Это самый эффективный способ из всех.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...