Подтвердить что ты не робот

IEEE-754 Двойная (64-разрядная плавающая точка) по сравнению с длинным (64-разрядное целое)

Я пересматриваю вопрос (Как проверить, изменит ли числовое преобразование значение?), что, насколько я был заинтересован, было полностью решено. Проблема заключалась в обнаружении, когда определенное числовое значение переполнило бы тип кода JavaScript IEEE-754. В предыдущем вопросе использовался С#, и отмеченный ответ работал отлично.

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

В обоих С# и Java значения min и max для long одинаковы:

long MIN_VALUE = -9223372036854775808L;
long MAX_VALUE = 9223372036854775807L;

AFAIK, эти значения находятся за пределами отображаемых чисел в IEEE-754 из-за фиксированных бит, зарезервированных для показателя и знака.

// this fails in browsers that have stuck with the pure ECMAScript Number format
var str = Number(-9223372036854775808).toFixed();
if ("-9223372036854775808" !== str) { throw new Error("Overflow!"); }

Это возвращает false для (value = -9223372036854775808L) в Java:

boolean invalidIEEE754(long value) {
    try {
        return ((long)((double)value)) != value;
    } catch (Exception ex) {
        return true;
    }
}

Это возвращает false для (value = -9223372036854775808L) в Java:

boolean invalidIEEE754(long value) {
    // trying to get closer to the actual representation and
    // being more explicit about conversions
    long bits = Double.doubleToLongBits(Long.valueOf(value).doubleValue());
    long roundtrip = Double.valueOf(Double.longBitsToDouble(bits)).longValue();
    return (value != roundtrip);
}

Это возвращает true для (value = -9223372036854775808L), но менее точно:

boolean invalidIEEE754(long value) {
    return (0x0L != (0xFFF0000000000000L & (value < 0L ? -value : value)));
}

Почему это работает так? Я упускаю что-то вроде оптимизации компилятора, например. компилятор обнаруживает мои конверсии и "фиксирует" их для меня?

Изменить: Добавление тестового примера по запросу. Все три из этих тестов терпят неудачу:

import static org.junit.Assert.*;
import org.junit.Test;

public class FooTests {

    @Test
    public void ieee754One() {
        assertTrue(((long)((double)Long.MIN_VALUE)) != Long.MIN_VALUE);
    }

    @Test
    public void ieee754Two() {
        long bits = Double.doubleToLongBits(Long.valueOf(Long.MIN_VALUE).doubleValue());
        long roundtrip = Double.valueOf(Double.longBitsToDouble(bits)).longValue();

        assertTrue(Long.MIN_VALUE != roundtrip);
    }

    @Test
    public void ieee754Three() {
        long bits = Double.doubleToRawLongBits(Long.valueOf(Long.MIN_VALUE).doubleValue());
        long roundtrip = Double.valueOf(Double.longBitsToDouble(bits)).longValue();

        assertTrue(Long.MIN_VALUE != roundtrip);
    }
}
4b9b3361

Ответ 1

-9223372036854775808L есть, представляемый как номер двойной точности IEEE-754. Это точно -2^63, который имеет двойное представление -1.0 x 2^63 и кодирование 0xc3e0000000000000.

Double способен отображать числа намного, намного больше, чем это. Тем не менее, он не способен представлять все целые числа в диапазоне представимых чисел. Например, если вы добавите номер к номеру, вы получите -9223372036854775807 = -2^63 + 1, который не будет представлен как значение двойной точности, и не сможет пережить конверсию в оба конца.

Преобразование -2^63 + 1 в double будет округлять его до ближайшего представляемого двойного значения, которое равно -2^63; преобразование обратно в long сохранит это значение.

Изменить: На какой платформе вы проверили JavaScript? В текущем Safari

"-9223372036854775808" === Number(-9223372036854775808).toFixed()

оценивается как True.