Можно ли выразить каждое число с плавающей точкой в ​​точности как двойное число? - PullRequest
15 голосов
/ 03 ноября 2008

Может ли каждое возможное значение переменной 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).

Редактировать : оригинальная формулировка вопроса вводила в заблуждение (указывалось два способа, один из которых отвечал бы «нет», другой отвечал бы «да» для того же ответа). Я переписал его так, чтобы он соответствовал названию вопроса.

Ответы [ 11 ]

23 голосов
/ 03 ноября 2008

Да.

Доказательство перечислением всех возможных случаев:

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 бита маленькие .

5 голосов
/ 03 ноября 2008

Теоретически, такого значения нет, поэтому «да», каждое число с плавающей запятой должно быть представлено как двойное число. Преобразование из числа с плавающей запятой в двойное число должно включать в себя просто добавление четырех байтов по 00 в конце - они хранится в том же формате, только с полями разного размера.

4 голосов
/ 03 ноября 2008

Да, числа с плавающей точкой - это подмножество двойных чисел. И числа с плавающей точкой, и двойные имеют форму (знак * a * 2 ^ b). Разница между числами и числами с плавающей запятой заключается в количестве битов в a & b. Поскольку у двойников есть больше доступных битов, присвоение значения с плавающей запятой двойному эффективно означает добавление дополнительных 0 бит.

3 голосов
/ 03 ноября 2008

Если я правильно читаю спецификацию языка (и это подтверждают все остальные), такого значения нет.

То есть каждый утверждает, что он содержит только стандартные значения IEEE 754, поэтому приведение между ними не должно вызывать никаких изменений, кроме как в заданной памяти.

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

3 голосов
/ 03 ноября 2008

Как все уже сказали "нет". Но на самом деле это «да» для самого вопроса, т. Е. Каждое число с плавающей точкой может быть точно выражено в виде двойного числа. Смешение. :)

1 голос
/ 03 ноября 2008

@ Кенг: Этот код:

float a = 0.1F
println "a=${a}"
double d = a
println "d=${d}"

терпит неудачу не потому, что 0.1f не может быть точно представлен. Вопрос был «есть ли значение с плавающей запятой, которое не может быть представлено как двойное», что этот код не доказывает. Хотя 0.1f не может быть сохранено точно, значение, которое дано (которое не является точно 0.1f), может быть сохранено как двойное число (которое также не будет точно 0.1f). Предполагая, что Intel FPU, битовая комбинация для:

0 01111011 10011001100110011001101

и битовая комбинация для d:

0 01111111011 100110011001100110011010 (с последующим большим количеством нулей)

с тем же знаком, показателем степени (-4 в обоих случаях) и одинаковой дробной частью (разделенной пробелами выше). Разница в выходных данных обусловлена ​​положением второй ненулевой цифры в числе (первая - 1 после точки), которая может быть представлена ​​только двойным числом. Код, который выводит формат строки, хранит промежуточные значения в памяти и специфичен для чисел с плавающей запятой и удваивается (т.е. есть функция double-to-string и другая функция float-to-string). Если бы функция to-string была оптимизирована для использования стека FPU для хранения промежуточных результатов процесса to-string, выходные данные были бы одинаковыми для float и double, поскольку FPU использует один и тот же, больший формат (80 бит) для обоих float и двойной.

Нет значений с плавающей запятой, которые не могут быть одинаково сохранены в double, т. Е. Набор значений с плавающей запятой является подмножеством набора двойных значений.

0 голосов
/ 28 августа 2012

Если тип с плавающей точкой рассматривается как представляющий точное значение, то, как отметили другие авторы, каждое значение float представляется как double, но только несколько значений double могут быть представлены float. С другой стороны, если кто-то признает, что значения с плавающей точкой являются приближениями, он поймет, что реальная ситуация обратная. Если кто-то использует очень точный инструмент для измерения чего-либо, что составляет 3,437 мм, можно правильно описать размер как 3,4 мм. если использовать линейку для измерения объекта как 3,4 мм, было бы неправильно описывать его размер как 3 400 мм.

На вершине диапазона существуют еще большие проблемы. Существует значение float, которое представляет: «вычисленное значение превысило 2 ^ 127 на неизвестную величину», но нет значения double, которое указывает на такую ​​вещь. Преобразование «бесконечности» из одинарного в двойное приведет к значению «вычисленное значение, превышенное на 2 ^ 1023 на неизвестную величину», которое больше коэффициента googol.

0 голосов
/ 04 ноября 2008

Теоретически у каждого нормального сингла могут быть добавлены экспонента и мантисса, чтобы создать двойное число, а затем убраны отступы, и вы вернетесь к исходному синглу.

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

IEEE - ужасный формат, насколько я понимаю, он был специально разработан так, чтобы никто не мог его встретить и позволить рынку догнать Intel (это было некоторое время назад), что позволило усилить конкуренцию. Если это правда, то это не удалось, так или иначе мы застряли с этой ужасной спецификацией. Нечто подобное формату TI намного превосходит реальный мир во многих отношениях. У меня нет связи ни с компанией, ни с любым из этих форматов.

Благодаря этой спецификации очень мало, если вообще существует, каких-либо fpus (в аппаратном обеспечении или даже в аппаратном обеспечении плюс операционная система), и таких, которые часто выходят из строя в следующем поколении. (Google: TestFloat). В наши дни проблемы, как правило, заключаются в том, чтобы int плавал и плавал в int, а не в одиночку в удвоение и в удвоение к одиночке, как вы указали выше. Конечно, какую операцию fpu собирается выполнить для этого преобразования? Добавить 0? Умножить на 1? Зависит от fpu и компилятора.

Проблема с IEEE, связанная с вашим вопросом выше, состоит в том, что число может быть представлено несколькими способами, не каждое число, а много чисел может быть представлено. Если бы я хотел сломать ваш код, я бы начал с минус ноля в надежде, что одна из двух операций преобразует его в плюс ноль. Тогда я бы попробовал денормалы. И он должен потерпеть неудачу с сигнальным наном, но вы назвали это известным исключением.

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

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

0 голосов
/ 04 ноября 2008

Ответ на первый вопрос - да, ответ на «другими словами», но нет. Если вы измените тест в коде на if (!(f1 != f2)), ответ на второй вопрос станет положительным - он выведет «Успех» для всех значений с плавающей запятой.

0 голосов
/ 03 ноября 2008

Я взял код, который вы перечислили, и решил попробовать его на C ++, так как думал, что он может выполняться немного быстрее, и небезопасное приведение будет значительно проще. : -D

Я обнаружил, что для действительных чисел преобразование работает, и вы получаете точное побитовое представление после приведения. Однако для не номеров, например 1. # QNAN0 и т. Д., Результат будет использовать упрощенное представление не числа, а не точных битов источника. Например:

**** ОТКАЗ **** 2140188725 | 1. # QNAN0 - 0xa0000000 0x7ffa1606

Я бросил беззнаковое целое, чтобы плавать, затем удваивать и обратно, чтобы плавать. Число 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...