[#20036] Re: Roundoff problem with Float and Marshal — matz@... (Yukihiro Matsumoto)

まつもと ゆきひろです

16 messages 2003/04/18
[#20045] Re: Roundoff problem with Float and Marshal — nobu.nakada@... 2003/04/20

なかだです。

[#20063] Re: Roundoff problem with Float and Marshal — matz@... (Yukihiro Matsumoto) 2003/04/22

まつもと ゆきひろです

[#20097] jcode.rb — akira yamada / やまだあきら <akira@...>

25 messages 2003/04/26
[#20098] Re: jcode.rb — matz@... (Yukihiro Matsumoto) 2003/04/27

まつもと ゆきひろです

[#20105] Re: jcode.rb — WATANABE Hirofumi <eban@...> 2003/04/28

わたなべです。

[#20108] Re: jcode.rb — matz@... (Yukihiro Matsumoto) 2003/04/28

まつもと ゆきひろです

[ruby-dev:20065] Re: Roundoff problem with Float and Marshal

From: nobu.nakada@...
Date: 2003-04-23 07:33:07 UTC
List: ruby-dev #20065
なかだです。

At Wed, 23 Apr 2003 08:26:06 +0900,
Yukihiro Matsumoto wrote:
> Rudiは[ruby-talk:69878]でstrtodがちゃんとしてればそもそも
> marshalはいじらなくてよいのでは(と彼の友達が考えた)と書いて
> ます。また、現在の実装のpowersOf10のテーブルを使うのは不正確
> だとも。実際のところどうなんでしょう。

1.0e+256を正確に表現するには仮数部が594bit必要ですから、当り前
といえばいえそうです。

> strtodの精度が上がるのはもちろん望ましいので、手が打てればそ
> れに越した方が良いのですが、私は誤差と精度についての知識はあ
> んまりないもんですから。

同じく。

> 確かにglibcのstrtodを使うとrubyが使っているものよりも正確な
> 値を出します。これは内部的にMPを使って精度を稼いでいるようで
> す。速度は重要ではないので、Bignumを使えば良いってことなのか
> なあ。

Bignum#to_fも誤差を含んでるようです。
$ ruby -e 'p 39560152763386141.to_f'
3.9560152763386144e+16

At Wed, 23 Apr 2003 14:44:14 +0900,
Yukihiro Matsumoto wrote:
> 現在の実装ではfrac1, frac2の2つのintで精度は確保できているよ
> うな気がします。要はここからdoubleへの変換がまずいと言うこと
> なのかしら。

10進小数を2進で正確に表現できないのはしょうがないので、どう丸め
るかという話になるわけですが、それがexponentによって変わってし
まうというのが問題ではないかと。

あるいは、入力のチェックを厳しくした上でシステム標準のstrtod()
を使う、というのも一つの手かも知れません。


Index: object.c
===================================================================
RCS file: /cvs/ruby/src/ruby/object.c,v
retrieving revision 1.109
diff -u -2 -p -r1.109 object.c
--- object.c	9 Apr 2003 18:33:51 -0000	1.109
+++ object.c	23 Apr 2003 04:16:30 -0000
@@ -1080,13 +1080,18 @@ rb_f_integer(obj, arg)
 }
 
-double
-rb_cstr_to_dbl(p, badcheck)
+#undef strtod
+
+static double
+rb_strtod(p, endp, badcheck)
     const char *p;
+    char **endp;
     int badcheck;
 {
     const char *q;
-    char *end;
+    char *end, *buf, *n;
+    int c, exp = 0, dot = 0;
     double d;
 
+    errno = 0;
     if (!p) return 0.0;
     q = p;
@@ -1097,19 +1102,19 @@ rb_cstr_to_dbl(p, badcheck)
 	while (ISSPACE(*p) || *p == '_') p++;
     }
-    d = strtod(p, &end);
-    if (p == end) {
-	if (badcheck) {
-	  bad:
-	    rb_invalid_str(q, "Float()");
-	}
-	return d;
+    buf = ALLOCA_N(char, strlen(p)+1);
+    n = buf;
+
+    if (*p == '-' || *p == '+') *n++ = *p++;
+    if (!strcasecmp(p, "inf")) {
+	d = (n > buf && n[-1] == '-' ? -1.0 : 0.0) / 0.0;
+	p += 3;
+    }
+    else if (!strcasecmp(p, "nan")) {
+	d = 0.0/0.0;
+	p += 3;
     }
-    if (*end) {
-	char *buf = ALLOCA_N(char, strlen(p)+1);
-	char *n = buf;
-
-	while (p < end) *n++ = *p++;
-	while (*p) {
-	    if (*p == '_') {
+    else {
+	while (c = *p) {
+	    if (c == '_') {
 		/* remove underscores between digits */
 		if (badcheck) {
@@ -1123,18 +1128,60 @@ rb_cstr_to_dbl(p, badcheck)
 		}
 	    }
-	    *n++ = *p++;
+	    else if (c == 'e' || c == 'E') {
+		if (exp++) {
+		    if (badcheck) goto bad;
+		    break;
+		}
+		c = 'e';
+	    }
+	    else if (c == '+' || c == '-') {
+		if (n == buf || n[-1] != 'e') {
+		    if (badcheck) goto bad;
+		    break;
+		}
+	    }
+	    else if (c == '.') {
+		if (dot++) {
+		    if (badcheck) goto bad;
+		    break;
+		}
+	    }
+	    else if (!ISDIGIT(c)) {
+		if (!badcheck) break;
+		while (ISSPACE(*p)) p++;
+		if (!*p) break;
+	      bad:
+		rb_invalid_str(q, "Float()");
+	    }
+	    *n++ = c;
+	    p++;
 	}
 	*n = '\0';
-	p = buf;
-	d = strtod(p, &end);
-	if (badcheck) {
-	    if (p == end) goto bad;
-	    while (*end && ISSPACE(*end)) end++;
-	    if (*end) goto bad;
-	}
+
+	d = atof(buf);
     }
+    if (endp) *endp = (char *)p;
+
+    return d;
+}
+
+double
+ruby_strtod(p, endp)
+    const char *p;
+    char **endp;
+{
+    return rb_strtod(p, endp, 0);
+}
+    
+double
+rb_cstr_to_dbl(p, badcheck)
+    const char *p;
+    int badcheck;
+{
+    double d = rb_strtod(p, 0, badcheck);
+
     if (errno == ERANGE) {
 	errno = 0;
-	rb_raise(rb_eArgError, "Float %s out of range", q);
+	rb_raise(rb_eArgError, "Float %s out of range", p);
     }
     return d;


-- 
--- 僕の前にBugはない。
--- 僕の後ろにBugはできる。
    中田 伸悦

In This Thread