5.2. Ввод чисел

  •  
      Пусть десятичная запись числа подается на вход программы символ за символом. Мы хотим "прочесть" это число (поместить в переменную типа real его значение). Кроме того, надо сообщить об ошибке, если число записано неверно.

      Более конкретно, представим себе такую ситуацию. Последовательность символов на входе делится на прочитанную и оставшуюся части. Мы можем пользоваться функцией Next:char, которая даёт первый символ оставшейся части, а также функцией Move, которая забирает первый символ из оставшейся части, переводя его в категорию прочитанных.
      ---------------------|--------------------------
      прочитанная часть | Next | ? | ? | ? |
      ---------------------|--------------------------

      Будем называть десятичной записью такую последовательность символов:
      <0 или более пробелов> <1 или более цифр>

      а также такую:
      <0 или более пробелов> <1 или более цифр>.<1 или более цифр>

      Заметим, что согласно этому определению '1.', '.1', '1. 1', '-1.1' не являются десятичными записями. Сформулируем теперь задачу точно:

      5.2.1. Прочесть из входной строки максимальную часть, которая может быть началом десятичной записи. Определить, является ли эта часть десятичной записью или нет.

      Решение. Запишем программу на паскале (используя "перечислимый тип" для наглядности записи: переменная state может принимать одно из значений, указанных в скобках).
      var state:
      (Accept, Error, Initial, IntPart, DecPoint, FracPart);

      state := Initial;
      while (state <> Accept) or (state <> Error) do begin
      | if state = Initial then begin
      | | if Next = ' ' then begin
      | | | state := Initial; Move;
      | | end else if Digit(Next) then begin
      | | | state := IntPart; {после начала целой части}
      | | | Move;
      | | end else begin
      | | | state := Error;
      | | end;
      | end else if state = IntPart then begin
      | | if Digit (Next) then begin
      | | | state := IntPart; Move;
      | | end else if Next = '.' then begin
      | | | state := DecPoint; {после десятичной точки}
      | | | Move;
      | | end else begin
      | | | state := Accept;
      | | end;
      | end else if state = DecPoint then begin
      | | if Digit (Next) then begin
      | | | state := FracPart; Move;
      | | end else begin
      | | | state := Error; {должна быть хоть одна цифра}
      | | end;
      | end else if state = FracPart then begin
      | | if Digit (Next) then begin
      | | | state := FracPart; Move;
      | | end else begin
      | | | state := Accept;
      | | end;
      | end else if
      | | {такого быть не может}
      | end;
      end;

      Заметьте, что присваивания state:=Accept и state:=Error не сопровождаются сдвигом (символ, который не может быть частью числа, не забирается).

      Приведённая программа не запоминает значение прочитанного числа.

      5.2.2. Решить предыдущую задачу с дополнительным требованием: если прочитанный кусок является десятичной записью, то в переменную val:real следует поместить ее значение.

      Решение. При чтении дробной части используется переменная step - множитель при следующей десятичной цифре.
      state := Initial; val:= 0;
      while (state <> Accept) or (state <> Error) do begin
      | if state = Initial then begin
      | | if Next = ' ' then begin
      | | | state := Initial; Move;
      | | end else if Digit(Next) then begin
      | | | state := IntPart; {после начала целой части}
      | | | val := DigitValue (Next);
      | | | Move;
      | | end else begin
      | | | state := Error;
      | | end;
      | end else if state = IntPart then begin
      | | if Digit (Next) then begin
      | | | state := IntPart; val := 10*val + DigitVal(Next);
      | | | Move;
      | | end else if Next = '.' then begin
      | | | state := DecPoint; {после десятичной точки}
      | | | step := 0.1;
      | | | Move;
      | | end else begin
      | | | state := Accept;
      | | end;
      | end else if state = DecPoint then begin
      | | if Digit (Next) then begin
      | | | state := FracPart;
      | | | val := val + DigitVal(Next)*step; step := step/10;
      | | | Move;
      | | end else begin
      | | | state := Error; {должна быть хоть одна цифра}
      | | end;
      | end else if state = FracPart then begin
      | | if Digit (Next) then begin
      | | | state := FracPart;
      | | | val := val + DigitVal(Next)*step; step := step/10;
      | | | Move;
      | | end else begin
      | | | state := Accept;
      | | end;
      | end else if
      | | {такого быть не может}
      | end;
      end;

      5.2.3. Та же задача, если перед числом может стоять знак "минус" или знак "плюс" (а может ничего не стоять).

      Формат чисел в этой задаче обычно иллюстрируют такой картинкой:
      ----- ---------
      ---| + |---->-| цифра |-------->--------------------->
      | ----- | | --------- | | |
      | ----- | | | | ----- --------- |
      |-| - |--| |----<------| |-| . |->---| цифра |--|
      | ----- | ----- | --------- |
      | | |-----<-----|
      |--->----|

      5.2.4. Та же задача, если к тому же после числа может стоять показатель степени десяти, как в 254E-4 (=0.0254) или в 0.123E+9 (=123000000). Нарисуйте соответствующую картинку.

      5.2.5. Что надо изменить в программе задачи 5.2.2, чтобы разрешить пустые целую и дробную части (как в '1.', '.1' или даже '.' - последнее число считаем равным нулю)?

      Мы вернемся к конечным автоматам в главе 10 (Сравнение с образцом).