Может ли каждое возможное значение переменной float быть точно представлено в переменной double?
Другими словами, для всех возможных значений X будет успешным:
float f1 = X;
double d = f1;
float f2 = (float)d;
if (f1 == f2)
System.out.println("Success!");
else
System.out.println("Failure!");
Я подозреваю, что нет никаких исключений, или, если есть, то только для крайнего случая (например, +/- бесконечность или NaN).
Редактировать: Исходная формулировка вопроса сбивала с толку (указано два варианта, на один ответ будет «нет», на другой - «да» на тот же ответ). Я изменил его формулировку, чтобы он соответствовал названию вопроса.




Теоретически такого значения не существует, поэтому «да», каждое число с плавающей запятой должно быть представлено как двойное. Преобразование из числа с плавающей запятой в двойное должно включать в себя просто прикрепление четырех байтов 00 на конце - они сохраняются с использованием тот же формат, только с полями разного размера.
Вставка 32 0 бит, да. Вставляя их все в конце, нет. Некоторые добавляются к мантиссе, некоторые - к показателю степени.
На самом деле, я солгу, показатель степени, конечно, не расширен до 0, потому что смещение другое в двойном. Таким образом, преобразование требует небольшого количества фактических арифметических действий.
Это почти правильный ответ - тип double - это не просто 32-битное расширение типа float. Поле экспоненты фактически расширено с 8 до 11 бит, поэтому для расширения поля мантиссы осталось всего 29 бит, а не 32. (Да, я заметил, что обсуждение немного устарело, но ради будущих поколений ... )
Да, поплавки - это подмножество двойников. И поплавки, и двойники имеют форму (знак * a * 2 ^ b). Разница между числами с плавающей запятой и числами типа double - это количество бит в a и b. Поскольку для чисел типа double доступно больше бит, присвоение значения с плавающей запятой для типа double фактически означает вставку дополнительных 0 бит.
Как уже все сказали «нет». Но на самом деле это «да» на сам вопрос, т.е. каждое число с плавающей запятой может должно быть точно выражено как double. Сбивает с толку. :)
Если я правильно читаю спецификация языка (и, как все подтверждают), такого значения нет.
То есть каждый утверждает, что содержит только стандартные значения IEEE 754, поэтому приведение между ними не должно вызывать никаких изменений, за исключением заданной памяти.
(пояснение: не будет никаких изменений, пока значение будет достаточно маленьким, чтобы его можно было удерживать в float; очевидно, если бы значение было слишком много битов, чтобы удерживаться во float, чтобы начать, приведение от double к float привело бы к потеря точности.)
@Mitch: приведение из Double в Float гарантированно приведет к потере точности. Вы можете выполнить float -> double -> float и получить тот же ответ. Но если у вас есть двойное значение, которое является результатом некоторых вычислений, его нельзя преобразовать в Float без сброса битов.
Снарк:NaN будут сравниваться по-разному после (или даже до) преобразования.
Однако это не отменяет уже данных ответов.
Я взял код, который вы указали, и решил попробовать его на C++, так как думал, что он может выполняться немного быстрее и что значительно проще выполнить небезопасное приведение. :-D
Я обнаружил, что для действительных чисел преобразование работает, и вы получаете точное побитовое представление после преобразования. Однако для нечисловых, например 1. # QNAN0 и т. д., В результате будет использоваться упрощенное представление нечислового числа, а не точные биты источника. Например:
**** FAILURE **** 2140188725 | 1.#QNAN0 -- 0xa0000000 0x7ffa1606
Я привел unsigned int к float, затем к удвоению и обратно к float. Число 2140188725 (0x7F90B035) приводит к NAN, и преобразование в удвоение и обратно по-прежнему является NAN, но не точный той же NAN.
Вот простой код на C++:
typedef unsigned int uint;
for (uint i = 0; i < 0xFFFFFFFF; ++i)
{
float f1 = *(float *)&i;
double d = f1;
float f2 = (float)d;
if (f1 != f2)
printf("**** FAILURE **** %u | %f -- 0x%08x 0x%08x\n", i, f1, f1, f2);
if ((i % 1000000) == 0)
printf("Iteration: %d\n", i);
}
Напомним, что любое значение NaN не равно все. Таким образом, вы можете определить NaN с помощью кода типа «if (a! = A) {/ * have NaN * /}».
«Я привел беззнаковое int к float» - технически вы этого не сделали, вы привели int * к float * и разыменовали его. Приведение типа int к float выполняет числовое преобразование.
@KenG: Этот код:
float a = 0.1F
println "a=${a}"
double d = a
println "d=${d}"
терпит неудачу не потому, что 0.1f не может быть точно представлен. Вопрос заключался в том, «существует ли значение с плавающей запятой, которое нельзя представить как двойное», чего этот код не доказывает. Хотя 0,1f не может быть сохранено точно, значение, которое задано (что не совсем 0,1f), может быть сохранено как двойное (что также не будет точно 0,1f). Предполагая Intel FPU, битовый шаблон для a:
0 01111011 10011001100110011001101
и битовый шаблон для d:
0 01111111011 100110011001100110011010 (followed by lots more zeros)
который имеет одинаковый знак, показатель степени (-4 в обоих случаях) и одинаковую дробную часть (разделенные пробелами выше). Разница в выводе связана с положением второй ненулевой цифры в числе (первая - это 1 после точки), которая может быть представлена только двойным числом. Код, который выводит строковый формат, сохраняет промежуточные значения в памяти и относится к числам с плавающей запятой и двойным числом (т.е. есть функция двойного преобразования в строку и еще одна функция преобразования в строку). Если бы функция преобразования в строку была оптимизирована для использования стека FPU для хранения промежуточных результатов преобразования в строку, выходные данные были бы одинаковыми для чисел с плавающей запятой и двойной точности, поскольку FPU использует один и тот же более крупный формат (80 бит) для обоих значений с плавающей запятой. и двойной.
Не существует значений с плавающей запятой, которые нельзя сохранить идентично в double, т.е. набор значений с плавающей запятой является подмножеством набора значений типа double.
да.
Доказательство перечислением всех возможных случаев:
public class TestDoubleFloat {
public static void main(String[] args) {
for (long i = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i++) {
float f1 = Float.intBitsToFloat((int) i);
double d = (double) f1;
float f2 = (float) d;
if (f1 != f2) {
if (Float.isNaN(f1) && Float.isNaN(f2)) {
continue; // ok, NaN
}
fail("oops: " + f1 + " != " + f2);
}
}
}
}
заканчивается за 12 секунд на моей машине. 32 бита - это маленький.
На самом деле это не проверяет все числа, представленные с помощью Floats; Поплавки не могут точно представлять целые числа больше 2 ^ 23 или около того.
Он перечисляет все возможные числа с плавающей запятой, перечисляя все целые числа (которые имеют одинаковый размер) и преобразовывая их битовые шаблоны в числа с плавающей запятой.
Если (float) «INF» интерпретируется как «вычисление более 2 ^ 127», эквивалентного double не существует. Преобразование такого значения float в double приведет к «вычислению, превышающему 2 ^ 1023». Только на сотни или порядки.
Ответ на первый вопрос - да, ответ на «другими словами» - нет. Если вы измените тест в коде на if (!(f1 != f2)), ответом на второй вопрос станет да - он напечатает «Успех» для всех значений с плавающей запятой.
Теоретически каждый нормальный сингл может иметь заполнение экспоненты и мантиссы, чтобы создать двойное, а затем удалить заполнение, и вы вернетесь к исходному синглу.
Когда вы переходите от теории к реальности, у вас возникают проблемы. Не знаю, интересовались ли вы теорией или реализацией. Если это реализация, то вы можете быстро попасть в беду.
IEEE - ужасный формат, насколько я понимаю, он был специально разработан, чтобы быть настолько жестким, чтобы никто не смог с ним справиться и позволить рынку догнать информацию (это было некоторое время назад), что повысило конкуренцию. Если это правда, это не удалось, в любом случае мы застряли с этой ужасной спецификацией. Что-то вроде формата TI намного превосходит реальный мир во многих отношениях. Я не имею отношения ни к одной компании, ни к какому-либо из этих форматов.
Благодаря этой спецификации очень мало (если вообще есть) fpus, которые действительно соответствуют ей (аппаратно или даже аппаратно плюс операционная система), а те, которые действительно не работают, в следующем поколении. (Google: TestFloat). Проблемы в наши дни, как правило, заключаются в том, что int для float и float в int, а не в single to double и double на single, как вы указали выше. Конечно, какую операцию будет выполнять фпу для этого преобразования? Добавить 0? Умножить на 1? Зависит от фпу и компилятора.
Проблема с IEEE, связанная с вашим вопросом выше, заключается в том, что число может быть представлено более чем одним способом, не каждое число, а много чисел. Если бы я хотел сломать ваш код, я бы начал с минус нуля в надежде, что одна из двух операций преобразует его в плюс ноль. Тогда я бы попробовал денормальные значения. И он должен выйти из строя с сигнальным nan, но вы вызвали это как известное исключение.
Проблема в том, что знак равенства, вот правило номер один о плавающей запятой, никогда не используйте знак равенства. Equals - это битовое сравнение, а не сравнение значений, если у вас есть два значения, представленные по-разному (например, плюс ноль и минус ноль), битовое сравнение не удастся, даже если это то же самое число. Больше и меньше чем делается в fpu, равно делается с целым числом alu.
Я понимаю, что вы, вероятно, использовали эквивалент для объяснения проблемы, а не обязательно код, который вы хотели добиться или потерпеть неудачу.
== - это не сравнение битов. В C, C++, C#, Java, Javascript и т. д. 0 == отрицательное значение 0. Метод Double.equals () выполняет побитовое сравнение.
-1 за пропаганду анти-IEEE-float с вводящей в заблуждение и откровенно неверной информацией.
@Р. Я расскажу вам, что нужно сделать, и создайте с нуля несколько фпу, которые проходят TestFloat уровня 3 и соответствуют спецификации IEEE в аппаратном обеспечении без программных клуджей (как и большинство из имеющихся на рынке), а затем давайте поговорим о том, как построить лучший фпу с лучшая спецификация.
Самый большой недостаток, который я вижу в спецификации IEEE, заключается в том, что с числовой точки зрения ноль, полученный вычитанием двух равных чисел, не должен быть положительным. Выражение 1.0 / (1.0-1.0) должно давать NaN, а не + INF. Если кто-то желает, чтобы 1 / (1 / + INF) давало + INF, а не NaN, должны быть положительные, отрицательные и беззнаковые нули (первые два генерируются умножениями или делениями, которые не переполняются, а последний - вычитанием равных чисел). Если бы я разрабатывал спецификацию, я бы подумал об использовании другого подхода к ...
... обеспечение того, чтобы наименьший интервал между числами был не меньше наименьшего представимого числа, но я не думаю, что назвал бы спецификацию "ужасной". Что бы вы сделали по-другому?
Теперь я не так хорошо знаком с самой последней спецификацией, как та, что была 10 лет назад, четырнадцать страниц ада. Результат операции меняется, например, в зависимости от режима. Разделение на ноль дает разные ответы, если у вас включены исключения или нет, я, кажется, помню. Режимы округления, nans, плюс и минус ноль, правила исключений и правила отсутствия исключений, крошечные числа и т. д., Все просто накапливается. С другой стороны, при использовании формата ti dsp один ноль, без денормальных значений без nans, одно правило деления на ноль, без округления и т. д. Было тривиально, заняло неделю
Что больше всего беспокоит, так это то, что программисты языков высокого уровня не знают достаточно о плавающей запятой, чтобы использовать ее должным образом, добавляется много ненужных ошибок, они могли бы просто использовать фиксированную точку и были бы так же счастливы. Практически ни одна из функций IEEE не используется, 90% используют несколько процентов таких функций. просто подумайте о фишках, которые тратятся впустую. вы хотите играть в крутые видеоигры получите видеокарту с кучей видеопроцессоров, вы хотите проводить научные исследования получите настоящий математический процессор
вы хотите вычислить пиксели для шрифта размером 12 пунктов, использовать минимальный, тупой, дешевый, быстрый, fpu или просто использовать фиксированную точку.
Согласно теории заговора / историям старых жен, на рынке процессоров начала доминировать разведка. и в страхе перед их доминированием на рынке сопроцессоров с плавающей запятой компании собрались вместе, чтобы создать спецификацию, и создать спецификацию, настолько трудную для выполнения, что либо никто не встретит ее (особенно Intel), либо, по крайней мере, все они имели хорошие шансы попасть туда. . Intel все равно победила, и мы застряли на этой спецификации и ее производных. Я был рядом, но недостаточно взрослый, чтобы знать, есть ли в этом что-нибудь вообще, но я был в команде fpu.
Если тип с плавающей запятой рассматривается как представляющий точное значение, то, как отмечали другие плакаты, каждое значение float может быть представлено как double, но только несколько значений double могут быть представлены как float. С другой стороны, если кто-то признает, что значения с плавающей запятой являются приблизительными, он поймет, что реальная ситуация обратная. Если использовать очень точный инструмент для измерения чего-то, что составляет 3,437 мм, можно правильно описать его размер как 3,4 мм. Если использовать линейку для измерения объекта как 3,4 мм, было бы неправильно описывать его размер как 3,400 мм.
На вершине диапазона существуют еще более серьезные проблемы. Существует значение float, которое представляет: «вычисленное значение превысило 2 ^ 127 на неизвестную величину», но нет значения double, которое указывает на такую вещь. Преобразование «бесконечности» от одинарного к двойному даст значение «вычисленное значение, превышающее 2 ^ 1023 на неизвестную величину», которое не соответствует коэффициенту более чем в гугол.
Я не согласен с тем, что «значения с плавающей запятой являются приблизительными». Все числа с плавающей запятой IEEE представляют собой точное значение по основанию 2. Они часто являются приближениями чисел с основанием 10, которые не могут быть выражены точно с основанием 2, но каждое из них имеет точное значение.
@Kip: Поведение чисел точно указано, но я думаю, что более полезно рассматривать 1.1f как представление «1+ (1,677,722 ± ½ / 16,777,216)», чем точное количество «1+ (1,677,722 / 16,777,216)». Если это точное количество, то почему вывод вышеуказанного числа дает «1,1», а не «1,10000002384185791015625»? Если рассматривать число как «что-то между 0,0999999940395355224609375 и 0,1000000536441802978515625», то становится ясно, что эти дополнительные цифры за строкой нулей не имеют смысла. Но если рассматривать это как точную дробь, да.
Если вычислить (11.0 / 10.0) и передать результат в float, результатом будет правильное представление float для дроби 11/10. Если вычислить (11.0f / 10.0f) и преобразовать результат в double, результат не будет правильным представлением double для дроби 11/10. Преобразование double в float теряет точность, но сохраняет правильность. Преобразование float в double не теряет числовой точности, но часто теряет правильность.
пожалуйста, уточните «нет» - два вопроса в исходном сообщении противоречат друг другу, поэтому я не могу сказать, на что вы отвечаете.