Feature #3961
closedprintfと精度指定と負の値と
Description
=begin
printf が、フォーマットに精度指定しつつ負の値を与えた時の挙動が、
Perl と必然性無く異なっていて不便です。
% ./ruby -e'printf("%#.8x", -1)'
0x..ffffff
そもそもこの挙動は C 言語由来で、C の場合例えば以下の通りになります。
% cat test.c
#include <stdio.h>
int main(void)
{
printf("%#x\n", -1);
return 0;
}
% cc t.c && ./a.out
0xffffffff
また、おそらく直接参考にしたであろう Perl では以下の通りです、
% perl -e'printf("%#x",-1)'
0xffffffffffffffff
% perl -e'printf("%#.30x",-1)'
0x00000000000000ffffffffffffffff
つまり、Perl (の 64bit int 版) では、64bitで補数を取っています。
さて、Ruby の場合多倍長整数が組み込みなため、補数を取ると無限に続いてしまうから、
% ./ruby -e'printf("%#x",-1)'
0x..f
と .. で略すのは理にかなっていると思います。
しかし、現在の Ruby は精度を指定した際にも..がついてしまうので、
Perl のような動きを実現させる事ができません。
% ./ruby -e'printf("%#.8x",-1)'
0x..ffffff
で、この .. っていらないと思うんです。
例えば浮動小数点数の場合には以下のように .. とか付けずにぶった切る訳で、
無限大方向と無限小方向という違いはあれど、
「Perlとかと同じ挙動にできない」というデメリットの方が大きいのではないでしょうか。
% ./ruby -e'printf("%#.8f",10.0/3)'
3.33333333
わたしの場合、CRuby 側の inspect が printf("%x", negative_value) などとしている部分の動作を
RubySpec で Ruby で書く時に頭を抱えてしまいました。
というわけでパッチは例えば以下の通りです。
diff --git a/sprintf.c b/sprintf.c
index 21509ea..0e97955 100644
--- a/sprintf.c
+++ b/sprintf.c
@@ -844,7 +844,7 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt)
}
else {
s = nbuf;
-
if (v < 0) {
-
if (v < 0 && !(flags & FPREC0)) { dots = 1; } snprintf(fbuf, sizeof(fbuf), "%%l%c", *p == 'X' ? 'x' : *p);
@@ -892,7 +892,8 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt)
tmp1 = tmp = rb_big2str0(val, base, RBIGNUM_SIGN(val));
s = RSTRING_PTR(tmp);
if (*s == '-') {
-
dots = 1;
-
if (!(flags & FPREC0))
-
dots = 1; if (base == 10) { rb_warning("negative number for %%u specifier"); }
@@ -925,14 +926,11 @@ rb_str_format(int argc, const VALUE argv, VALUE fmt)
}
}
if (prefix && !prefix[1]) { / octal */
-
if (dots) {
-
prefix = 0;
-
}
-
else if (len == 1 && *s == '0') {
-
if (len == 1 && *s == '0') { len = 0; if (flags & FPREC) prec--; }
-
else if ((flags & FPREC) && (prec > len)) {
-
else if ((flags & FPREC) && (prec > len) && v >= 0) { prefix = 0; } }
diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb
index 96a1b62..24b6a34 100644
--- a/test/ruby/test_sprintf.rb
+++ b/test/ruby/test_sprintf.rb
@@ -24,12 +24,12 @@ class TestSprintf < Test::Unit::TestCase
assert_equal("0000", sprintf("%.4b", 0))
assert_equal("0001", sprintf("%.4b", 1))
assert_equal("0010", sprintf("%.4b", 2))
- assert_equal("..11", sprintf("%.4b", -1))
-
assert_equal("1111", sprintf("%.4b", -1))
assert_equal(" 0000", sprintf("%6.4b", 0))
assert_equal(" 0001", sprintf("%6.4b", 1))
assert_equal(" 0010", sprintf("%6.4b", 2))
- assert_equal(" ..11", sprintf("%6.4b", -1))
-
assert_equal(" 1111", sprintf("%6.4b", -1))
assert_equal(" 0", sprintf("%#4b", 0))
assert_equal(" 0b1", sprintf("%#4b", 1))
@@ -44,12 +44,12 @@ class TestSprintf < Test::Unit::TestCase
assert_equal("0000", sprintf("%#.4b", 0))
assert_equal("0b0001", sprintf("%#.4b", 1))
assert_equal("0b0010", sprintf("%#.4b", 2))
- assert_equal("0b..11", sprintf("%#.4b", -1))
-
assert_equal("0b1111", sprintf("%#.4b", -1))
assert_equal(" 0000", sprintf("%#6.4b", 0))
assert_equal("0b0001", sprintf("%#6.4b", 1))
assert_equal("0b0010", sprintf("%#6.4b", 2))
- assert_equal("0b..11", sprintf("%#6.4b", -1))
-
assert_equal("0b1111", sprintf("%#6.4b", -1))
assert_equal("+0", sprintf("%+b", 0))
assert_equal("+1", sprintf("%+b", 1))
@@ -288,6 +288,8 @@ class TestSprintf < Test::Unit::TestCase
b1 = (/../ =~ s1) != nil
b2 = (/../ =~ s2) != nil
assert(b1 == b2, "[ruby-dev:33224]") -
assert_equal("ffffffff", sprintf("%.8x", -1))
end
def test_named
diff --git a/test/ruby/test_sprintf_comb.rb b/test/ruby/test_sprintf_comb.rb
index 261732b..3105127 100644
--- a/test/ruby/test_sprintf_comb.rb
+++ b/test/ruby/test_sprintf_comb.rb
@@ -190,7 +190,7 @@ class TestSprintfComb < Test::Unit::TestCase
if digits.last != radix-1
digits << (radix-1)
end
-
sign = '..'
-
sign = '..' unless precision else sign = '-' end
@@ -222,8 +222,8 @@ class TestSprintfComb < Test::Unit::TestCase
end
end
if type == 'o' && hs
-
if digits.empty? || digits.last != d
-
digits << d
-
if digits.empty? || digits.last != 0
-
endprefix = '0' end
=end