пятница, 10 июня 2011 г.

Форматирование числа в строку на PL/SQL (число прописью v2)

Здесь я предлагал вариант реализации функции, которая форматирует заданное число в строку прописью на английском языке. Однако для нас, жителей России, эта функция не особо-то и интересна...
Предлагаю вариант функции, которая форматирует число на русском языке.


Зачем это нужно именно на PL/SQL?

Прямой необходимости в реализации такой функции на стороне сервера нет. Функция, очевидно, нужна для формирования отчётов, её можно реализовать на стороне клиента (Delphi-приложение, или web-страница). Однако наличие этой функции на стороне сервера:

  • позволяет иметь единственную реализацию функции, что удобно при сопровождении кода;
  • функция на PL/SQL работает в самой Oracle, реализация на PL/SQL никак не привязывается к платформе;
  • позволяет создавать view, ссылающиеся на эту функцию, а view можно использовать из любого клиента


Пару слов об алгоритме.

Алгоритм форматирования достаточно прост. Сначала разбиваем число на триады (по три разряда, справа на лево, делением на тысячи). Затем перебираем триады в обратном порядке, и форматируем строку для каждой триады, добавляя перед строкой наименование триады (миллион, триллион и т.п.). При этом необходимо помнить об отдельном написании числительных от 11 до 19, десятков (от 20, 30 и до 90), сотен (100, 200 .. 900). Так же не забываем о склонении числительных (одна тысяча, две тысячи, пять тысяч). Ну и напоследок вспомним, что число прописью может использоваться совместно с существительным мужского, женского, либо среднего рода. Чтобы все эти нюансы учесть красиво, само-собой напрашивается решение использовать массивы со всеми возможными написаниями числительных. О работе с массивами расскажу ниже.

Реализация кода (пакет на PL/SQL)

create or replace package fmt_ru is
  -- function num_declination возвращает склонение числа: 0, 1 или 2
  function num_declination(number_ in integer) return integer;
  
  -- function number_in_words форматирут число прописью
  --   gender_ указывает род числа: 'M' - мужской, 'F' - женский, 'S' - средний
  --   short_ указывает, использовать ли сокращённые наименования триад (если > 0) или нет (0)
  function number_in_words(number_ in number, gender_ in varchar2 default 'M', short_ in integer default 0) return varchar2;
