Почему я получаю большие случайные значения при использовании scanf("%f", &intVar)? Как входные данные с плавающей запятой преобразуются в эти значения?

Я знаю, что использовал неправильный спецификатор формата в своей программе на C при чтении чисел с плавающей запятой в переменные int с использованием scanf("%f", &variable).

Я не знаю, как программа выдает большие случайные значения. Как входные значения преобразуются в эти случайные значения?

#include <stdio.h>
#include <stdlib.h>

// Width Height --> Floating Point

float findArea (float width, float height)
{
  float area;
  area = width* height;
  printf("width:%f\n",width);
  printf("height:%f\n",height);
  return area;
}


int main()
{
  int heightRectangle, widthRectangle;
  float area;
  printf("Enter width: ");
  scanf("%f",&widthRectangle);
  printf("Enter height: ");
  scanf("%f", &heightRectangle);
  area = findArea(widthRectangle,heightRectangle);
  printf("The area of your given rectangle is: %f \n",area);

  return 0;
}

Выход:

Enter width: 5.0
Enter height: 2.5
width:1084227584.000000
height:1075838976.000000
The area of your given rectangle is: 1166454293721513984.000000 

Размер int и float в моей системе составляет 4 байта. Чтобы определить это, я использовал оператор sizeof.

Информация о компиляторе:

Я использую онлайн-компилятор programiz

Ребята, я еще раз знаю, что использовал неправильный спецификатор формата. Я просто жду ответа, который объясняет, как значения 5.0 и 2.5 преобразуются в 1084227584 и 1075838976.

Поведение программы неопределенно из-за неправильного спецификатора формата (или, скорее, неправильного типа, с помощью которого определены ваши переменные). Это означает, что вы можете наблюдать любой результат — ничего не гарантировано и не указано. Было бы интересно узнать, что именно происходит, чтобы вы видели то, что видите; однако. имейте в виду, что с другим компилятором или с тем же компилятором в немного другой среде ваши наблюдения в принципе могут сильно отличаться.

n. m. could be an AI 07.08.2024 14:04

@JKR, печать чисел с плавающей запятой с помощью "%a" и целых чисел с помощью "%x" может облегчить просмотр общих (возможно, сдвинутых) битовых шаблонов.

chux - Reinstate Monica 07.08.2024 14:38

Поиграйте с онлайн-конвертером IEEE-754, таким как этот, чтобы увидеть, как числа с плавающей запятой представляются в двоичном формате.

Ian Abbott 07.08.2024 17:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
119
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

%f в scanf("%f",&widthRectangle); инструктирует scanf преобразовать входные данные в схему кодирования, используемую для объекта float, и сохранить биты, полученные в результате этого кодирования, в памяти, на которую указывает widthRectangle.

Поскольку в mainwidthRectangle объявлен как int, когда widthRectangle используется в findArea(widthRectangle,heightRectangle), программа интерпретирует эти биты, используя схему кодирования, используемую для int. Затем, поскольку объявлено, что findArea имеет параметры float, программа преобразует это значение в тип float и передает его в findArea.

Таким образом, значение, которое позже напечатается printf("width:%f\n",width), является результатом интерпретации кодировки 5 как float с использованием декодирования int.

Все, что мы представляем в компьютере, закодировано произвольным назначением того, какие биты представляют какое значение. Типы int и float используют разные правила для того, какие биты представляют какие значения.

Для типов int обычно используется дополнение до двух. Сначала начнем с правил для кодирования неотрицательного целого числа в двоичном формате:

Чтобы закодировать неотрицательное целое число x в w битах (w означает «ширина»):

  1. Пусть bi обозначает бит, который находится на i бит справа, начиная с b0 для самого правого бита.
  2. Для i от 0 до w−1:
    • Если x четный или нечетный, установите bi равным 0 или 1 соответственно.
    • Установите x равным x/2, усекая любую дробь.
  3. Если x не равен нулю после приведенного выше, он не помещается в w бит. Объявите об ошибке и остановитесь.

Чтобы закодировать целое число x в дополнении до двух w-битов:

  1. Если x превышает 2w−1−1 или меньше −2w−1, он не подходит. Объявите об ошибке и остановитесь.
  2. Если x отрицательное значение, присвойте x значение x + 2w.
  3. Закодируйте x в w-битном двоичном формате, как указано выше.

