Разве «int i = * (short *) & float» не должен сохранять знак? (С++)

Следующий код Python3+ пытается скомпилировать сценарий Cpp и использовать его для преобразования из числа с плавающей запятой в целое число, оставляя память нетронутой; это выглядит следующим образом:

import sys, os
import numpy as np
import matplotlib.pyplot as plt 

# Build a C++ Script that accepts a str,
# converts it to a float, and prints 
# the result of the operation
def build():
    script = """
#include<iostream>

int main(int argc, char *argv[]){

  float f = std::stof(argv[1]);
  int i = *(short *)&f;


  std::cout << f << " " << i <<std::endl;

  return 0;

}
    """
    with open('script.cpp', 'w') as f:
        f.write(script)

    return 1

# Loads the results from the C++ script
def load_results():
    x,y = [],[]
    with open('results-ctest.txt', 'r') as f:
        result = f.readlines()
    for _ in result:
        local = _.split(' ')
        x.append(float(local[0]))
        y.append(int(local[1][:-2]))

    return x,y

# Plots the results from the C++ script
def show_results(x,y):
    # Define a figure
    f,ax = plt.subplots()

    # Plot results
    ax.scatter(x,y)

    # Format the axis according to the shown figure
    ax.set_xticks(np.linspace(min(x), max(x), 20))
    ax.set_yticks(np.linspace(min(y), max(y), 20))
    plt.show()

if __name__=='__main__':

    # build the C++ script
    build()

    # Compile the C++ script
    # and clean the previous results
    # by removing "results-ctest.txt"
    os.system(f'g++ script.cpp')
    os.system('rm results-ctest.txt')

    # Generate 500 floats between -1.000.000 and 1.000.000
    # and pass them to the C++ script
    numbers=np.linspace(-1e6, 1e6, 500)
    for number in numbers:
        os.system(f'./a.out {number}>> results-ctest.txt')

    # Open the results of the C++ script and 
    # split the input from the output
    x,y = load_results()

    # Produce the figure and open
    # a window for it
    show_results(x,y)

Очевидная проблема заключается в том, что (выходные) целые числа против (входных) чисел с плавающей запятой следующие:

Тем не менее, если и "int", и "float" реализованы с 4 байтами согласно следующему рисунку, тогда вход и выход должны иметь одинаковый знак.

Резюме состоит в том, что то, что следует ниже, используется для создания int с использованием C++, и его знак не сохраняется в соответствии с тем, что показано на первом рисунке.

  float f = std::stof(argv[1]);
  int i = *(short *)&f;

Спасибо


Обновлено: Суть в том, что была опечатка. Я редактирую вопрос, чтобы показать «правильный» сюжет

Как указано в комментариях, проблема заключалась в следующей строке:

int i = *(short *)&f;

что должно было быть:

int i = *(int *)&f;

и, таким образом, получить следующую диаграмму:

short скорее всего 2 байта, а не 4.
Bill Lynch 25.12.2020 21:55
int i = *(short *)&f; -- Строгое правило алиасинга
PaulMcKenzie 25.12.2020 21:59

Билл Линч дал реальный ответ на ваш вопрос. Кроме того, вы вызываете неопределенное поведение, см., например. en.cppreference.com/w/c/language/object#Strict_aliasing. Если вы хотите добиться того же, но без неопределенного поведения, используйте memcpy.

Cereal 25.12.2020 22:01

Это не то, как вы конвертируете числа с плавающей запятой в целые числа в С++. Какой учебник C++ научил вас этому? Что бы это ни было, вам нужен лучший учебник C++.

Sam Varshavchik 25.12.2020 22:06

@SamVarshavchik Я сомневаюсь, что речь идет о числовом преобразовании, я думаю, он действительно хочет посмотреть, как байтовое представление чисел с плавающей запятой будет отображаться в целые числа.

Cereal 25.12.2020 22:10

Какой TF ваш вопрос о Python? Пожалуйста, убедитесь, что вы сократили код в своих вопросах до минимального воспроизводимого примера , прежде чем спрашивать здесь! Как новый пользователь, также пройдите тур и прочитайте Как спросить.

Ulrich Eckhardt 25.12.2020 22:26

Краткий поверхностный ответ — это то, на что указали BillLynch и PaulMcKenzie: в преобразовании есть опечатка (см. редактирование). Вопрос к опытным пользователям: стоит ли исправлять опечатку в заголовке? Думаю, нет.

Gaston 26.12.2020 15:23

@UlrichEckhardt Я отредактировал его, чтобы сделать код более читаемым. Я надеюсь, что теперь качество кода лучше соответствует правилам сайта.

Gaston 26.12.2020 15:38
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
8
102
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Первая проблема с вашим кодом заключается в том, что это неопределенное поведение при чтении данных одного типа как другого несвязанного типа без определенных свойств, таких как общий префикс, или с использованием нескольких типов, таких как std::byte или char.

std::bit_cast — это c++20 способ сделать это правильно.

Вторая проблема заключается в том, что то, что означают точные биты, зависит от таких вещей, как порядок следования байтов на вашем компьютере и используемый вами стандарт операций с плавающей запятой. Сейчас они относительно стандартны, но не полностью.

Ваша третья проблема заключается в том, что размер short, int и float зависит от платформы и компилятора. Вы можете использовать целочисленные типы фиксированного размера, например std::int32_t, и вам следует использовать их вместо int или short. Часто short — 16 бит, а int — 32, но это далеко не универсально. float быть 32-битным очень часто.

Так:

std::int32_t i = std::bit_cast<std::int32_t>(f);

std::cout << f << " " << i <<std::endl;

по крайней мере избавится от большинства безумных проблем.

Я не знаю навскидку, каковы проблемы преобразования порядков байтов при преобразовании из с плавающей запятой в целые числа. Что бы я хотел, так это:

std::uint32_t ui = std::bit_cast<std::uint32_t>(f);
std::cout << f << " 0b";
for (int i = 0; i < 32; ++i)
  std::cout << (ui&(1<<i));

std::int32_t i = std::bit_cast<std::int32_t>(f);
std::cout << " " << i <<std::endl;

чтобы также выгрузить биты f в соответствии с порядком байтов вашей архитектуры. Затем возьмите значение с плавающей запятой, чье битовое представление вам известно (и оно не равно нулю), и посмотрите, что оно генерирует.

Это отличный комментарий, который расширяет опечатку (пожалуйста, прочитайте редактирование) и добавляет больше информации. Я приму это, надеясь, что вы найдете время, чтобы добавить упоминание о том, как int i = *(int *)&f сохраняет знак, вероятно, в большинстве реализаций vainilla, насколько я понимаю. Спасибо

Gaston 26.12.2020 15:05

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