Skip to content

Instantly share code, notes, and snippets.

@w495
Last active December 28, 2015 14:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save w495/7514989 to your computer and use it in GitHub Desktop.
Save w495/7514989 to your computer and use it in GitHub Desktop.
Перевод чисел в систему исчисления с основанием от 2 до 95 (участвуют все печатные символы таблицы `ASCII`). Для случае основания равного `64`, формула преобразования не совпадает со стандартным `base64`. Ниже самого кодировщика приведены программы для декодирования на языках Python и Erlang. Подробное описание в комментариях.
/** ****************************************************************************
*
* @mainpage Перевод всех байтов входного потока в заданную систему счисления.
*
* @section ОПИСАНИЕ
* Программа переводит все байты входного потока (stdin) в заданную
* систему заданную счисления и печатает результат в выходной поток.
* При необходимости добавляет ведущие нули, для выравнивания числа
* до одинаковой ширины.
* @note Важно понимать, что байты входного потока,
* это просто числа в системе счисления с основанием 256.
* Основание системы счисления результата задается как первый аргумент
* командной строки при вызове программы:
* >$ ./basen 2
* Основание системы счисления выбирается от 2 до 95.
* При использовании основания 95, в качестве цифр системы счисления
* используются все печатные символы таблицы ASCII.
*
* Пример работы программы:
* >$ echo 'Hello' | ./basen 2
* 010010000110010101101100011011000110111100001010
* >$ echo 'Hello' | ./basen 4
* 102012111230123012330022
* >$ echo 'Hello' | ./basen 8
* 110145154154157012
* >$ echo 'Hello' | ./basen 16
* 48656C6C6F0A
* >$ echo 'Hello' | ./basen 95
* 0*161D1D1G0A
* >$ echo 'Мама мыла раму' | ./basen 64
* 3G2S3G2m3G2y3G2m0W3G2y3H2B3G2x3G2m0W3H203G2m3G2y3H230A
* >$ echo 'Мама мыла раму' | ./basen 95
* 2I1z2I1=2I1}2I1=0W2I1}2J1i2I1|2I1=0W2J1X2I1=2I1}2J1a0A
*
* В качестве дополнительной опции можно задать разделитель между
* представлениями байт выходного потока. Например:
* >$ echo 'Hello' | ./basen 10 ' '
* 072 101 108 108 111 010
* >$ echo 'Hello' | ./basen 16 ' :) '
* 48 :) 65 :) 6C :) 6C :) 6F :) 0A
* >$ echo 'Hello' | ./basen 2 $'\n'
* 01001000
* 01100101
* 01101100
* 01101100
* 01101111
* 00001010
*
* @subsection АЛГОРИТМ
* Для перевода в заданную систему исчисления используется
* рекурсивный алгоритм. На каждом шаге рекурсии вычисляется
* остаток деления байта входного потока на основание
* заданной систему счисления.
* Так определяется цифра в новой системе счисления.
* При обратном ходе рекурсии, происходит вывод цифры на экран.
* Благодаря тому, что вывод происходит именно на обратном ходе
* рекурсии, порядок цифр в выходном числе получается прямым.
*
* @section ЛИЦЕНЗИЯ (BSD)
* © 2013 Илья w-495 Никитин, кафедра 806, МАИ.
* Разрешается повторное распространение и использование как
* в виде исходного кода, так и в двоичной форме,
* с изменениями или без, при соблюдении следующих условий:
* * при повторном распространении исходного кода должно оставаться
* указанное выше уведомление об авторском праве,
* этот список условий и последующий отказ от гарантий;
* * при повторном распространении двоичного кода должна
* сохраняться указанная выше информация об авторском праве,
* этот список условий и последующий отказ от гарантий
* в документации и/или в других материалах,
* поставляемых при распространении;
* * ни название организации, ни имена ее сотрудников
* не могут быть использованы в качестве поддержки
* или продвижения продуктов, основанных на этом ПО
* без предварительного письменного разрешения.
* Эта программа предоставлена владельцами авторских прав и/или
* другими сторонами «как она есть» без какого-либо вида гарантий,
* выраженных явно или подразумеваемых, включая, но не ограничиваясь ими,
* подразумеваемые гарантии коммерческой ценности и пригодности
* для конкретной цели. Ни в коем случае ни один владелец авторских
* прав и ни одно другое лицо, которое может изменять и/или повторно
* распространять программу, как было сказано выше,
* не несёт ответственности, включая любые общие,
* случайные, специальные или последовавшие убытки,
* вследствие использования или невозможности использования программы
* (включая, но не ограничиваясь потерей данных, или данными,
* ставшими неправильными, или потерями принесенными из-за вас
* или третьих лиц, или отказом программы работать совместно
* с другими программами), даже если такой владелец
* или другое лицо были извещены о возможности таких убытков.
*
* @file basen.c
* Основной файл программы.
*
* @package basen
* Основной модуль программы, перевода байтов входного потока
* в заданную систему счисления. Основание системы счисления задается
* как первый аргумент командной строки при вызове программы.
* На вход подается произвольная последовательность байт.
* На выходе --- входные байты в указанной системе исчисления.
*
* @author Илья w-495 Никитин <w@w-495.ru>
* @date 2013.11.18 01:50:44
* @version 1.1
*
** ***************************************************************************/
#include<stdio.h>
#include<stdlib.h>
/**
* @typedef bool_t --- синоним для перечисления {true, false}.
* Просто удобно завести отдельный тип, для дальнейших вычислений.
**/
typedef enum{FALSE = 0, TRUE = 1} bool_t;
/**
* @typedef number_t --- синоним для `int`.
* Просто удобно завести отдельный тип, для дальнейших вычислений.
**/
typedef int number_t;
/**
* @typedef byte_t --- синоним для `unsigned char`.
* Мы используем именно `unsigned char`.
* Ибо именно он является абстракцией байта в языке Си.
* Достаточно подробно рассказано тут:
* habrahabr.ru/post/156593/
*
**/
typedef unsigned char byte_t;
/**
* @fn Выводит на печать число `input` в системе с основанием `base`.
**/
number_t print_base(number_t base, number_t input, number_t width);
/**
* @fn Вычисляет целочисленный логарифм от val по основанию base.
**/
number_t mylog(number_t base, number_t val);
/**
* @fn Вычисляет целочисленный логарифм от val по основанию 2.
**/
number_t mylog2(number_t val);
/**
* @fn Возвращает значение (число) основания системы для преобразования.
* В нашем случае это первый аргумент программы.
**/
number_t get_base(int argc, char* argv[]);
/**
* @fn Возвращает значение разделителя (это строка) для чисел в новой системе.
* @note Для того, чтобы в качестве разделителя использовать непечатные
* символы, типа '\t', '\n', и пр, нужно аргумент командной строки
* заключить в $'<символ>'. Например `./basen 2 $'\n' `
**/
const char* get_sep(int argc, char* argv[]);
int main(int argc, char* argv[]){
/*
* Узнаем основание.
*/
number_t base = get_base(argc, argv);
/*
* Узнаем разделитель (если он есть).
*/
const char* sep = get_sep(argc, argv);
/*
* Вычислим ширину самого широкого числа, для заполнения нулями.
* Это логарифм по основанию системы исчисления от числа 255
* с добавлением единицы.
*/
number_t numberwidth = mylog(base, 255)+1;
/*
* Побайтно считываем файл,
* до тех пор пока не встретим признак конца файла.
* Обратите внимание на, конструкцию `(byte_t)EOF`.
* Это приведение типов.
* Т.к. `typedef unsigned char byte_t`, a EOF = -1.
* То наше условие без приведения типов никогда не выполнится,
* ибо `chr` всегда больше `0`.
*/
byte_t chr = 0;
bool_t isfirst = TRUE;
while((chr = getchar()) != (byte_t)EOF){
/*
* Простые манипуляции для вывода разделителя, если это требуется.
*/
if((sep) && (FALSE == isfirst)){
printf("%s", sep);
}
if (isfirst)
isfirst = FALSE;
/*
* Аналогичное приведение мы используем и при вызове функции.
* Правда тут на самом деле можно просто поменять типы у функции.
*/
print_base(base, (number_t)chr, numberwidth);
}
printf("\n");
return 0;
}
/**
* @fn Возвращает значение (число) основания системы для преобразования.
* В нашем случае это первый аргумент программы.
* Если аргументов недостаточно прерываем программу.
**/
number_t get_base (int argc, char* argv[]) {
int res = 2;
if(argc < 2){
fprintf(stderr, "Error: wrong usage: "
"at least one argument required --- "
"the base of number system. \n");
exit(1);
}
res = abs(atoi(argv[1]));
/*
* Защита от дурака.
*/
if ((2 > res) || (res > 95)){
fprintf(stderr, "Error: wrong usage: "
"the base of number system "
"should be from 2 to 95. \n");
exit(1);
}
return res;
}
/**
* @fn Возвращает значение разделителя (это строка) для чисел в новой системе.
* Если аргумент не указан, возвращает NULL.
**/
const char* get_sep(int argc, char* argv[]) {
if(argc < 3){
return (char *)NULL;
}
return argv[2];
}
/**
* @fn Вычисляет целочисленный логарифм от `val` по основанию `base`.
* Тут используется формула $ log_b(a) = log_c(a) / log_c(b) $.
* Это не самая эффективная реализация, логарифма.
* Попробуйте сделать лучше, и сравнить.
**/
number_t mylog (number_t base, number_t val) {
return mylog2 (val) / mylog2 (base);
}
/**
* @fn Вычисляет целочисленный логарифм от `val` по основанию `2`.
* Для двоичной системы логарифм это всего лишь количество
* правых сдвигов для нуля.
**/
number_t mylog2 (number_t val) {
number_t ret = 0;
while (val != 0) {
val >>= 1;
ret++;
}
return ret;
}
/**
* @fn Выводит на печать число `input` в системе с основанием `base`.
*
* Рекурсивная функция перевода числа в систему исчисления с основанием
* от 2 до 95 (участвуют все печатные символы таблицы `ASCII`).
*
* Если `base < 10`, функция возвращает входное число (`input`)
* приведенное к системе `base`, т.е. разряды входного числа в системе `base`
* умножены на 10, (десятичный сдвиг). Если `base > 10`, функция возвращает
* входное число разряды которого, в системе `base` умножены на 100,
* (стоичный сдвиг).
*
* Возврат описанного значения не является основным назначением данной функции,
* но служит иллюстрацией как можно работать с системами основание,
* которых меньше 10, и больше 10.
*
* @WARNING: Обращаем внимание, что для случае основания равного `64`,
* формула преобразования данной функции не совпадает
* со стандартным `base64`.
*
* @param base основание системы счисления;
* @param input входное число;
* @param width ширина выводимого слова;
* Этот аргумент нужен, чтобы дополнять выходное число
* ведущему нулями, до нужного размера.
* Например `print_base(2, 2, 4)` напечатает `0010`.
* @returns если `base < 10`, `input` приведенное к системе `base`,
* иначе, входное число разряды которого, умножены на 100.
*
**/
number_t print_base(number_t base, number_t input, number_t width){
/*
* Получаем разряд числа `input` в системе счисления `base`
*/
number_t digit = input % base;
/*
* Переменная для хранения предыдущих разрядом числа.
*/
number_t prevdigits = 0;
/*
* Условие остановки рекурсии.
*/
if(!input){
number_t i;
/*
* Печатаем ведущие нули.
* Каждый предыдущий шаг рекурсии уменьшал `width` на единицу,
* т.к. к выходному числу добавлялось по разряду.
* Теперь мы выводим столько нулей, сколько необходимо,
* до дополнения до ширины `width`.
*/
for(i = 0; i < width; ++i)
printf("0");
return 0;
}
/*
* Рекурсивно вызываем эту же функцию,
* Параметр `base` не меняется, параметр `input` становится
* в `base` раз меньше, a параметр `width` уменьшаем на 1,
* если он был положительным (тут используется тренарный оператор).
* Как результат возвращает предыдущие разряды числа.
*/
prevdigits = print_base(base, input / base, (width) ? (width - 1) : 0);
/**
* Вывод полученной цифры на печать.
* Тут возможны варианты.
* Например в стандарте Base64 предлагается иное соответствие
* входных и выходных цифр:
* http://bit.ly/181ON9I
**/
/*
* Если мы получили значение разряда менее 10,
* то выводим его как десятичное число.
*/
if(digit < 10)
printf("%d", digit);
/*
* Далее используем большие латинские буквы от `A` до `Z`.
*/
if((10 <= digit) && (digit < 36))
printf("%c", digit + 'A' - 10);
/*
* Далее используем малые латинские буквы от `a` до `z`.
*/
if((36 <= digit) && (digit < 62))
printf("%c", digit + 'a' - 36);
/*
* Далее используем символы ASCII от `32` до `47`, это:
* ` ` (пробел), `!`, `"`, `#`, `$`, `%`, `&`, `'`,
* `(`, `)`, `*`, `+`, `,`, `—`, `.`, `/`
*/
if((62 <= digit) && (digit < 78))
printf("%c", digit + ' ' - 62);
/*
* Далее используем символы ASCII от `58` до `64`, это:
* `:`, `;`, `<`, `=`, `>`, `?`, `@`
*/
if((78 <= digit) && (digit < 85))
printf("%c", digit + ':' - 78);
/*
* Далее используем символы ASCII от `91` до `96`, это:
* `[`, `\`, `]`, `^`, `_`, ```
*/
if((85 <= digit) && (digit < 91))
printf("%c", digit + '[' - 85);
/*
* Далее используем символы ASCII от `123` до `126`, это:
* `{`, `|`, `}`, `~`
*/
if((91 <= digit) && (digit < 95))
printf("%c", digit + '{' - 91);
if(base < 10)
return prevdigits * 10 + digit;
/*
* Каждый разряд сдвинут на 100, (стоичный сдвиг).
* Примеры:
* 15 = 0x0f -> 15
* 16 = 0x10 -> 100
* 33 = 0x21 -> 201
* 200 = 0xc8 -> 1208
* 255 = 0xff -> 1515
*/
return prevdigits * 100 + digit;
}
#!/usr/bin/env python
# -*- coding: utf8 -*-
##
## Простая программа перевода двоичных строк входного потока,
## в байты выходного потока.
## На вход подаются строки, например
## 01001000
## 01100101
## 01101100
## 01101100
## 01101111
## 00001010
## В результате работы программы они будут переведены в строку `Hello`
##
import sys
import string
if __name__ == '__main__':
while True:
# Читаем построчно из входного потока.
line = sys.stdin.readline()
# Если читать больше нечего (конец файла --- `EOF`),
# то прерываем цикл.
if(not line):
break;
# Переводим считанную строку из двоичной системы счисления.
# В итоге получаем некоторое число --- номер символа.
number = string.atoi(line, 2)
# Получаем символ ASCII по его номеру.
char = chr(number)
# Записываем символ в выходной поток
sys.stdout.write(char)
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname decoder
%%
%% Простая программа перевода двоичных октетов входного потока,
%% в байты выходного потока. На вход подаются строка, например:
%% 010010000110010101101100011011000110111100001010
%% В результате работы программы она будет переведена в строку `Hello`
%%
main(_) ->
% Считываем 8 символов.
case io:get_chars('', 8) of
eof ->
% Если это конец файла, останавливаем программу.
init:stop();
"\n" ->
% Если это перевод строки, то просто его выводим.
io:format("~n");
String ->
% В противном случае, переводим строку в число.
Byte = erlang:list_to_integer(String, 2),
% Выводим число как символ.
io:format("~c", [Byte]),
% Повторяем цикл, вызывая функцию рекурсивно.
main([])
end.
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname decoder
%%
%% Простая и быстрая программа перевода двоичных октетов входного потока,
%% в байты выходного потока. На вход подаются строка, например:
%% 010010000110010101101100011011000110111100001010
%% В результате работы программы она будет переведена в строку `Hello`.
%% WARNING:
%% Не пытайтесь дословно воспроизвести всю логику в программе на Си.
%% Erlang имеет иную парадигму программирования.
%% В отличие от Си он основан не на машине фон Неймана.
%% Однако, на уровне идеи логика будет сходной.
%%
main(_) ->
% Считываем 131072 символа (128 КиБ).
% На самом деле тут все равно сколько считывать.
% Но удобнее, если это число будет кратно количеству байт,
% представляющих закодированный символ.
case io:get_chars('', 131072) of
eof ->
% Если это конец файла, останавливаем программу.
init:stop();
"\n" ->
% Если это перевод строки, то ничего не делаем,
% просто повторяем итерацию.
main([]);
Input ->
% Иначе разберем входную строку вручную,
% и получим перекодированную строку.
Output = parse(Input),
% Выведем строку как строку.
io:format("~s", [Output]),
% Повторяем итерацию.
main([])
end.
%%
%% @doc Разбирает входной список символов и декодирует его.
%% @spec parse(list()) -> list().
%%
parse(List) ->
parse(List, []).
%%
%% @doc Разбирает входной список символов и декодирует его.
%% Первый параметр это входной список,
%% во втором параметре накапливаются значения выходного списка.
%% Функция состоит из трех уравнений.
%% * Первое уравнение срабатывает если входной список пуст
%% (т.е. мы уже все разобрали). Тогда просто возвращаем выходной
%% список, предварительно перевернув его.
%% * Второе уравнение срабатывает, если мы встретили символ
%% перевода строки. Тогда откусываем, этот символ
%% от входного списка, во входной список следующей итерации
%% кладем то, что осталось, а выходной список
%% оставляем без изменения.
%% * Третье уравнение срабатывает, когда во входном списке есть,
%% восемь символов. Откусываем от входного списка восемь элементов,
%% кладем, что осталось во входной список следующей итерации.
%% Вычисляем результирующий символ по схеме Горнера
%% и добавляем его в начало выходного списка.
%% @spec parse(list(), list()) -> list().
%%
parse([], Acc) ->
lists:reverse(Acc);
parse([$\n| Rest], Acc) ->
parse(Rest, Acc);
parse([X1,X2,X3,X4,X5,X6,X7,X8| Rest], Acc) ->
% Вычислим многочлен по основанию 2 по схеме Горнера.
Number =
(
(
(
(
(
(
(
(
(
(
(
(
convert(X1)
* 2
) + convert(X2)
) * 2
) + convert(X3)
) * 2
) + convert(X4)
) * 2
) + convert(X5)
) * 2
) + convert(X6)
) * 2
) + convert(X7)
) * 2
+ convert(X8),
parse(
Rest, % Остаток входного списка.
[Number|Acc] % Добавка к выходному списку.
).
%%
%% @doc Переводит символ в число.
%% Просто отнимаем от входного символа код символа '0'.
%% @spec convert(integer()) -> integer()
%%
convert(X) ->
X - $0.
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname decoder
%%
%% Простая и быстрая программа перевода 95-ичных пар входного потока,
%% в байты выходного потока. На вход подаются строка, например:
%% 2I1z2I1_2J1X2J1a0W2I1}2I1_2J1X0A
%% В результате работы программы она будет переведена в строку
%% `Миру мир!`.
%% WARNING:
%% Не пытайтесь дословно воспроизвести всю логику в программе на Си.
%% Erlang имеет иную парадигму программирования.
%% В отличие от Си он основан не на машине фон Неймана.
%% Однако, на уровне идеи логика будет сходной.
%%
-define(BASE, 95).
main(_) ->
% Считываем 32768 символа (32 КиБ).
% На самом деле тут все равно сколько считывать.
% Но удобнее, если это число будет кратно количеству байт,
% представляющих закодированный символ.
case io:get_chars('', 32768) of
eof ->
% Если это конец файла, останавливаем программу.
init:stop();
"\n" ->
% Если это перевод строки, то ничего не делаем,
% просто повторяем итерацию.
main([]);
Input ->
% Иначе разберем входную строку вручную,
% и получим перекодированную строку.
Output = parse(Input),
% Выведем строку как строку.
io:format("~s", [Output]),
% Повторяем итерацию.
main([])
end.
%%
%% @doc Разбирает входной список символов и декодирует его.
%% @spec parse(list()) -> list().
%%
parse(List) ->
parse(List, []).
%%
%% @doc Разбирает входной список символов и декодирует его.
%% Первый параметр это входной список,
%% во втором параметре накапливаются значения выходного списка.
%% Функция состоит из трех уравнений.
%% * Первое уравнение срабатывает если входной список пуст
%% (т.е. мы уже все разобрали). Тогда просто возвращаем выходной
%% список, предварительно перевернув его.
%% * Второе уравнение срабатывает, если мы встретили символ
%% перевода строки. Тогда откусываем, этот символ
%% от входного списка, во входной список следующей итерации
%% кладем то, что осталось, а выходной список
%% оставляем без изменения.
%% * Третье уравнение срабатывает, когда во входном списке есть,
%% восемь символов. Откусываем от входного списка восемь элементов,
%% кладем, что осталось во входной список следующей итерации.
%% Вычисляем результирующий символ по схеме Горнера
%% и добавляем его в начало выходного списка.
%% @spec parse(list(), list()) -> list().
%%
parse([], Acc) ->
lists:reverse(Acc);
parse([$\n| Rest], Acc) ->
parse(Rest, Acc);
parse([X1,X2| Rest], Acc) ->
% Вычислим многочлен по основанию BASE по схеме Горнера.
Number = ((convert(X1) * ?BASE) + convert(X2)),
parse(
Rest, % Остаток входного списка.
[Number|Acc] % Добавка к выходному списку.
).
%%
%% @doc Переводит символ в число.
%% Просто отнимаем от входного символа код символа
%% в зависимости от диапазона входного символа.
%% @spec convert(integer()) -> integer()
%%
convert(X) when $0 =< X, X =< $9 ->
X - $0;
convert(X) when $A =< X, X =< $Z ->
X - $A + 10;
convert(X) when $a =< X, X =< $z ->
X - $a + 36;
convert(X) when 32 =< X, X =< $/ ->
X - 32 + 62;
convert(X) when $: =< X, X =< $@ ->
X - $: + 78;
convert(X) when $[ =< X, X =< $` ->
X - $[ + 85;
convert(X) when ${ =< X, X =< $~ ->
X - ${ + 91;
convert(_) ->
% Если ни один из вариантов не срабатывает.
0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment