NaN拳击的目的是什么?


44

阅读21世纪C时,我到达了第6章的“用NaNs标记异常数值”一节,其中解释了使用尾数中的位来存储一些任意位模式,将它们用作标记或指针的方法(书中提到WebKit使用了这种技术)。

我不确定我是否了解这种技术的效用,我认为这是一种hack(它依赖于硬件而不是NaN中尾数的值),但是来自我不习惯的Java背景C的粗糙度

这是在NaN中设置和读取标记的代码段

#include <stdio.h>
#include <math.h> //isnan

double ref;

double set_na(){
    if (!ref) {
        ref=0/0.;
        char *cr = (char *)(&ref);
        cr[2]='a';
    }
    return ref;
}

int is_na(double in){
    if (!ref) return 0;  //set_na was never called==>no NAs yet.

    char *cc = (char *)(&in);
    char *cr = (char *)(&ref);
    for (int i=0; i< sizeof(double); i++)
        if (cc[i] != cr[i]) return 0;
    return 1;
}

int main(){
    double x = set_na();
    double y = x;
    printf("Is x=set_na() NA? %i\n", is_na(x));
    printf("Is x=set_na() NAN? %i\n", isnan(x));
    printf("Is y=x NA? %i\n", is_na(y));
    printf("Is 0/0 NA? %i\n", is_na(0/0.));
    printf("Is 8 NA? %i\n", is_na(8));
}

它打印:

Is x=set_na() NA? 1
Is x=set_na() NAN? 1
Is y=x NA? 1
Is 0/0 NA? 0
Is 8 NA? 0

JSValue.h上, webkit解释了编码,但没有解释为什么使用它。

该技术的目的是什么?空间/性能的好处是否足够高,可以平衡其骇人听闻的特性?


你能提供一个简单的例子吗?
2013年

需要明确的是,OP正在询问在哪里可以使用信号NaN
棘手怪胎

1
@ratchetfreak,你怎么认为呢?
2013年

@ratchetfreak:问题不是关于信号NaN的,正如Webkit JSValue.h解释的那样,但是感谢您让我发现新东西!
andijcr 2013年

1
@Hudson isnan()si在主要的第二个printf中使用。is_an()的目的是测试double输入中的位模式是否等于ref全局变量内部保存的位模式。
andijcr

Answers:


63

在实现动态类型的语言时,必须具有可以容纳任何对象的单一类型。为此,我知道三种不同的方法:

首先,您可以传递指针。这就是CPython实现所要做的。每个对象都是一个PyObject指针。这些指针被传递,通过查看PyObject结构中的细节以找出类型来执行操作。

缺点是像数字之类的小值存储为盒装值,因此,您的小数5会作为内存块存储在某处。因此,这引出了Lua使用的联合方法。代替a PyObject*,每个值都是一个结构,其中一个字段指定类型,然后是所有不同支持类型的并集。这样,我们避免为小值分配任何内存,而是将它们直接存储在联合中。

NaN方法将所有内容存储为两倍,并将未使用的部分重新NaN用于额外的存储。与union方法相比,优点是我们保存了type字段。如果它是有效的double,则为double,否则尾数为指向实际对象的指针。

请记住,这是每个javascript对象。每个变量,对象中的每个值,每个表达式。如果我们可以将所有这些位从96位减少到64位,那将是非常令人印象深刻的。

值得一试吗?回想一下,对高效Javascript有很多需求。Javascript是许多Web应用程序中的瓶颈,因此使其速度更快是一个更高的优先级。出于性能原因,引入一定程度的入侵是合理的。在大多数情况下,这不是一个好主意,因为它引入了一定程度的复杂性而几乎没有收益。但是在这种特定情况下,值得在内存和速度方面进行改进。


2
实际上,CPython会缓存少量数字。参见hg.python.org/cpython/file/e6cc582cafce/Objects/longobject.c
菲利普·

1
@cpcloud,是的,但是这个细节似乎无关紧要。
2013年

1
@WinstonEwert你是对的。读完我写的东西后,我也想同样的事情。
菲利普·

2
使用原始类型的位来避免“装箱”所有值是一种历史悠久的技术。Smalltalk在1970年代使用它,从16位整数中窃取一位,以信号通知对象指针或15位SmallInteger
乔纳森·尤妮丝

2
@JonathanEunice,真的吗?这让我感到惊讶,因为我真的不愿意放弃16位的范围。
Winston Ewert

7

使用NaN作为“异常值”是一种众所周知的,有时有用的技术,可以避免使用额外的布尔变量this_value_is_invalid。明智地使用它可以帮助人们在不进行任何性能折衷的情况下使代码更简洁,更简洁,更易读。

当然,此技术有一些陷阱(请参阅http://ppkwok.blogspot.co.uk/2012/11/java-cafe-1-never-write-nan-nan_24.html),但使用Java(或非常相似的C#),有一些标准的库函数Float.isNaN可以简化NaN的处理。当然,在Java中,您可以选择使用Floatand Double类,在C#中可以使用可为空的值类型float?double?,从而使您可以使用nullNaN代替无效的浮点数,但是这些技术会对性能和内存产生重大负面影响程序的使用情况。

在C语言中,NaN的使用不是100%可移植的,这是正确的,但是您可以在有IEEE 754浮点标准可用的任何地方使用它。AFAIK这几乎是当今几乎所有主流硬件(或者至少大多数编译器的运行时环境都支持它)。例如,此SO帖子包含一些信息,以查找有关在C中使用NaN的更多详细信息。


Java中的自动装箱很混乱,应该避免,仅使用它来提供空值就很荒谬并且容易出现错误
棘手怪胎

我编辑了问题,以链接到Webkit在哪里使用NaN装箱。似乎Webkit除了
标志

2
@ratchetfreak:这当然支持我的观点
布朗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.