繁体   English   中英

java 中正负零的长表示与双重表示

[英]Long Representation vs Double representation of positive and negative zero in java

我想知道不同数字类型中正零和负零之间的区别。
我了解 IEEE-754 用于浮点算术和双精度位表示,因此以下内容并不令人意外

double posz = 0.0;
double negz = -0.0;
System.out.println(Long.toBinaryString(Double.doubleToLongBits(posz)));
System.out.println(Long.toBinaryString(Double.doubleToLongBits(negz)));
// output
>>> 0
>>> 1000000000000000000000000000000000000000000000000000000000000000

令我惊讶的是,我对 java 中long类型的位表示一无所知是,即使我右移(无符号>>> ),那么正零和负零的二进制表示是相同的

long posz = 0L;
long negz = -0L;
for (int i = 63; i >= 0; i--) {
    System.out.print((posz >>> i) & 1);
}
System.out.println();
for (int i = 63; i >= 0; i--) {
    System.out.print((negz >>> i) & 1);
}
// output
>>> 0000000000000000000000000000000000000000000000000000000000000000
>>> 0000000000000000000000000000000000000000000000000000000000000000

所以我想知道当我写以下内容时,java 从位表示中做了什么

long posz = 0L;
long negz = -0L;

编译器是否理解它们都是零并且忽略符号(因此将 0 分配给符号位)或者这里还有其他魔法吗?

还是这里有其他魔法?

是的。 2 的补码。

2 的补码有点神奇。 它实现了 2 个主要目标。 在开始讨论之前,让我们先讨论一下负零的概念。

负零有点奇怪。 为什么它存在?

负零实际上不是一回事。 问任何数学家“嘿,那么,负零是怎么回事?” 他们只会困惑地看着你。 这不是一回事。 在数学上,0 和 -0 完全一样。 不仅“几乎相同”,而且100%,完全,以所有可能的方式,相同 我们通常不希望我们的数字能够同时代表5.05.00 - 因为这两者是完全 100% 相同的。 如果您认为价值系统不应该浪费位试图区分5.05.00 ,那么希望能够将-0.0+0.0表示为不同的实体同样奇怪。

所以,一开始就想要-0有点奇怪。 所有的数字原语( longintshortbyte ,我猜char在技术上也是数字)都不能代表这个数字。 相反, long z = -0归结为:

  • 取常数“0”。
  • 对这个数应用'negate'运算( -是一元运算符。就像2+5使系统计算元素2和5的“加法”二元运算一样, -x使系统计算“否定”的一元运算" 在元素x上。对 0 应用否定运算会产生 0。这与写int x = 5 + 0;没有什么不同。 +0部分没有做任何事情。 -0前面的-没有做任何事情。与-0.0相比,它确实做某事(让你负零, double值,而不是正零)。
  • 将此结果存储在z中(因此,只有 0)。

没有办法判断那个减号是否存在。 它们都导致全零位,因此,计算机无法判断您是使用表达式-0还是使用+0初始化了该变量。 再次与double where 你注意到有一点不同。

那么为什么double会有它呢?

让我们稍微了解一下双打和 IEEE-754 数学的概念。

double需要 64 位。 那么,从基本的纯数学原理来看,双精度数无法表示超过 2^64 个不同的可能值,您能够打破光速或使1+1=3

然而, double旨在代表所有数字 0 到 1 之间的数字比 2^64 个选项要多得多(实际上,0 到 1 之间存在无限数量的数字),而这只是 0 到 1。

所以,双打的实际工作方式是不同的。 从整个数字行中选择几个少于 2^64 的数字。 让我们称这些为有福的数字

受祝福的人数分布不均。 您越接近1 ,存在的祝福数字就越多。 换句话说,当您远离 1 时,两个受祝福的数字之间的距离会增加。例如,如果您从 1e100(一个 1 和一百个零)开始的 go 并想要找到下一个受祝福的数字,这是一种相当多的方法. 它实际上高于 1.0! - 1e100+1实际上又是 1e100 ,因为double数学的工作方式是,在你做它们的每一个最后的数学运算之后,最终结果会四舍五入到最接近的祝福数字

让我们试试吧!

double d = 1e100;
System.out.println(d);
System.out.println(d + 1);

// prints: 1.0E100
//         1.0E100

但这意味着.. double实际上并不代表单个数字! . 任何给定的 double 所代表的实际上是这个概念:

一个未知数,其值介于 [D -, D + ] 之间,其中 D 是与此值表示的未知数接近的受祝福数,并且 是 D 与任一上最近的受祝福数之间距离的一半边。

鉴于它通常非常小,这“足够好”。 但是这种怪异确实解释了为什么如果准确性很重要(例如货币),您真的,真的不想要任何double业务。永远不要将它们存储在双倍中!)

