Цикл for с десятичным приращением !=.5 дает странные результаты

Оригинальная идея

Я только что нашел свой старый компьютер Commodore 64, подключил его и решил снова попробовать выучить Basic. Я только что закончил главу 3, в которой демонстрируется простой цикл FOR:

10 FOR NB = 1 TO 10 STEP 1
20 PRINT NB,
30 NEXT NB

Это дает, как и ожидалось, следующее:

1       2       3       4
5       6       7       8
9       10

Знакомство с числами с плавающей запятой

Приведенный выше результат будет таким же, если для шага установлено значение 1,0. Однако другие числа, кроме 0,5, вызывают проблемы:

Если я изменяю приращение шага на любое, кроме 0,5 (или 1), я получаю странные числа с плавающей запятой, по-видимому, появляющиеся тем раньше, чем меньше установлено число с плавающей запятой. Для первого теста я изменил NB на 1 TO 40.

Результаты теста

  • FOR NB = 1 TO 40 STEP .6: нормальные результаты для 1–31, затем 31,6000001. Чтобы посмотреть, не появятся ли странные результаты дальше, я увеличил NB до 100 и снова увидел странные числа, начиная с 42: 41,2, 41,8, 42,4, 42,9999999, 43,5999999 и т. д.
  • FOR NB = 1 TO 40 STEP .4: Нормальные результаты для 1–7,4, затем 7,8000001, затем нормальные результаты 8,2–22,6, затем 22,9999999, 23,3999999 и т. д.
  • FOR NB = 1 TO 40 STEP .2: нормальные результаты для 1–6,2, затем 6,3999999 с шагом 0,2 до 8,5999999, затем изменены с 8,7999998 до 9,9999998, затем нормальные результаты с 10,2.
  • FOR NB = 1 TO 40 STEP .1: нормальные результаты для 1–3,6, затем 3,6999999 и т. д.
  • FOR NB = 1 TO 40 STEP .05: нормальные результаты для 1–2,3, затем 2,34999999 (обратите внимание на дополнительную цифру) до 2,59999999, затем 2,65–2,7, затем 2,74999999 и т. д.

Номер итерации отказа

Шаги не выполняются на следующих итерациях:

  • 0.6 increment fails at iteration
    • 52 (31.6000001),
    • 51-70 нормально,
    • тогда 71–87 будет от 0,0000001 до мало (пример: 42,9999999),
    • тогда 88–103 еще на единицу меньше (пример: 53.1999998),
    • затем число 104 и далее сокращается (например: 62,7999997).
  • 0.4 increment fails at iteration
    • 18,
    • 19–55 нормально,
    • 56–64 is at −.9999999,
    • 65 нормально,
    • 66–84 is at −.9999999,
    • 85-100 нормально,
    • 101–116 is +.0000001,
    • 117 продолжается с 0,000002 и так далее.
  • 0.2 increment fails at iteration
    • 28 at −.9999999,
    • 47–107 нормально,
    • 108–140 терпит неудачу при +0,0000001,
    • 141 и далее терпит неудачу при +0,0000002 и т. д.
    • 0,1 инкремент не работает на итерации
    • 28 at −.9999999,
    • 79–88 нормально,
    • 89–90 терпит неудачу при +0,00000001 (sic),
    • 91–116 нормально,
    • 117–187 не работает на +0,0000001,
    • 188 и выше терпит неудачу при +0,0000002 и так далее.
  • 0.05 increment fails at iteration
    • 28–33 at −.00000001,
    • 34-35 нормально,
    • 36–68 терпит неудачу при −0,00000001,
    • 69-78 нормально,
    • 79–92 не работает на +0,00000001,
    • 93–106 не работает на +0,00000002,
    • 107 и далее терпит неудачу на +0.00000003 и так далее.

Примечания к вышеизложенному

Для записи я добавил счетчик, чтобы упростить отчетность; поэтому программа выглядит так:

05 NC = 1
10 FOR NB = 1 TO 100 STEP 0.05: REM 0.6, 0.4, 0.2, 0.1, 0.05
20 PRINT NC;":";NB,
25 NC = NC + 1
30 NEXT NB

Главный вопрос

Я подозреваю, что проблема в том, как десятичное число преобразуется в двоичное, но мне кажется странным, что он отлично работает с шагом 0,5. Что вызывает эту ошибку, и как ее можно исправить или как ее объяснить? Мой Commodore работает на Basic v2.