end;
/
create or replace package body fmt_ru is
  s_zero constant varchar2(32) := 'ноль';
  s_minus constant varchar2(32) := 'минус';
   
  -- единицы:  
  nw_med constant ta_vc_32 := ta_vc_32('один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять');-- мужской род
  nw_fed constant ta_vc_32 := ta_vc_32('одна', 'две', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять');-- женский род
  nw_sed constant ta_vc_32 := ta_vc_32('одно', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять');-- средний род
  -- от 11 до 19:
  nw_eds constant ta_vc_32 := ta_vc_32('одиннадцать', 'двенадцать', 'тринадцать', 'четырнадцать', 'пятнадцать', 'шестнадцать',
            'семнадцать', 'восемнадцать', 'девятнадцать');
  -- десятки:
  nw_dds constant ta_vc_32 := ta_vc_32('десять', 'двадцать', 'тридцать', 'сорок', 'пятьдесят', 'шестьдесят',
            'семьдесят', 'восемьдесят', 'девяносто');
  -- сотни:
  nw_hds constant ta_vc_32 := ta_vc_32('сто', 'двести', 'триста', 'четыреста', 'пятьсот', 'шестьсот',
            'семьсот', 'восемьсот', 'девятьсот');
  -- триады
  nw_tst constant ta_vc_32 := ta_vc_32('тыс.', 'млн.', 'млрд.', 'трлн.', 'квадр.', 'квинт.',
            'секст.', 'септ.', 'окт.', 'нон.', 'дец.', 'ундец.', '');  -- сокр.
  nw_tf0 constant ta_vc_32 := ta_vc_32('тысяч', 'миллионов', 'миллиардов', 'триллионов', 'квадриллионов', 'квинтиллионов',
            'секстиллионов', 'септиллионов', 'октиллионов', 'нониллионов', 'дециллионов', 'ундециллионов', '');
  nw_tf1 constant ta_vc_32 := ta_vc_32('тысяча', 'миллион', 'миллиард', 'триллион', 'квадриллион', 'квинтиллион',
            'секстиллион', 'септиллион', 'октиллион', 'нониллион', 'дециллион', 'ундециллион' ,'');
  nw_tf2 constant ta_vc_32 := ta_vc_32('тысячи', 'миллиона', 'миллиарда', 'триллиона', 'квадриллиона', 'квинтиллиона',
            'секстиллиона', 'септиллиона', 'октиллиона', 'нониллиона', 'дециллиона', 'ундециллиона', '');
  
  nw_triads ta_integer := ta_integer(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

  
  function num_declination(number_ in integer) return integer is
    num_ integer;
  begin
    num_ := abs(number_) mod 100;
    if num_ > 20 then
      num_ := num_ mod 10;
    end if;
    
    case num_
      when 1 then return 1;
      when 2 then return 2;
      when 3 then return 2;
      when 4 then return 2;
    else
      return 0;
    end case;
  end;
  
  function number_in_words(number_ in number, gender_ in varchar2 default 'M', short_ in integer default 0) return varchar2
  is
    result  varchar2(4000);
    num     number(38, 0);
    tmp     number(38, 0);
    cnt     integer;
    triada  integer;
    
    e integer;
    d integer;
    h integer;
  begin
    if number_ is null then
      return null;
    elsif number_ = 0 then
      return s_zero;
    elsif number_ > 0 then
      num := number_;
      result := null;
    else
      num := -number_;
      result := s_minus;
    end if;
    
    -- разбиваем число на триады:
    cnt := 1;
    loop
      -- HINT: mod - дорогостоящая операция, поэтому здесь и далее вместо mod используется 
      -- деление и вычитание с умножением
      --nw_triads(cnt) := num mod 1000;
      tmp := trunc(num / 1000);
      nw_triads(cnt) := num - tmp * 1000;
      exit when tmp = 0;
      num := tmp;
      cnt := cnt + 1;
    end loop;
    
    --  формируем строку слева направо:
    for i in reverse 1 .. cnt loop
      triada := nw_triads(i);
      
      if triada > 0 then
        --h := trunc(triada / 100);
        --d := trunc(triada / 10) mod 10;
        --e := triada mod 10;
        h := trunc(triada / 100);
        tmp := triada - h * 100;
        d := trunc(tmp / 10);
        e := tmp - d * 10;
        
        -- добавляем сотни
        if h > 0 then
          str.rappend(result, nw_hds(h), ' ');
        end if;
        
        -- добавляем десятки:
        if d > 0 then
          if d = 1 and e > 0 then
            str.rappend(result, nw_eds(e), ' ');
            e := 0;
          else
            str.rappend(result, nw_dds(d), ' ');
          end if;
        end if;
        
        -- добавляем единицы:
        if e > 0 then
          case i
            when 1 then   -- до 1 000 - смотрим указанный род
              case gender_
                when 'M' then
                  str.rappend(result, nw_med(e), ' ');
                when 'F' then
                  str.rappend(result, nw_fed(e), ' ');
                when 'S' then
                  str.rappend(result, nw_sed(e), ' ');
              else
                raise_application_error(-20100, 'unknown gender: "'||gender_||'"');
              end case;
            when 2 then   -- от 1 000 до 1 000 000 - женский род
              str.rappend(result, nw_fed(e), ' ');
          else    -- свыше 1 000 000 - мужской род
            str.rappend(result, nw_med(e), ' ');
          end case;
        end if;
        
        -- добавляем наименование триады:
        if i > 1 then
          tmp := i - 1;
          if short_ > 0 then
            -- краткое наименование:
            str.rappend(result, nw_tst(tmp), ' ');
          else
            -- полное наименование с учётом склонения:
            case num_declination(triada)
              when 0 then
                str.rappend(result, nw_tf0(tmp), ' ');
              when 1 then
                str.rappend(result, nw_tf1(tmp), ' ');
              when 2 then
                str.rappend(result, nw_tf2(tmp), ' ');
            end case;
          end if;
        end if;
      end if;
    end loop;
    
    return result;
  end; 
end;
/

Типы данных, на которые ссылается приведённый код.

Для работы приведённого кода, потребуются следующие типы.

create or replace type ta_number is table of number;
create or replace type ta_integer is table of integer;
create or replace type ta_vc_32 is table of varchar2(32);

Это типы для объявления массивов: целочисленного и строкового (до 32х символов – нам этого хватит).

Фрагмент пакета STR

create or replace package str is
  -- function append возвращает результат в виде left_||delim_||right_
  -- если left_ или right_ is null, то delim_ не используется 
  function append(left_ in varchar2, delim_ in varchar2, right_ in varchar2) return varchar2;
  
  -- процедуры lappend и rappend добавляют строку src_ к строке dest_ через разделитель delim_
  -- если src_ is null - dest_ не меняется
  -- если dest_ is null - результат равен src_
  -- lappend добавляет src_ слева от dest_: dest_ := src_||delim_||dest_
  -- rappend добавляет src_ справа к dest_: dest_ := dest_||delim_||src_
  procedure lappend(dest_ in out varchar2, src_ in varchar2, delim_ in varchar2 default null);
  procedure rappend(dest_ in out varchar2, src_ in varchar2, delim_ in varchar2 default null);  
end;
/
create or replace package body str is
  function append(left_ in varchar2, delim_ in varchar2, right_ in varchar2) return varchar2 is
  begin
    if left_ is null then
      return right_;
    elsif right_ is null then
      return left_;
    else
      return left_||delim_||right_;
    end if;
  end;

  procedure lappend(dest_ in out varchar2, src_ in varchar2, delim_ in varchar2) is
  begin
    if dest_ is null then
      dest_ := src_;
    elsif src_ is not null then
      dest_ := src_||delim_||dest_;
    end if;
  end;

  procedure rappend(dest_ in out varchar2, src_ in varchar2, delim_ in varchar2) is
  begin
    if dest_ is null then
      dest_ := src_;
    elsif src_ is not null then
      dest_ := dest_||delim_||src_;
    end if;
  end;
end;
/

Фрагмент пакета ARR

create or replace package arr is
  ----------------------------------------------------------------
  -- procedure init инициализирует массив:
  --   - если ещё не создан - создаёт
  --   - если указан clear_flag_, то происходит очистка массива
  ----------------------------------------------------------------
  procedure init(array_ in out nocopy ta_number, clear_flag_ in boolean default true);
  procedure init(array_ in out nocopy ta_integer, clear_flag_ in boolean default true);
  procedure init(array_ in out nocopy ta_vc_32, clear_flag_ in boolean default true);

  ----------------------------------------------------------------
  -- procedure add добавляет элемент в массив соответствующего типа
  --   для строковых аргументов происходит усечение value_, если его 
  --   длина превышает допустимый размер
  ----------------------------------------------------------------
  procedure add(array_ in out nocopy ta_number, value_ in number);
  procedure add(array_ in out nocopy ta_integer, value_ in integer);
  procedure add(array_ in out nocopy ta_vc_32, value_ in varchar2);

  ----------------------------------------------------------------
  -- procedure list_to_array рассматривает входную строку list_
  --   как набор значений, разделённых символом-разделителем delim_
  --   и преобразует список в массив;
  --   если параметр clear_flag_ = false, то исходный массив не очищается (т.е. массив можно
  --   построить на основе нескольких исходных строк)
  ----------------------------------------------------------------
  procedure list_to_array(list_ in varchar2, delim_ in varchar2,
                array_ in out nocopy ta_number, clear_flag_ in boolean default true);
  procedure list_to_array(list_ in varchar2, delim_ in varchar2,
                array_ in out nocopy ta_integer, clear_flag_ in boolean default true);
  procedure list_to_array(list_ in varchar2, delim_ in varchar2,
                array_ in out nocopy ta_vc_32, clear_flag_ in boolean default true);  

  ----------------------------------------------------------------
  -- Функции для использования в SQL
  --   эти функции не рекомендуется использовать в PL/SQL из-за копирования массивов
  ----------------------------------------------------------------
  -- to_XXX_array преобразует строку в массив
  function to_number_array(list_ in varchar2, delim_ in varchar2) return ta_number;
end;
/
create or replace package body arr is

  ----------------------------------------------------------------
  -- инициализация массива:
  ----------------------------------------------------------------
  procedure init(array_ in out nocopy ta_number, clear_flag_ in boolean default true) is
  begin
    if array_ is null then
      array_ := ta_number();
    elsif clear_flag_ then
      array_.delete;
    end if;
  end;
  procedure init(array_ in out nocopy ta_integer, clear_flag_ in boolean default true) is
  begin
    if array_ is null then
      array_ := ta_integer();
    elsif clear_flag_ then
      array_.delete;
    end if;
  end;
  procedure init(array_ in out nocopy ta_vc_32, clear_flag_ in boolean default true) is
  begin
    if array_ is null then
      array_ := ta_vc_32();
    elsif clear_flag_ then
      array_.delete;
    end if;
  end;
    
  ----------------------------------------------------------------
  -- добавление элемента в массив:
  ----------------------------------------------------------------
  procedure add(array_ in out nocopy ta_number, value_ in number) is
  begin
    array_.extend;
    array_(array_.count) := value_;
  end;
  procedure add(array_ in out nocopy ta_integer, value_ in integer) is
  begin
    array_.extend;
    array_(array_.count) := value_;
  end;
  procedure add(array_ in out nocopy ta_vc_32, value_ in varchar2) is
  begin
    array_.extend;
    array_(array_.count) := substrb(value_, 1, 32);
  end;

  ----------------------------------------------------------------
  -- процедура list_to_array рассматривает входную строку list_ как набор значений, разделённых
  -- символом-разделителем delim_ и преобразует список в массив
  -- procedure list_to_array(list_ in varchar2, delim_ in varchar2, 
  --                array_ in out nocopy ta_XXX, clear_flag_ in boolean default true)
  ----------------------------------------------------------------
  procedure list_to_array(list_ in varchar2, delim_ in varchar2,
                array_ in out nocopy ta_number, clear_flag_ in boolean default true)
  is
    dl  pls_integer;
    p1  pls_integer;
    p2  pls_integer;
  begin
    init(array_, clear_flag_);
    
    -- если строка пуста - выход
    if list_ is null then
      return;
    end if;
    
    -- dl = длина разделителя, если разделитель не указан - не страшно
    dl := length(delim_);

    -- основной цикл
    p1 := 1;
    p2 := instr(list_, delim_, p1);
    while p2 > 0 loop
      add(array_, substr(list_, p1, p2 - p1));

      p1 := p2 + dl;
      p2 := instr(list_, delim_, p1);
    end loop;
    add(array_, substr(list_, p1));
  end;
  procedure list_to_array(list_ in varchar2, delim_ in varchar2,
                array_ in out nocopy ta_integer, clear_flag_ in boolean default true)
  is
    dl  pls_integer;
    p1  pls_integer;
    p2  pls_integer;
  begin
    init(array_, clear_flag_);
    if list_ is null then
      return;
    end if;
    dl := length(delim_);

    p1 := 1;
    p2 := instr(list_, delim_, p1);
    while p2 > 0 loop
      add(array_, substr(list_, p1, p2 - p1));

      p1 := p2 + dl;
      p2 := instr(list_, delim_, p1);
    end loop;
    add(array_, substr(list_, p1));
  end;
  procedure list_to_array(list_ in varchar2, delim_ in varchar2,
                array_ in out nocopy ta_vc_32, clear_flag_ in boolean default true)
  is
    dl  pls_integer;
    p1  pls_integer;
    p2  pls_integer;
  begin
    init(array_, clear_flag_);
    if list_ is null then
      return;
    end if;
    dl := length(delim_);

    p1 := 1;
    p2 := instr(list_, delim_, p1);
    while p2 > 0 loop
      add(array_, substr(list_, p1, p2 - p1));

      p1 := p2 + dl;
      p2 := instr(list_, delim_, p1);
    end loop;
    add(array_, substr(list_, p1));
  end;

  ----------------------------------------------------------------
  -- функции для использования в SQL:  
  ----------------------------------------------------------------
  function to_number_array(list_ in varchar2, delim_ in varchar2) return ta_number is
    array_ ta_number;
  begin
    list_to_array(list_, delim_, array_);
    return array_;
  end;
end;
/

Итак

Чтобы можно было скомпилировать пакет FMT_RU, надо: создать типы для определения массивов и скомпилировать приведённые пакеты STR и ARR

Бонус

В качестве бонуса предлагаю функцию, которая форматирует число прописью на английском языке (эта функция получилась значительно проще).

create or replace package fmt_en is
  -- function number_in_words форматирут число прописью
  function number_in_words(number_ in number) return varchar2;
end;
/
create or replace package body fmt_en is
  
  s_zero_en constant varchar2(32) := 'zero';
  s_minus_en constant varchar2(32) := 'negative';
  s_hundred_en constant varchar2(32) := 'hundred';
  
  nw_eed constant ta_vc_32 := ta_vc_32('one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine');  -- единицы (от 1 до 9)
  nw_eds constant ta_vc_32 := ta_vc_32('eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen',
            'seventeen', 'eighteen', 'nineteen');  -- от 11 до 19:
  nw_dds constant ta_vc_32 := ta_vc_32('ten', 'twenty', 'thirty', 'forty', 'fifty', 'sixty',
            'seventy', 'eighty', 'ninety');  -- десятки (10, 20 .. 90)
  nw_trs constant ta_vc_32 := ta_vc_32('thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion',
            'sextillion', 'septillion', 'octillion', 'nonillion', 'decillion', 'undecillion', '');  -- триады (по короткой шкале)
  nw_triads ta_integer := ta_integer(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  
  function number_in_words(number_ in number) return varchar2
  is
    result  varchar2(4000);
    num     number(38, 0);
    tmp     number(38, 0);
    cnt     integer;
    triada  integer;
    
    e integer;
    d integer;
    h integer;
  begin
    if number_ is null then
      return null;
    elsif number_ = 0 then
      return s_zero_en;
    elsif number_ > 0 then
      num := number_;
      result := null;
    else
      num := -number_;
      result := s_minus_en;
    end if;
    
    -- разбиваем число на триады:
    cnt := 1;
    loop
      -- HINT: mod - дорогостоящая операция, поэтому здесь и далее вместо mod используется 
      -- деление и вычитание с умножением
      --nw_triads(cnt) := num mod 1000;
      tmp := trunc(num / 1000);
      nw_triads(cnt) := num - tmp * 1000;
      exit when tmp = 0;
      num := tmp;
      cnt := cnt + 1;
    end loop;
    
    --  формируем строку слева направо:
    for i in reverse 1 .. cnt loop
      triada := nw_triads(i);
      
      if triada > 0 then
        --h := trunc(triada / 100);
        --d := trunc(triada / 10) mod 10;
        --e := triada mod 10;
        h := trunc(triada / 100);
        tmp := triada - h * 100;
        d := trunc(tmp / 10);
        e := tmp - d * 10;
        
        -- добавляем сотни
        if h > 0 then
          str.rappend(result, nw_eed(h) || ' ' || s_hundred_en, ' ');
        end if;
        
        -- добавляем десятки:
        if d > 0 then
          if d = 1 and e > 0 then
            str.rappend(result, nw_eds(e), ' ');
            e := 0;
          else
            str.rappend(result, nw_dds(d), ' ');
          end if;
        end if;
        
        -- добавляем единицы:
        if e > 0 then
          str.rappend(result, nw_eed(e), ' ');
        end if;
        
        -- добавляем полное наименование триады:
        if i > 1 then
          tmp := i - 1;
          str.rappend(result, nw_trs(tmp), ' ');
        end if;
      end if;
    end loop;
    
    return result;
  end;
end;
/

Альтернативный вариант, который я предлагал ранее, хоть и проще для понимания, но работает в 2-3 раза медленнее, и у него есть ограничение: число по модулю должно быть меньше 1010. Приведённые же реализации такого ограничения не имеют: можно передавать целое число длинной до 38 знаков включительно (максимум для Oracle).

Пример использования функций.

Тестовый запрос:

select column_value num,
       fmt_en.number_in_words(column_value) in_words_en,
       fmt_ru.number_in_words(column_value) in_words_ru
  from table(arr.to_number_array(
              '-123456789,-1,0,1,12,123,1234,12345,123456,'||
              '1234567,1721057,1721058,1721300,1721423,'||
              '1721424,5373484,5373485,7777777,9999999,'||
              '12345678,20000000,77777777,123456789,200000000,'||
              '777777007,777777011,777777777,'||
              '999999999999999999999999999999999999', 
              ','))

Результат запроса:

NUM IN_WORDS_EN IN_WORDS_RU
-123456789 negative one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine минус сто двадцать три миллиона четыреста пятьдесят шесть тысяч семьсот восемьдесят девять
-1 negative one минус один
0 zero ноль
1 one один
12 twelve двенадцать
123 one hundred twenty three сто двадцать три
1234 one thousand two hundred thirty four одна тысяча двести тридцать четыре
12345 twelve thousand three hundred forty five двенадцать тысяч триста сорок пять
123456 one hundred twenty three thousand four hundred fifty six сто двадцать три тысячи четыреста пятьдесят шесть
1234567 one million two hundred thirty four thousand five hundred sixty seven один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь
1721057 one million seven hundred twenty one thousand fifty seven один миллион семьсот двадцать одна тысяча пятьдесят семь
1721058 one million seven hundred twenty one thousand fifty eight один миллион семьсот двадцать одна тысяча пятьдесят восемь
1721300 one million seven hundred twenty one thousand three hundred один миллион семьсот двадцать одна тысяча триста
1721423 one million seven hundred twenty one thousand four hundred twenty three один миллион семьсот двадцать одна тысяча четыреста двадцать три
1721424 one million seven hundred twenty one thousand four hundred twenty four один миллион семьсот двадцать одна тысяча четыреста двадцать четыре
5373484 five million three hundred seventy three thousand four hundred eighty four пять миллионов триста семьдесят три тысячи четыреста восемьдесят четыре
5373485 five million three hundred seventy three thousand four hundred eighty five пять миллионов триста семьдесят три тысячи четыреста восемьдесят пять
7777777 seven million seven hundred seventy seven thousand seven hundred seventy seven семь миллионов семьсот семьдесят семь тысяч семьсот семьдесят семь
9999999 nine million nine hundred ninety nine thousand nine hundred ninety nine девять миллионов девятьсот девяносто девять тысяч девятьсот девяносто девять
12345678 twelve million three hundred forty five thousand six hundred seventy eight двенадцать миллионов триста сорок пять тысяч шестьсот семьдесят восемь
20000000 twenty million двадцать миллионов
77777777 seventy seven million seven hundred seventy seven thousand seven hundred seventy seven семьдесят семь миллионов семьсот семьдесят семь тысяч семьсот семьдесят семь
123456789 one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine сто двадцать три миллиона четыреста пятьдесят шесть тысяч семьсот восемьдесят девять
200000000 two hundred million двести миллионов
777777007 seven hundred seventy seven million seven hundred seventy seven thousand seven семьсот семьдесят семь миллионов семьсот семьдесят семь тысяч семь
777777011 seven hundred seventy seven million seven hundred seventy seven thousand eleven семьсот семьдесят семь миллионов семьсот семьдесят семь тысяч одиннадцать
777777777 seven hundred seventy seven million seven hundred seventy seven thousand seven hundred seventy seven семьсот семьдесят семь миллионов семьсот семьдесят семь тысяч семьсот семьдесят семь
1E36 nine hundred ninety nine decillion nine hundred ninety nine nonillion nine hundred ninety nine octillion nine hundred ninety nine septillion nine hundred ninety nine sextillion nine hundred ninety nine quintillion nine hundred ninety nine quadrillion nine hundred ninety nine trillion nine hundred ninety nine billion nine hundred ninety nine million nine hundred ninety nine thousand nine hundred ninety nine девятьсот девяносто девять дециллионов девятьсот девяносто девять нониллионов девятьсот девяносто девять октиллионов девятьсот девяносто девять септиллионов девятьсот девяносто девять секстиллионов девятьсот девяносто девять квинтиллионов девятьсот девяносто девять квадриллионов девятьсот девяносто девять триллионов девятьсот девяносто девять миллиардов девятьсот девяносто девять миллионов девятьсот девяносто девять тысяч девятьсот девяносто девять

 

Конец.

Такой вот получился пост. Много кода но, надеюсь, здесь всё понятно.

P.S.: весь приведённый в этом посте код написан мною лично. Пакеты ARR и STR выложены не полностью, но представленного кода достаточно, чтобы заработали функции number_in_words. Возможно, я найду время, и расскажу, зачем эти пакеты создавались и как их можно использовать в повседневной жизни.

5 коммент.:

JayDi комментирует...

Во тут можно найти пример реализации прописи. Без кучи кода и пакетов -- всего одна небольшая функция.

http://oraclenotes.ru/?p=63

Николай Зверев комментирует...

JayDi, Вы хотя бы смотрели код, который предлагаете?.. А Вы знаете, сколько стоит работа функции replace?

Я подобных ужасных реализаций видел много, поэтому и предлагаю ОПТИМАЛЬНЫЙ (на мой взгляд - достаточно оптимальный) алгоритм, который формирует число максимально быстро.

P.S. По указанной ссылке - варианту уже несколько лет, а блог oraclenotes, к сожалению, страдает плагиатом.

JayDi комментирует...

Николай Зверев, код по ссылке работает отлично. Никаких тормозов с его стороны не видно -- как при формировании отдельных документов, так и при выводе большого количества данных.

Не вижу смысла заниматься оптимизацией кода там, где его влияние меньше 1%.

Unknown комментирует...

create or replace type ta_number is table of number;

похоже нигде не используется

Николай Зверев комментирует...

возможно, я вырезал код из пакетов и вырезал что-то лишнее

Отправить комментарий

.

.