Для типов float используется многочастное кодирование числа с отдельными частями знака, показателя и мантиссы. Чаще всего используется форматbinary32 IEEE-754:

  1. Если x отрицательное или нет, пусть S будет битом 1 или 0 соответственно и присвоит x значение |x|.
  2. Установите показатель степени e равным 0.
  3. Пока 2 ≤ x:
    • Установите e на e+1.
    • Установите x равным x/2 (с сохранением любой дроби).
  4. Пока x < 1 и −126 < e:
    • Установите e равным e-1.
    • Установите x на 2x.
  5. Установите f равным 223•x, округленным до целого числа. (Существует выбор методов округления, которые здесь не обсуждаются.)
  6. Если 224 ≤ f, установите e равным e+1 и установите f равным f/2. (теперь f будет ровно 223, потому что это происходит только тогда, когда округление выше дает f 224.)
  7. Если 127 < e, x был слишком большим. Объявите переполнение, создайте S, объединенное с битами 1111111100000000000000000000000, которые представляют +∞ или -∞ в соответствии с S, и остановитесь.
  8. Если f < 223:
    • Пусть E — строка из восьми нулевых битов.
    • Пусть F будет результатом кодирования f в 23 бита в двоичном формате.
  9. В противном случае:
    • Пусть E будет результатом кодирования e+127 восемью битами в двоичном формате.
    • Пусть F будет результатом кодирования f−223 в 23 битах в двоичном формате.
  10. Результирующая битовая строка представляет собой объединение S, E и F.

Для x = 5 это работает:

  • На шаге 1 число 5 не является отрицательным, поэтому S устанавливается равным 0.
  • На шаге 2 e устанавливается в 0.
  • На шаге 3:
    • 5 больше 2, поэтому e увеличивается до 1, а x изменяется до 2,5.
    • 2,5 больше 2, поэтому e увеличивается до 2, а x изменяется до 1,25.
    • 1,25 не больше 2, поэтому цикл на шаге 3 останавливается.
  • На шаге 4 1,25 не меньше 1, поэтому цикл на шаге 4 не запускается.
  • На шаге 5 f устанавливается равным 223•1,25 = 10 485 760 = A0000016.
  • На шаге 6 f не больше и не равно 224, поэтому ничего не делается.
  • На шаге 7 e не больше 127, поэтому ничего не делается.
  • На шаге 8 f не меньше 223, поэтому ничего не делается.
  • На шаге 9 E устанавливается в восемь битов, полученных в результате кодирования 127+2 в двоичном формате, что представляет собой строку 10000001, а F устанавливается в результат кодирования 10 485 760 - 223 в 23 битах в двоичном формате, что равно 01000000000000000000000.
  • На шаге 10 результатом является объединение 0, 10000001 и 01000000000000000000000, что делает 01000000101000000000000000000000.

Когда битовая строка 01000000101000000000000000000000 интерпретируется как int, результат равен 1 084 227 584.

Мы также можем обратить это вспять:

В шестнадцатеричном формате 1 084 227 584 — это 40A00000. В 32-битном двоичном формате это 0 10000001 01000000000000000000000. Я вставил пробелы, чтобы показать, где мы разделяем число на части из-за схемы кодирования с плавающей запятой. Первый бит, 0, означает положительный результат. Следующие восемь битов, 10000001, представляют собой кодировку показателя степени (и один бит мантиссы, ведущую 1). В двоичном виде это будет число 129. Показатель степени с плавающей экспонентой равен этому числу минус 127, то есть оно равно 2. Последние 23 бита — это последние 23 бита мантиссы. Вместе с ведущим 1 битом они представляют двоичное число 1,01000000000000000000000, что равно 1,25 в десятичном виде. Таким образом, представленное число с плавающей запятой равно + 22 • 1,25, что равно 5.

Аналогично, 1 075 838 976 — это шестнадцатеричное число 40200000, двоичное 0 10000000 01000000000000000000000, которое отличается только тем, что поле показателя является двоичным 10000000, десятичным 128, поэтому оно представляет показатель степени 1. Представленное число с плавающей запятой равно + 21 • 1,25, то есть 2,5 .