person Canned Man    schedule 16.08.2018    source источник
comment
Добро пожаловать в дикий, причудливый, удивительный мир чисел с плавающей запятой. Это не ограничивается BASIC. Как кратко объясняется в ответе aframestor, это связано с характером работы с точностью хранения десятичных чисел как двоичных.   -  person Bill Hileman    schedule 16.08.2018
comment
(a) Подумайте, что произойдет, если вы попытаетесь увеличить число на 1/3, но ваш компьютер сможет обрабатывать только десятичные числа и только с двумя цифрами после запятой. Он не мог считать 1/3, 2/3, 1, 4/3, 5/3, 2,... Он мог считать только 0,33, 0,66, 0,99, 1,32, 1,65, 1,98,... То же самое происходит, когда вы используете двоичные числа с плавающей запятой, чтобы попытаться подсчитать десятичную дробь. Цифры немного отличаются, и ошибка увеличивается по мере развития событий. (b) Вы не всегда сразу видите ошибки, потому что вывод с плавающей запятой форматируется всего несколькими цифрами, а не показывает все значение.   -  person Eric Postpischil    schedule 16.08.2018
comment
@EricPostpischil Я предполагаю, что именно поэтому я получаю явно случайные вхождения чисел смещения, которые затем исправляются на некоторое время, а затем, наконец, после достаточного количества итераций кажутся постоянно неправильными.   -  person Canned Man    schedule 17.08.2018
comment
Возможный дубликат Почему числа с плавающей запятой неточны?   -  person Flimzy    schedule 04.02.2019


Ответы (1)


Я предполагаю, что, поскольку числа, кратные 0,5, можно легко перевести в базу 2, поэтому это не вызывает никаких проблем. Бьюсь об заклад, что если вы попробуете с шагом 0,25, он также будет работать нормально.

person aframestor    schedule 16.08.2018
comment
Но C64 четко отличает целые числа от чисел с плавающей запятой. На стр. моего руководства. 34ff, в разделе «Переменные» нам объясняют, что переменные различаются двумя символами, первая буква и ничего больше (число с плавающей запятой), процентиль (целое число) или знак доллара (строка); кроме того, на с. 35 он ограничивает целые числа от −32768 до +32767, то есть 2¹⁶ байта (хотя здесь я могу ошибаться). - person Canned Man; 16.08.2018
comment
Арифметика будет точной для разумных дробей, которые можно точно представить как «A/(2^B)» для целых чисел A и B. 0,5, 0,25, 0,75, 0,125, 0,325, 0,824... - person Patricia Shanahan; 16.08.2018
comment
Вы совершенно правы. Я только что проверил его с шагом 0,25 и 0,0625, и ошибок не было. Я полагаю, что на вторую часть моего вопроса ответят в сообщениях, посвященных двоичным вычислениям. - person Canned Man; 17.08.2018
comment
Незначительная придирка, это будет 2 ^ 16 бит (не байтов) в отношении целых значений. В более поздних поколениях BASIC реализована двойная точность (8 байтов) по сравнению с одинарной точностью C64 (с плавающей запятой) (4 байта), но существуют даже разные форматы этих двух чисел в отношении того, как они хранятся внутри. Я помню, что у меня была функция CVDMBF, которую я написал для преобразования, например, из двоичного формата Microsoft в формат двойных чисел IEEE. Тогда есть различные виды округления. Ради интереса поищите Banker's Rounding. это то, что VB6 решил использовать по умолчанию. - person Bill Hileman; 17.08.2018
comment
@BillHileman Отличный комментарий! Вы, конечно, совершенно правы в том, что это биты, а не байты (не придирки, сэр!), но мне понадобилось время, чтобы в голове это утряслось. Я бы поправил свой комментарий, если бы мог (ссылаясь на вас, конечно), но после такой задержки это недопустимо. Я предполагаю, что «банковское округление» объясняет разницу между количеством битов, доступных для чисел, и точностью n байт. - person Canned Man; 23.08.2018
comment
Банковское округление относится к методу округления полцентов. Обычно значение 0,5 всегда округляется до 1, а -0,5 всегда округляется до нуля (наибольшее целочисленное значение), но в округлении Банкера направление округления зависит от того, какая цифра слева от десятичной дроби, нечетная или четная. Я не помню точную формулу навскидку, но она может, например, округлить и 3,5, и 4,5 до трех или до трех и пяти соответственно, в зависимости от того, какой триггер (нечетный или четный). Считается, что это более справедливая система округления для банковского дела. - person Bill Hileman; 23.08.2018
comment
@CannedMan C64 BASIC различает целые (целые) числа и числа с плавающей запятой ТОЛЬКО для хранения. Все математические операции выполняются с плавающей запятой. целые числа перед любой математикой сначала преобразуются в числа с плавающей запятой; только при присвоении целочисленной переменной результат конвертируется в целочисленное значение; поэтому целые переменные медленные - person alvalongo; 29.08.2018