鉴于此, -0.0代表什么? 实际上不仅仅是0。它具体表示:一个未知数,其值介于 [-, 0] 之间,其中 0 是实数零(并且 this,没有符号),并且是Double.MIN_VALUE :可表示的最小非零正数带double

这就是-0.0+0.0都存在的原因:它们实际上是不同的概念。 很少相关,但有时确实如此。 与例如long相比,其中5仅表示5而不是“在 4.5 和 5.5 之间”,因为 long 从根本上不承认小数部分首先存在。 鉴于5仅表示5 ,那么0仅表示0 ,并且首先不存在负零之类的东西

现在我们得到 2 的补码

2 的补码是一个很酷的系统。 它有两个简洁的属性:

  • 它只有一个零。
  • 出于以下操作的目的,将位序列视为有符号的 2s 补码还是无符号的并不重要:加法、减法、增量、减量、零校验。 您为实现这些操作而对位所做的修改是相同的。

它对大于、小于和除数很重要。

2 的补码是这样工作的:要对一个数取反,取所有位并将它们翻转(即对位执行 NOT 操作)。 然后,加 1。

让我们试试吧!

int x = 5;
int y = -x;
for (int i = 31; i >= 0; i--) {
    System.out.print((x >>> i) & 1);
}
System.out.println();
for (int i = 31; i >= 0; i--) {
    System.out.print((y >>> i) & 1);
}
System.out.println();

// prints 00000000000000000000000000000101
//        11111111111111111111111111111011

正如我们所见,应用了“翻转所有位并加 1”算法。

2s 补码当然是可逆的:如果你连续两次“翻转所有位并加 1”,你会得到相同的数字。

现在让我们试试-0 0是32个0位,然后全部翻转,然后加1:

 00000000000000000000000000000000
 11111111111111111111111111111111 // flip all
100000000000000000000000000000000 // add 1
 00000000000000000000000000000000 // that 1 fell off

并且因为 int 只能存储 32 位,所以最终的“1”会从末尾脱落。 我们又是零。

现在让我们用字节(稍微小一点)的 go 并尝试将 200 和 50 加在一起。

11001000 // 200 in binary
00110010 // 50 in binary
-------- +
11111010 // 250 in binary.

现在让我们改为 go:哦等等,哎呀,这是一个错误,实际上这些数字是 2s 补码。 那不是200,诺诺。 11001000 是一个位序列,实际上意味着(让我们应用“翻转所有位,加 1”方案:00111000 - 它实际上是 -56。所以该操作旨在表示“-56 + 50”。即 -6。-6二进制是(写出 6,翻转位,加 1):

00000110
11111001
11111010

嘿,现在看,没有任何变化,结果是一样的! 因此,当计算机执行x + y时,其中 x 和 y 是数字,计算机不在乎 无论 x 是“无符号数”还是“有符号的 2s 补数”,操作都是相同的。

这就是应用 2s 补码的原因。 它使数学更快。 CPU 不必费心处理符号位的分支问题。

从这个意义上说,在 java 中, intlongcharbyteshort既不是有符号的也不是无符号的更正确,它们只是。 至少对于 +、-、++ 和 -- 而言。 不, int已签名的想法基本上是System.out.println(int)的属性 - 该方法选择将位序列 11111111111111111111111111111111 呈现为“-1”而不是 4294967296。

long没有负零这样的东西。 只有floatdouble具有不同的正零和负零表示。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM