IEEE-754 double(64 位浮点数)与 long(64 位整数)的回顾

发布于 2024-10-05 18:12:35 字数 2535 浏览 6 评论 0原文

我正在重新审视一个问题(如何测试数字转换是否会改变值?),据我所知,该问题已完全解决。问题是检测特定数值何时会溢出 JavaScript 的 IEEE-754 数字类型。上一个问题是使用 C# 的,标记的答案工作得很好。

现在我正在执行完全相同的任务,但这次是在 Java 中,但它不起作用。 AFAIK,Java 使用 IEEE-754 作为其 double 数据类型。所以我应该能够来回投射它以强制损失精度,但它是往返的。对此感到困惑,我开始深入研究 Java,现在我真的很困惑。

在 C# 和 Java 中, long 的最小值和最大值是相同的:

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

据我所知,由于为指数和符号保留了固定位,这些值超出了 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!"); }

对于 Java 中的 (value = -9223372036854775808L) 返回 false

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

:对于 Java 中的 (value = -9223372036854775808L) 返回 false

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 for (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);
    }
}

I'm revisiting a question (How to test if numeric conversion will change value?) that as far as I was concerned was fully solved. The problem was to detect when a particular numeric value would overflow JavaScript's IEEE-754 Number type. The previous question was using C# and the marked answer worked perfectly.

Now I'm doing the exact same task but this time in Java and it doesn't work. AFAIK, Java uses IEEE-754 for its double data type. So I should be able to cast it back and forth to force the loss of precision but it round trips. Baffled by this I started poking deeper in Java and now I'm really confused.

In both C# and Java, the min and max values for long are the same:

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

AFAIK, these values are outside the representable numbers in IEEE-754 because of the fixed bits reserved for the exponent and sign.

// 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!"); }

This returns false for (value = -9223372036854775808L) in Java:

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

This returns false for (value = -9223372036854775808L) in 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);
}

This returns true for (value = -9223372036854775808L) but is less accurate:

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

Why does this work this way? Am I missing something like compiler optimization, e.g. is the compiler detecting my conversions and "fixing" them for me?

Edit: Adding test case by request. All three of these tests fail:

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);
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

旧伤还要旧人安 2024-10-12 18:12:35

-9223372036854775808L 可表示为 IEEE-754 双精度数。它正是 -2^63,具有双重表示形式 -1.0 x 2^63 和编码 0xc3e0000000000000

Double 能够表示比这大得多的数字。但是,它无法表示可表示数字范围内的所有整数。例如,如果您将数字加一,您将得到 -9223372036854775807 = -2^63 + 1,它不能表示为双精度值,并且将无法在往返转换中幸存。

-2^63 + 1 转换为 double 会将其舍入为最接近的可表示的 double 值,即 -2^63;转换回 long 将保留该值。

编辑:您在什么平台上进行 JavaScript 测试?在当前的 Safari 中,

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

计算结果为 True

-9223372036854775808L is representable as an IEEE-754 double-precision number. It is exactly -2^63, which has the double representation -1.0 x 2^63 and encoding 0xc3e0000000000000.

Double is capable of representing numbers much, much larger than this. However, it is not capable of representing all integers in the range of representable numbers. For example, if you add one to the number, you will get -9223372036854775807 = -2^63 + 1, which is not representable as a double-precision value, and will not survive the round-trip conversion.

Converting -2^63 + 1 to double will round it to the nearest representable double value, which is -2^63; conversion back to long will preserve that value.

Edit: What platform did you do you JavaScript test on? In current Safari,

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

evaluates as True.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文