IEEE-754 Двойной (64-битное с плавающей запятой) и Длинный (64-битное целое) - PullRequest
12 голосов
/ 03 декабря 2010

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

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

И в C #, и в 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 для (значение = -9223372036854775808L) в Java:

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

Возвращает false для (значение = -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 для (значение = -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);
    }
}

1 Ответ

6 голосов
/ 03 декабря 2010

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

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

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

Редактировать: На какой платформе вы тестировали JavaScript? В текущем Safari

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

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

...