Feature #3961

printfと精度指定と負の値と

Added by Yui NARUSE over 3 years ago. Updated almost 3 years ago.

[ruby-dev:42420]
Status:Rejected
Priority:Normal
Assignee:-
Category:core
Target version:1.9.3

Description

=begin
printf が、フォーマットに精度指定しつつ負の値を与えた時の挙動が、
Perl と必然性無く異なっていて不便です。

% ./ruby -e'printf("%#.8x", -1)'
0x..ffffff

そもそもこの挙動は C 言語由来で、C の場合例えば以下の通りになります。

% cat test.c
#include
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 @@ rbstrformat(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 @@ rbstrformat(int argc, const VALUE *argv, VALUE fmt)
tmp1 = tmp = rbbig2str0(val, base, RBIGNUMSIGN(val));
s = RSTRING_PTR(tmp);
if (
s == '-') {
- dots = 1;
+ if (!(flags & FPREC0))
+ dots = 1;
if (base == 10) {
rbwarning("negative number for %%u specifier");
}
@@ -925,14 +926,11 @@ rb
strformat(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/testsprintf.rb
index 96a1b62..24b6a34 100644
--- a/test/ruby/test
sprintf.rb
+++ b/test/ruby/testsprintf.rb
@@ -24,12 +24,12 @@ class TestSprintf < Test::Unit::TestCase
assert
equal("0000", sprintf("%.4b", 0))
assertequal("0001", sprintf("%.4b", 1))
assert
equal("0010", sprintf("%.4b", 2))
- assertequal("..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))

    assertequal(" 0", sprintf("%#4b", 0))
    assert
    equal(" 0b1", sprintf("%#4b", 1))
    @@ -44,12 +44,12 @@ class TestSprintf < Test::Unit::TestCase
    assertequal("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))

    assertequal(" 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))

    assertequal("+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, "")
    +

  • assert_equal("ffffffff", sprintf("%.8x", -1))
    end

    def testnamed
    diff --git a/test/ruby/test
    sprintfcomb.rb b/test/ruby/testsprintfcomb.rb
    index 261732b..3105127 100644
    --- a/test/ruby/test
    sprintfcomb.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
    
  •    prefix = '0'
    end
    

    end
    =end

History

#1 Updated by Akira Tanaka over 3 years ago

=begin
2010年10月18日18:01 Yui NARUSE redmine@ruby-lang.org:

Feature #3961: printfと精度指定と負の値と
http://redmine.ruby-lang.org/issues/show/3961

起票者: Yui NARUSE
ステータス: Open, 優先度: Normal
カテゴリ: core, Target version: 1.9.3

printf が、フォーマットに精度指定しつつ負の値を与えた時の挙動が、
Perl と必然性無く異なっていて不便です。

Ruby の挙動は 32bit とか 64bit という環境依存なものに依存しないという点で
必然性のある仕様です。

例えば浮動小数点数の場合には以下のように .. とか付けずにぶった切る訳で、
無限大方向と無限小方向という違いはあれど、
「Perlとかと同じ挙動にできない」というデメリットの方が大きいのではないでしょうか。
% ./ruby -e'printf("%#.8f",10.0/3)'
3.33333333

無限大と無限小はぜんぜん違うでしょう。
小さい方を無視するのはありえても、大きい方を無視するのはありえないと思います。

わたしの場合、CRuby 側の inspect が printf("%x", negative_value) などとしている部分の動作を
RubySpec で Ruby で書く時に頭を抱えてしまいました。

結局、どのような挙動にしたいのかが日本語で説明されていないんですが、
環境依存なのにしたいのかなぁ、と思いきや、パッチを見るとそうでもないようです。

どうしたいのか説明していただけませんか?
--
[田中 哲][たなか あきら][Tanaka Akira]

=end

#2 Updated by Motohiro KOSAKI over 3 years ago

=begin
2010年10月18日18:01 Yui NARUSE redmine@ruby-lang.org:

Feature #3961: printfと精度指定と負の値と
http://redmine.ruby-lang.org/issues/show/3961

起票者: Yui NARUSE
ステータス: Open, 優先度: Normal
カテゴリ: core, Target version: 1.9.3

printf が、フォーマットに精度指定しつつ負の値を与えた時の挙動が、
Perl と必然性無く異なっていて不便です。

% ./ruby -e'printf("%#.8x", -1)'
0x..ffffff

そもそもこの挙動は C 言語由来で、C の場合例えば以下の通りになります。

% cat test.c
#include
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
で、この .. っていらないと思うんです。

めずらしく反対に一票投じよう。

0x..f

0x..fffffff
が両方とも同じ数値ってのはまあ納得できると思うんだ。でも

0xf

0xfffffff

は明らかに違う数に見えるしperl(やC)のように

% perl -e'printf("%#.3x",-1)'
0xffffffffffffffff

と、桁数が小さい時に内部表現まで拡張する。という仕様だとすると内部表現って
何桁よ。という話になるのじゃないかと思います。

別の言い方をすると、この変更が内部モデルの根本的な差異を吸収できるような
パワーを十分もっているとは思いません

例えば浮動小数点数の場合には以下のように .. とか付けずにぶった切る訳で、
無限大方向と無限小方向という違いはあれど、
「Perlとかと同じ挙動にできない」というデメリットの方が大きいのではないでしょうか。
% ./ruby -e'printf("%#.8f",10.0/3)'
3.33333333

わたしの場合、CRuby 側の inspect が printf("%x", negative_value) などとしている部分の動作を
RubySpec で Ruby で書く時に頭を抱えてしまいました。

=end

#3 Updated by Nobuyoshi Nakada over 3 years ago

=begin
なかだです。

At Mon, 18 Oct 2010 18:01:03 +0900,
Yui NARUSE wrote in :

わたしの場合、CRuby 側の inspect が printf("%x", negative_value) などとしている部分の動作を
RubySpec で Ruby で書く時に頭を抱えてしまいました。

それはRubySpecが間違ったことをしているのです。

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

=end

#4 Updated by Yui NARUSE over 3 years ago

  • Status changed from Open to Rejected

=begin

=end

#5 Updated by Yui NARUSE over 3 years ago

=begin
(2010/10/18 19:13), Tanaka Akira wrote:

2010年10月18日18:01 Yui NARUSEredmine@ruby-lang.org:

Feature #3961: printfと精度指定と負の値と
http://redmine.ruby-lang.org/issues/show/3961

起票者: Yui NARUSE
ステータス: Open, 優先度: Normal
カテゴリ: core, Target version: 1.9.3

printf が、フォーマットに精度指定しつつ負の値を与えた時の挙動が、
Perl と必然性無く異なっていて不便です。

Ruby の挙動は 32bit とか 64bit という環境依存なものに依存しないという点で
必然性のある仕様です。

まぁ、それ自体はわかります。

例えば浮動小数点数の場合には以下のように .. とか付けずにぶった切る訳で、
無限大方向と無限小方向という違いはあれど、
「Perlとかと同じ挙動にできない」というデメリットの方が大きいのではないでしょうか。
% ./ruby -e'printf("%#.8f",10.0/3)'
3.33333333

無限大と無限小はぜんぜん違うでしょう。
小さい方を無視するのはありえても、大きい方を無視するのはありえないと思います。

フォーマット時に無限大に溢れているだけで、元は有限の負の値なのですから、
指定次第で省略の仕方が変わるのはあり得ると思います。

わたしの場合、CRuby 側の inspect が printf("%x", negative_value) などとしている部分の動作を
RubySpec で Ruby で書く時に頭を抱えてしまいました。

結局、どのような挙動にしたいのかが日本語で説明されていないんですが、
環境依存なのにしたいのかなぁ、と思いきや、パッチを見るとそうでもないようです。

どうしたいのか説明していただけませんか?

sprintf で %x に負数を与えた場合、無限大に対する2の補数として返ってくるので、
その桁数を精度で制御したかったのです。

が、遡って無限大以外の何に対して補数をとるかを、%ld などで制御するのが
正しいことに気づいたので引っ込めます。

--
NARUSE, Yui naruse@airemix.jp

=end

Also available in: Atom PDF