Возможно, вы захотите упомянуть неопределенное поведение в качестве введения к вашему объяснению... ОП знает о неправильном спецификаторе преобразования и хочет получить подробное объяснение, которое вы предоставляете, но кажется важным указать, что такое поведение не гарантируется.

chqrlie 07.08.2024 14:59

@chqrlie: Нет. Вопрос явно спрашивает, почему они получают наблюдаемые значения, и «неопределенное поведение» не отвечает на этот вопрос. Это не объяснение; неопределенное поведение — это отсутствие правил, а не наличие причины. В первом предложении автор заявляет, что знает, что использовал неправильный спецификатор. Они повторяют это в предпоследнем предложении. В последнем предложении они заявляют, что просто ожидают ответа, объясняющего наблюдаемые значения. Автор явно исключил другое обсуждение, и я ответил на вопрос в соответствии с их рекомендациями.

Eric Postpischil 07.08.2024 23:57
Ответ принят как подходящий

Почему такое поведение? Числа с плавающей запятой с типами данных с плавающей запятой кодируются с использованием стандарта кодирования IEEE-754 (формат одинарной точности).

Как работает IEEE-754 в деталях?

1.What is IEEE 754?
> It's a standard for how computers represent floating-point numbers. Both positive and negative 
floating-point numbers.

2.Single Precision Basics
>Single precision means we use 32 bits (or 4 bytes) to represent a number.

3.The 32 Bits are Split Into Three Parts:

>Sign bit: 1 bit that shows if the number is positive or negative (0 for positive, 1 for negative).
>Exponent: 8 bits that help represent the range of the number.
>Mantissa (or Fraction): 23 bits that hold the actual digits of the number.

4.The Formula for the Number:

>The number is represented as: 
    [±]1.[mantissa]*2^(exponent-127)
 
>The exponent is stored with a bias of 127, which helps in handling both small and large numbers.

Дополнительную информацию о формате одинарной точности см. здесь.

ссылка:->iee-754

Объяснение:=

ширина = 5,0-> 101,0 высота=2,5->10,1

Single precision conversion of width and height
width=0 10000001 01000000000000000000000
Height=0 10000000 01000000000000000000000

Типы данных int и float используют 32-битную/4-байтовую компьютерную память для хранения данных.

Эти числа читались как числа с плавающей запятой, но интерпретировались как целые числа. Обычное целочисленное 32-битное преобразование:

Width =01000000101000000000000000000000 is 1084227584
Height=01000000001000000000000000000000 is 1075838976

ширина*высота=1166454293721513984

IEE --> IEEE (необходимо исправление в 3+ местах)

chux - Reinstate Monica 07.08.2024 14:35

Все ответы верны, но ваш ответ было легко понять. поэтому я отмечаю это как правильное.

J KR 10.08.2024 04:38

Предполагаемые базовые знания: двоичные/битовые представления целых чисел и чисел с плавающей запятой.

Это сводится к

  • bitstring(5.0) == 01000000101000000000000000000000 (base 2) == 1084227584 (base 10) и
  • bitstring(2.5) == 01000000001000000000000000000000 (base 2) == 1075838976 (base 10)

Когда вы вызываете scanf со спецификатором формата %f, он анализирует входные данные в представление IEEE 754 с одинарной точностью. Например. 5.0 будет преобразован в 01000000101000000000000000000000. В вашем случае это точное значение, хранящееся в &widthRectangle.

Но поскольку widthRectangle на самом деле является int, это битовое представление (пере)интерпретируется как целое число при переходе к findArea. Таким образом, значение в конечном итоге будет соответствующим десятичным (как в базе 10) целым числом для 01000000101000000000000000000000, то есть 1084227584.

Обратите внимание, что (как также указывали другие) использование неправильных спецификаторов формата в целом приводит к неопределенному поведению, и, следовательно, вы можете наблюдать разные выходные данные разных компиляторов. В этом случае результат оказывается «логичным», вероятно, потому, что float и int имеют одинаковый размер и выравнивание. Но опять же, на это никогда нельзя полагаться, поскольку это UB.

Другие вопросы по теме