From 8860f974912450ec62731ad939997cb608ad213a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 4 Dec 2018 12:14:06 +0900 Subject: [PATCH 001/546] Update CHANGES [ci skip] --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b3fa2a5a..eb5a4106 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,14 @@ ## master +* Support `exception:` keyword in `BigDecimal()` + + **Kenta Murata** + +* Remove `BigDecimal#initialize` + + **Kenta Murata** + * Fix the string parsing logic in `BigDecimal()` to follow `Float()` **Kenta Murata** From d7e8ad0a9a8c01187c975fe09c50551ed9c3d76e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 4 Dec 2018 12:12:46 +0900 Subject: [PATCH 002/546] Version 1.4.0.pre.20181204a --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 93cb0476..c65c1da0 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.0.pre.20181130a' +bigdecimal_version = '1.4.0.pre.20181204a' Gem::Specification.new do |s| s.name = "bigdecimal" From 621db4f63f1df7e9d7c4e5398cd0c04a85a8591f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 4 Dec 2018 17:08:58 +0900 Subject: [PATCH 003/546] Remove unnecessary directory listing --- ext/bigdecimal/extconf.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 602c7d61..6f732648 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -60,10 +60,4 @@ def windows_platform? create_makefile('bigdecimal') {|mf| mf << "\nall:\n\nextconf.h: $(srcdir)/#{gemspec_name}\n" - case RUBY_PLATFORM - when /mswin/ - mf << "\nall:\n\tdir $(TARGET_SO_DIR)" - else - mf << "\nall:\n\tls $(TARGET_SO_DIR)" - end } From 2789773a7eca68794bbc8445e10fbeb62eac37de Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 4 Dec 2018 17:09:45 +0900 Subject: [PATCH 004/546] Move dependency on gemspec --- ext/bigdecimal/depend | 2 ++ ext/bigdecimal/extconf.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/depend b/ext/bigdecimal/depend index 6783192b..78833701 100644 --- a/ext/bigdecimal/depend +++ b/ext/bigdecimal/depend @@ -1,3 +1,5 @@ +extconf.h: $(srcdir)/$(GEMSPEC) + # AUTOGENERATED DEPENDENCIES START bigdecimal.o: $(RUBY_EXTCONF_H) bigdecimal.o: $(arch_hdrdir)/ruby/config.h diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 6f732648..a78e740a 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -59,5 +59,5 @@ def windows_platform? end create_makefile('bigdecimal') {|mf| - mf << "\nall:\n\nextconf.h: $(srcdir)/#{gemspec_name}\n" + mf << "GEMSPEC = #{gemspec_name}\n" } From 89e99ccdf5c70ed2c05c7002ed8e7b021529e035 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 4 Dec 2018 17:10:30 +0900 Subject: [PATCH 005/546] Remove unnecessary linker flags Cygwin/mingw linker should be able to link against shared library itself. Mswin build sets -def:$(DEFFILE) option by the default. --- ext/bigdecimal/extconf.rb | 30 ------------------------------ ext/bigdecimal/util/extconf.rb | 30 +++++++----------------------- 2 files changed, 7 insertions(+), 53 deletions(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index a78e740a..2ae5e1b7 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -1,10 +1,6 @@ # frozen_string_literal: false require 'mkmf' -def windows_platform? - /cygwin|mingw|mswin/ === RUBY_PLATFORM -end - gemspec_name = gemspec_path = nil unless ['', '../../'].any? {|dir| gemspec_name = "#{dir}bigdecimal.gemspec" @@ -32,32 +28,6 @@ def windows_platform? have_func("rb_array_const_ptr", "ruby.h") have_func("rb_sym2str", "ruby.h") -if windows_platform? - library_base_name = "ruby-bigdecimal" - case RUBY_PLATFORM - when /cygwin|mingw/ - import_library_name = "libruby-bigdecimal.a" - when /mswin/ - import_library_name = "bigdecimal-$(arch).lib" - end -end - -checking_for(checking_message("Windows")) do - if windows_platform? - case RUBY_PLATFORM - when /cygwin|mingw/ - $DLDFLAGS << " $(srcdir)/bigdecimal.def" - $DLDFLAGS << " -Wl,--out-implib=$(TARGET_SO_DIR)#{import_library_name}" - when /mswin/ - $DLDFLAGS << " /DEF:$(srcdir)/bigdecimal.def" - end - $cleanfiles << import_library_name - true - else - false - end -end - create_makefile('bigdecimal') {|mf| mf << "GEMSPEC = #{gemspec_name}\n" } diff --git a/ext/bigdecimal/util/extconf.rb b/ext/bigdecimal/util/extconf.rb index 023b19ba..8750db1c 100644 --- a/ext/bigdecimal/util/extconf.rb +++ b/ext/bigdecimal/util/extconf.rb @@ -1,36 +1,20 @@ # frozen_string_literal: false require 'mkmf' -def windows_platform? - /cygwin|mingw|mswin/ === RUBY_PLATFORM -end - -if windows_platform? - library_base_name = "ruby-bigdecimal" +checking_for(checking_message("Windows")) do case RUBY_PLATFORM when /cygwin|mingw/ - import_library_name = "libruby-bigdecimal.a" - when /mswin/ - import_library_name = "bigdecimal-$(arch).lib" - end -end - -checking_for(checking_message("Windows")) do - if windows_platform? if defined?($extlist) build_dir = "$(TARGET_SO_DIR)../" else base_dir = File.expand_path('../../../..', __FILE__) - build_dir = File.join(base_dir, "tmp", RUBY_PLATFORM, "bigdecimal", RUBY_VERSION) - end - case RUBY_PLATFORM - when /cygwin|mingw/ - $LDFLAGS << " -L#{build_dir} -L.. -L .." - $libs << " -l#{library_base_name}" - when /mswin/ - $DLDFLAGS << " /libpath:#{build_dir} /libpath:.." - $libs << " #{import_library_name}" + build_dir = File.join(base_dir, "tmp", RUBY_PLATFORM, "bigdecimal", RUBY_VERSION, "") end + $libs << " #{build_dir}bigdecimal.so" + true + when /mswin/ + $DLDFLAGS << " -libpath:.." + $libs << " bigdecimal-$(arch).lib" true else false From 68ed6abec43bc4fe2747f5073c7680147f14c685 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 5 Dec 2018 18:31:33 +0900 Subject: [PATCH 006/546] Fix forgetting to keep the object reference Fix #113 --- ext/bigdecimal/bigdecimal.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 470a1163..64d7f200 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3141,13 +3141,12 @@ rmpd_util_str_to_d(VALUE str) ENTER(1); char const *c_str; Real *pv; - VALUE obj; c_str = StringValueCStr(str); GUARD_OBJ(pv, VpAlloc(0, c_str, 0, 1)); - obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, pv); - RB_OBJ_FREEZE(obj); - return obj; + pv->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, pv); + RB_OBJ_FREEZE(pv->obj); + return pv->obj; } /* Document-class: BigDecimal From 440bc4d69648aa6c100ac59c90f3e65d22909267 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 5 Dec 2018 18:36:41 +0900 Subject: [PATCH 007/546] Version 1.4.0.pre.20181205a --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index c65c1da0..6140bd30 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.0.pre.20181204a' +bigdecimal_version = '1.4.0.pre.20181205a' Gem::Specification.new do |s| s.name = "bigdecimal" From 655f5322b248d7cb690eb24fecab1408e8cce04f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 5 Dec 2018 21:26:03 +0900 Subject: [PATCH 008/546] Fix test --- test/bigdecimal/test_bigdecimal.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index ff295b42..f6231317 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -56,8 +56,8 @@ def test_BigDecimal assert_equal(1234, BigDecimal(" \t\n\r \r1234 \t\n\r \r")) assert_raise(ArgumentError) { BigDecimal("1", -1) } - assert_raise(ArgumentError, /"1__1_1"/) { BigDecimal("1__1_1") } - assert_raise(ArgumentError, /"_1_1_1"/) { BigDecimal("_1_1_1") } + assert_raise_with_message(ArgumentError, /"1__1_1"/) { BigDecimal("1__1_1") } + assert_raise_with_message(ArgumentError, /"_1_1_1"/) { BigDecimal("_1_1_1") } BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) @@ -88,10 +88,10 @@ def test_BigDecimal_with_invalid_string BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - assert_raise(ArgumentError, /"Infinity_"/) { BigDecimal("Infinity_") } - assert_raise(ArgumentError, /"+Infinity_"/) { BigDecimal("+Infinity_") } - assert_raise(ArgumentError, /"-Infinity_"/) { BigDecimal("-Infinity_") } - assert_raise(ArgumentError, /"NaN_"/) { BigDecimal("NaN_") } + assert_raise_with_message(ArgumentError, /"Infinity_"/) { BigDecimal("Infinity_") } + assert_raise_with_message(ArgumentError, /"\+Infinity_"/) { BigDecimal("+Infinity_") } + assert_raise_with_message(ArgumentError, /"-Infinity_"/) { BigDecimal("-Infinity_") } + assert_raise_with_message(ArgumentError, /"NaN_"/) { BigDecimal("NaN_") } end end @@ -209,15 +209,15 @@ def test_BigDecimal_with_exception_keyword end def test_s_ver - assert_raise(NoMethodError, /undefined method `ver`/) { BigDecimal.ver } + assert_raise_with_message(NoMethodError, /undefined method `ver'/) { BigDecimal.ver } end def test_s_allocate - assert_raise(NoMethodError, /undefined method `allocate`/) { BigDecimal.allocate } + assert_raise_with_message(NoMethodError, /undefined method `allocate'/) { BigDecimal.allocate } end def test_s_new - assert_raise(NoMethodError, /undefined method `new`/) { BigDecimal.new("1") } + assert_raise_with_message(NoMethodError, /undefined method `new'/) { BigDecimal.new("1") } end def _test_mode(type) @@ -1821,7 +1821,7 @@ def test_dup def test_dup_subclass c = Class.new(BigDecimal) - assert_raise(NoMethodError, /undefined method `new`/) { c.new(1) } + assert_raise_with_message(NoMethodError, /undefined method `new'/) { c.new(1) } end def test_to_d From f138dfa1a5c324d39e45e7508efc787a17bcb98c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 14 Dec 2018 10:26:27 +0900 Subject: [PATCH 009/546] Restore `BigDecimal.new` for very old version of Rails Fixes #114 --- CHANGES.md | 4 ++++ lib/bigdecimal.rb | 4 ++++ test/bigdecimal/test_bigdecimal.rb | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index eb5a4106..2d2e8e23 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## master +* Restore `BigDecimal.new` just for version 1.4 for very old version of Rails + + **Kenta Murata** + * Support `exception:` keyword in `BigDecimal()` **Kenta Murata** diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index c9682deb..a3b1a559 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -3,3 +3,7 @@ rescue LoadError require 'bigdecimal.so' end + +def BigDecimal.new(*args, **kwargs) + BigDecimal(*args, **kwargs) +end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f6231317..7623a8b2 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -217,7 +217,13 @@ def test_s_allocate end def test_s_new - assert_raise_with_message(NoMethodError, /undefined method `new'/) { BigDecimal.new("1") } + # TODO: BigDecimal.new will be removed on 1.5 + # assert_raise_with_message(NoMethodError, /undefined method `new'/) { BigDecimal.new("1") } + assert_equal(BigDecimal(1), BigDecimal.new(1)) + assert_raise(ArgumentError) { BigDecimal.new(',', exception: true) } + assert_nothing_raised { assert_equal(nil, BigDecimal.new(',', exception: false)) } + assert_raise(TypeError) { BigDecimal.new(nil, exception: true) } + assert_nothing_raised { assert_equal(nil, BigDecimal.new(nil, exception: false)) } end def _test_mode(type) @@ -1821,7 +1827,13 @@ def test_dup def test_dup_subclass c = Class.new(BigDecimal) - assert_raise_with_message(NoMethodError, /undefined method `new'/) { c.new(1) } + # TODO: BigDecimal.new will be removed on 1.5 + # assert_raise_with_message(NoMethodError, /undefined method `new'/) { c.new(1) } + assert_equal(BigDecimal(1), c.new(1)) + assert_raise(ArgumentError) { c.new(',', exception: true) } + assert_nothing_raised { assert_equal(nil, c.new(',', exception: false)) } + assert_raise(TypeError) { c.new(nil, exception: true) } + assert_nothing_raised { assert_equal(nil, c.new(nil, exception: false)) } end def test_to_d From 9770872ddce83564731a7f16acba75445bc68600 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 14 Dec 2018 10:34:51 +0900 Subject: [PATCH 010/546] Display deprecation warning of BigDecimal.new --- lib/bigdecimal.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index a3b1a559..2a95df58 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -5,5 +5,6 @@ end def BigDecimal.new(*args, **kwargs) + warn "BigDecimal.new is deprecated; use BigDecimal() method instead.", uplevel: 1 BigDecimal(*args, **kwargs) end From 628a241084a68230fe4085c08128e7f2945bef7c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 14 Dec 2018 11:37:56 +0900 Subject: [PATCH 011/546] Version 1.4.0.pre.20181214a --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 6140bd30..2475de78 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.0.pre.20181205a' +bigdecimal_version = '1.4.0.pre.20181214a' Gem::Specification.new do |s| s.name = "bigdecimal" From 8f03422431ff20b3874b123eec6c39ffd659cbac Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 20 Dec 2018 08:54:27 +0900 Subject: [PATCH 012/546] Fix VpAlloc so that '1.2.3'.to_d is 1.2 [Bug #15426] [ruby-dev:50712] --- ext/bigdecimal/bigdecimal.c | 2 +- test/bigdecimal/test_bigdecimal_util.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 64d7f200..052fdc57 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4271,7 +4271,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) while (ISSPACE(szVal[j])) ++j; /* Invalid character */ - if (szVal[j]) { + if (szVal[j] && strict_p) { goto invalid_value; } } diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 04c8eb2b..bb9ed831 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -74,6 +74,7 @@ def test_String_to_d assert_equal(BigDecimal('0.1'), "0.1_e10".to_d) assert_equal(BigDecimal('0.1'), "0.1e_10".to_d) assert_equal(BigDecimal('1'), "0.1e1__0".to_d) + assert_equal(BigDecimal('1.2'), "1.2.3".to_d) assert("2.5".to_d.frozen?) end From 9d7aabf5440c00bdeafc739e4b798330a0335702 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 20 Dec 2018 08:58:36 +0900 Subject: [PATCH 013/546] Update CHANGES.md [ci skip] --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 2d2e8e23..692ed1fa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## master +* Fix VpAlloc so that '1.2.3'.to_d is 1.2 + + **Nobuyoshi Nakada** + * Restore `BigDecimal.new` just for version 1.4 for very old version of Rails **Kenta Murata** From b3c55c998814b045aee139e65ddc365f1403634a Mon Sep 17 00:00:00 2001 From: nobu Date: Fri, 14 Dec 2018 04:10:42 +0000 Subject: [PATCH 014/546] Suppress deprecation warnings git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@66397 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ext/bigdecimal/depend | 1 + test/bigdecimal/test_bigdecimal.rb | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/ext/bigdecimal/depend b/ext/bigdecimal/depend index 78833701..4c797d79 100644 --- a/ext/bigdecimal/depend +++ b/ext/bigdecimal/depend @@ -1,4 +1,5 @@ extconf.h: $(srcdir)/$(GEMSPEC) +Makefile: $(srcdir)/lib/bigdecimal.rb # AUTOGENERATED DEPENDENCIES START bigdecimal.o: $(RUBY_EXTCONF_H) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 7623a8b2..e4f14449 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -219,11 +219,14 @@ def test_s_allocate def test_s_new # TODO: BigDecimal.new will be removed on 1.5 # assert_raise_with_message(NoMethodError, /undefined method `new'/) { BigDecimal.new("1") } + verbose, $VERBOSE = $VERBOSE, nil assert_equal(BigDecimal(1), BigDecimal.new(1)) assert_raise(ArgumentError) { BigDecimal.new(',', exception: true) } assert_nothing_raised { assert_equal(nil, BigDecimal.new(',', exception: false)) } assert_raise(TypeError) { BigDecimal.new(nil, exception: true) } assert_nothing_raised { assert_equal(nil, BigDecimal.new(nil, exception: false)) } + ensure + $VERBOSE = verbose end def _test_mode(type) @@ -1829,11 +1832,14 @@ def test_dup_subclass c = Class.new(BigDecimal) # TODO: BigDecimal.new will be removed on 1.5 # assert_raise_with_message(NoMethodError, /undefined method `new'/) { c.new(1) } + verbose, $VERBOSE = $VERBOSE, nil assert_equal(BigDecimal(1), c.new(1)) assert_raise(ArgumentError) { c.new(',', exception: true) } assert_nothing_raised { assert_equal(nil, c.new(',', exception: false)) } assert_raise(TypeError) { c.new(nil, exception: true) } assert_nothing_raised { assert_equal(nil, c.new(nil, exception: false)) } + ensure + $VERBOSE = verbose end def test_to_d From 974cd94f77b7a58cfaff0b8bec9c8f20ec2fb465 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 20 Dec 2018 09:06:00 +0900 Subject: [PATCH 015/546] Version 1.4.0.pre.20181220a --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 2475de78..c20dada3 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.0.pre.20181214a' +bigdecimal_version = '1.4.0.pre.20181220a' Gem::Specification.new do |s| s.name = "bigdecimal" From cbe4149b97bff80a18a9e09ce5a9fefcbe6479c9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 20 Dec 2018 09:22:49 +0900 Subject: [PATCH 016/546] Fix depends --- ext/bigdecimal/depend | 2 +- ext/bigdecimal/extconf.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/depend b/ext/bigdecimal/depend index 4c797d79..943bd6c3 100644 --- a/ext/bigdecimal/depend +++ b/ext/bigdecimal/depend @@ -1,5 +1,5 @@ extconf.h: $(srcdir)/$(GEMSPEC) -Makefile: $(srcdir)/lib/bigdecimal.rb +Makefile: $(BIGDECIMAL_RB) # AUTOGENERATED DEPENDENCIES START bigdecimal.o: $(RUBY_EXTCONF_H) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 2ae5e1b7..a6a36304 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -28,6 +28,13 @@ have_func("rb_array_const_ptr", "ruby.h") have_func("rb_sym2str", "ruby.h") +if File.file?(File.expand_path('../lib/bigdecimal.rb', __FILE__)) + bigdecimal_rb = "$(srcdir)/lib/bigdecimal.rb" +else + bigdecimal_rb = "$(srcdir)/../../lib/bigdecimal.rb" +end + create_makefile('bigdecimal') {|mf| mf << "GEMSPEC = #{gemspec_name}\n" + mf << "BIGDECIMAL_RB = #{bigdecimal_rb}\n" } From c652818dd249fc428989afaa6766b9cac21e8477 Mon Sep 17 00:00:00 2001 From: zverok Date: Sat, 22 Dec 2018 21:54:28 +0200 Subject: [PATCH 017/546] Mention exception: argument --- ext/bigdecimal/bigdecimal.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 052fdc57..35b5e14a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2668,7 +2668,7 @@ BigDecimal_new(int argc, VALUE *argv) } /* call-seq: - * BigDecimal(initial, digits) + * BigDecimal(initial, digits, exception: true) * * Create a new BigDecimal object. * @@ -2682,8 +2682,13 @@ BigDecimal_new(int argc, VALUE *argv) * the number of significant digits is determined from the initial * value. * - * The actual number of significant digits used in computation is usually - * larger than the specified number. + * The actual number of significant digits used in computation is + * usually larger than the specified number. + * + * exception:: Whether an exception should be raised on invalid arguments. + * +true+ by default, if passed +false+, just returns +nil+ + * for invalid. + * * * ==== Exceptions * From b7a927cb6fc74b5dd0709750e484dea8de389c25 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 23 Dec 2018 13:50:30 +0900 Subject: [PATCH 018/546] Remove needless function calls --- ext/bigdecimal/bigdecimal.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 052fdc57..e9506aac 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3955,9 +3955,6 @@ VP_EXPORT size_t VpInit(BDIGIT BaseVal) { /* Setup +/- Inf NaN -0 */ - VpGetDoubleNaN(); - VpGetDoublePosInf(); - VpGetDoubleNegInf(); VpGetDoubleNegZero(); /* Allocates Vp constants. */ From a50a00d32765ba30d2c07a30da1a87281447fd8d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 23 Dec 2018 23:10:04 +0900 Subject: [PATCH 019/546] Version 1.4.0 --- CHANGES.md | 2 +- bigdecimal.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 692ed1fa..fea3a4ee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # CHANGES -## master +## 1.4.0 * Fix VpAlloc so that '1.2.3'.to_d is 1.2 diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index c20dada3..fcf52f9a 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.0.pre.20181220a' +bigdecimal_version = '1.4.0' Gem::Specification.new do |s| s.name = "bigdecimal" From 3cf124961db2bd070aee040cfc9736e06a221a93 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 23 Dec 2018 23:35:34 +0900 Subject: [PATCH 020/546] Update CHANGES.md [ci skip] --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index fea3a4ee..82a8dab9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## 1.4.0 +* Update documentation of `exception:` keyword. + + **Victor Shepelev** + * Fix VpAlloc so that '1.2.3'.to_d is 1.2 **Nobuyoshi Nakada** From 03401c876bc12e8d6598a36927fa0d64ca739072 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Sun, 23 Dec 2018 22:05:51 -0800 Subject: [PATCH 021/546] Add lib/bigdecimal.rb to files array Fixes #118 Co-authored-by: Chongyu Zhu --- bigdecimal.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index fcf52f9a..9fdd8013 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |s| ext/bigdecimal/extconf.rb ext/bigdecimal/util/extconf.rb ext/bigdecimal/util/util.c + lib/bigdecimal.rb lib/bigdecimal/jacobian.rb lib/bigdecimal/ludcmp.rb lib/bigdecimal/math.rb From c350a7dd64c8766239537111a801ae206d8cde3f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 25 Dec 2018 10:08:37 +0900 Subject: [PATCH 022/546] Version 1.4.1 --- CHANGES.md | 6 ++++++ bigdecimal.gemspec | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 82a8dab9..0de5ab22 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 1.4.1 + +* Fix wrong packaging. + + **Ben Ford** + ## 1.4.0 * Update documentation of `exception:` keyword. diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 9fdd8013..68d8f3c0 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.0' +bigdecimal_version = '1.4.1' Gem::Specification.new do |s| s.name = "bigdecimal" From 27a5c319f5aeb10a91b74dc37e645a5ce5e874e1 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 25 Dec 2018 22:18:42 +0900 Subject: [PATCH 023/546] Test installability on appvayor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 4df19c91..6cbbfa52 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,7 @@ build_script: - rake -rdevkit compile test_script: - rake test + - rake install deploy: off environment: matrix: From f81bec9eda274917d41382fd5b84cdb92c86140a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 25 Dec 2018 22:19:59 +0900 Subject: [PATCH 024/546] Fix gem installation problem on mingw32 --- ext/bigdecimal/util/extconf.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/util/extconf.rb b/ext/bigdecimal/util/extconf.rb index 8750db1c..b0d56358 100644 --- a/ext/bigdecimal/util/extconf.rb +++ b/ext/bigdecimal/util/extconf.rb @@ -4,11 +4,11 @@ checking_for(checking_message("Windows")) do case RUBY_PLATFORM when /cygwin|mingw/ - if defined?($extlist) - build_dir = "$(TARGET_SO_DIR)../" - else + if ARGV.include?('-rdevkit') # check `rake -rdevkit compile` case base_dir = File.expand_path('../../../..', __FILE__) build_dir = File.join(base_dir, "tmp", RUBY_PLATFORM, "bigdecimal", RUBY_VERSION, "") + else + build_dir = "$(TARGET_SO_DIR)../" end $libs << " #{build_dir}bigdecimal.so" true From 73c722f892bff75628f7281391494e8de6215657 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 26 Dec 2018 10:42:28 +0900 Subject: [PATCH 025/546] Version 1.4.2.pre.20181216a --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 68d8f3c0..a94013e5 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.1' +bigdecimal_version = '1.4.2.pre.20181216a' Gem::Specification.new do |s| s.name = "bigdecimal" From 5d21ab9ae6c235ee592caae5f2f3c4eebf2de42e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 26 Dec 2018 11:08:53 +0900 Subject: [PATCH 026/546] Version 1.4.2 --- CHANGES.md | 6 ++++++ bigdecimal.gemspec | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0de5ab22..db47693d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 1.4.2 + +* Fix gem installation issue on mingw32. + + **Kenta Murata** + ## 1.4.1 * Fix wrong packaging. diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index a94013e5..c7a27fe1 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.2.pre.20181216a' +bigdecimal_version = '1.4.2' Gem::Specification.new do |s| s.name = "bigdecimal" From 023ff31a173c02052c795e54cdaa43698a40781c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 25 Dec 2018 22:14:11 +0900 Subject: [PATCH 027/546] Update .travis.yml --- .travis.yml | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 112c0c58..d067a8c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,26 @@ --- -language: ruby -rvm: -- ruby-head -- 2.5.1 -- 2.4.4 -- 2.3.7 -matrix: -before_install: -- gem install bundler -v 1.13.7 -script: -- rake travis notification: email: - mrkn@ruby-lang.org + +language: ruby + +before_install: + - gem install bundler -v 1.17.2 + +script: + - rake travis + - rake install + +matrix: + include: + - name: "2.3" + rvm: 2.3 + - name: "2.4" + rvm: 2.4.5 + - name: "2.5" + rvm: 2.5.2 + - name: "2.6" + rvm: 2.6 + - name: "trunk" + rvm: ruby-head From 7f801b72eef2daf69339ddf8eb337b4a2615cc87 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 26 Dec 2018 12:06:31 +0900 Subject: [PATCH 028/546] Remove BigDecimal.new, again --- CHANGES.md | 6 ++++++ lib/bigdecimal.rb | 5 ----- test/bigdecimal/test_bigdecimal.rb | 24 +++--------------------- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index db47693d..2a75743b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## master + +* Remove `BigDecimal.new` + + **Kenta Murata** + ## 1.4.2 * Fix gem installation issue on mingw32. diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 2a95df58..c9682deb 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -3,8 +3,3 @@ rescue LoadError require 'bigdecimal.so' end - -def BigDecimal.new(*args, **kwargs) - warn "BigDecimal.new is deprecated; use BigDecimal() method instead.", uplevel: 1 - BigDecimal(*args, **kwargs) -end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e4f14449..28fc3c84 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -217,16 +217,7 @@ def test_s_allocate end def test_s_new - # TODO: BigDecimal.new will be removed on 1.5 - # assert_raise_with_message(NoMethodError, /undefined method `new'/) { BigDecimal.new("1") } - verbose, $VERBOSE = $VERBOSE, nil - assert_equal(BigDecimal(1), BigDecimal.new(1)) - assert_raise(ArgumentError) { BigDecimal.new(',', exception: true) } - assert_nothing_raised { assert_equal(nil, BigDecimal.new(',', exception: false)) } - assert_raise(TypeError) { BigDecimal.new(nil, exception: true) } - assert_nothing_raised { assert_equal(nil, BigDecimal.new(nil, exception: false)) } - ensure - $VERBOSE = verbose + assert_raise_with_message(NoMethodError, /undefined method `new'/) { BigDecimal.new("1") } end def _test_mode(type) @@ -1828,18 +1819,9 @@ def test_dup end end - def test_dup_subclass + def test_new_subclass c = Class.new(BigDecimal) - # TODO: BigDecimal.new will be removed on 1.5 - # assert_raise_with_message(NoMethodError, /undefined method `new'/) { c.new(1) } - verbose, $VERBOSE = $VERBOSE, nil - assert_equal(BigDecimal(1), c.new(1)) - assert_raise(ArgumentError) { c.new(',', exception: true) } - assert_nothing_raised { assert_equal(nil, c.new(',', exception: false)) } - assert_raise(TypeError) { c.new(nil, exception: true) } - assert_nothing_raised { assert_equal(nil, c.new(nil, exception: false)) } - ensure - $VERBOSE = verbose + assert_raise_with_message(NoMethodError, /undefined method `new'/) { c.new(1) } end def test_to_d From 9c1744da3b51170c526673a603af51503f190286 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 10 Jan 2019 00:00:57 +0900 Subject: [PATCH 029/546] Come back subclassing support --- ext/bigdecimal/bigdecimal.c | 39 ++++++++++++++++++++---------- lib/bigdecimal.rb | 18 +++++++++++--- test/bigdecimal/test_bigdecimal.rb | 13 +++++++--- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index da1b24a6..a728b459 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2573,7 +2573,7 @@ opts_exception_p(VALUE opts) } static Real * -BigDecimal_new(int argc, VALUE *argv) +VpNewVarArgs(int argc, VALUE *argv) { size_t mf; VALUE opts = Qnil; @@ -2667,6 +2667,23 @@ BigDecimal_new(int argc, VALUE *argv) return VpAlloc(mf, RSTRING_PTR(iniValue), 1, exc); } +static VALUE +BigDecimal_new(int argc, VALUE *argv, VALUE klass) +{ + ENTER(1); + Real *pv; + VALUE obj; + + obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + pv = VpNewVarArgs(argc, argv); + if (pv == NULL) return Qnil; + SAVE(pv); + if (ToValue(pv)) pv = VpCopy(NULL, pv); + RTYPEDDATA_DATA(obj) = pv; + RB_OBJ_FREEZE(obj); + return pv->obj = obj; +} + /* call-seq: * BigDecimal(initial, digits, exception: true) * @@ -2706,18 +2723,14 @@ BigDecimal_new(int argc, VALUE *argv) static VALUE f_BigDecimal(int argc, VALUE *argv, VALUE self) { - ENTER(1); - Real *pv; - VALUE obj; + return BigDecimal_new(argc, argv, rb_cBigDecimal); +} - obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); - pv = BigDecimal_new(argc, argv); - if (pv == NULL) return Qnil; - SAVE(pv); - if (ToValue(pv)) pv = VpCopy(NULL, pv); - RTYPEDDATA_DATA(obj) = pv; - RB_OBJ_FREEZE(obj); - return pv->obj = obj; +/* DEPRECATED: BigDecimal.new() */ +static VALUE +BigDecimal_s_new(int argc, VALUE *argv, VALUE klass) +{ + return BigDecimal_new(argc, argv, klass); } /* call-seq: @@ -3299,7 +3312,7 @@ Init_bigdecimal(void) /* Class methods */ rb_undef_method(CLASS_OF(rb_cBigDecimal), "allocate"); - rb_undef_method(CLASS_OF(rb_cBigDecimal), "new"); + rb_define_singleton_method(rb_cBigDecimal, "new", BigDecimal_s_new, -1); rb_define_singleton_method(rb_cBigDecimal, "mode", BigDecimal_mode, -1); rb_define_singleton_method(rb_cBigDecimal, "limit", BigDecimal_limit, -1); rb_define_singleton_method(rb_cBigDecimal, "double_fig", BigDecimal_double_fig, 0); diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 2a95df58..1a044728 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -4,7 +4,19 @@ require 'bigdecimal.so' end -def BigDecimal.new(*args, **kwargs) - warn "BigDecimal.new is deprecated; use BigDecimal() method instead.", uplevel: 1 - BigDecimal(*args, **kwargs) +class BigDecimal + module Deprecation + def new(*args, **kwargs) + warn "BigDecimal.new is deprecated; use BigDecimal() method instead.", uplevel: 1 + super + end + end + + class << self + prepend Deprecation + + def inherited(subclass) + warn "subclassing BigDecimal will be disallowed after bigdecimal version 2.0", uplevel: 1 + end + end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e4f14449..9a913293 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1828,12 +1828,17 @@ def test_dup end end - def test_dup_subclass - c = Class.new(BigDecimal) - # TODO: BigDecimal.new will be removed on 1.5 - # assert_raise_with_message(NoMethodError, /undefined method `new'/) { c.new(1) } + def test_define_subclass + assert_warning(/subclassing BigDecimal will be disallowed/) do + Class.new(BigDecimal) + end + end + + def test_subclass_behavior verbose, $VERBOSE = $VERBOSE, nil + c = Class.new(BigDecimal) assert_equal(BigDecimal(1), c.new(1)) + assert_kind_of(c, c.new(1)) assert_raise(ArgumentError) { c.new(',', exception: true) } assert_nothing_raised { assert_equal(nil, c.new(',', exception: false)) } assert_raise(TypeError) { c.new(nil, exception: true) } From ef62daa25fe0131d30c4b9fbff6116c7c4d0fbc0 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 10 Jan 2019 00:01:32 +0900 Subject: [PATCH 030/546] Update CHANGES.md --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index db47693d..b9cac0b2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 1.4.3 + +* Restore subclassing support + + **Kenta Murata** + ## 1.4.2 * Fix gem installation issue on mingw32. From 322573a60a34e5c88d93c4b9d5660e758c46b87d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 10 Jan 2019 00:02:17 +0900 Subject: [PATCH 031/546] Version 1.4.3.pre.20190110 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index c7a27fe1..63e61918 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.2' +bigdecimal_version = '1.4.3.pre.20190110' Gem::Specification.new do |s| s.name = "bigdecimal" From 04f782f31b496dc250a02e0ee43f84c907b4aa25 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 10 Jan 2019 00:16:10 +0900 Subject: [PATCH 032/546] Rename function --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index da1b24a6..5fef625e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2573,7 +2573,7 @@ opts_exception_p(VALUE opts) } static Real * -BigDecimal_new(int argc, VALUE *argv) +VpNewVarArg(int argc, VALUE *argv) { size_t mf; VALUE opts = Qnil; @@ -2711,7 +2711,7 @@ f_BigDecimal(int argc, VALUE *argv, VALUE self) VALUE obj; obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); - pv = BigDecimal_new(argc, argv); + pv = VpNewVarArg(argc, argv); if (pv == NULL) return Qnil; SAVE(pv); if (ToValue(pv)) pv = VpCopy(NULL, pv); From 2c5d770dca0c5dfc37c3af3075ab285ffa810505 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 11 Jan 2019 01:24:45 +0900 Subject: [PATCH 033/546] Version 1.4.3 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 63e61918..692b8d5f 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.3.pre.20190110' +bigdecimal_version = '1.4.3' Gem::Specification.new do |s| s.name = "bigdecimal" From fbe3c43ba07a8fa57d1c6d42051f16e6e764917a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 11 Jan 2019 03:43:07 +0900 Subject: [PATCH 034/546] Version 2.0.0.dev --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 692b8d5f..6782f6b5 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '1.4.3' +bigdecimal_version = '2.0.0.dev' Gem::Specification.new do |s| s.name = "bigdecimal" From 8b76f25a86abef6d21284aba2db6c2ad4759f90c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 11 Jan 2019 03:53:13 +0900 Subject: [PATCH 035/546] README.md: Write about versions [ci skip] --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index a230e1fe..9dc8e101 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,23 @@ Or install it yourself as: $ gem install bigdecimal +## Which version should you select + +The differences among versions are given below: + +| version | characteristics | Supported ruby version range | +| ------- | --------------- | ----------------------- | +| 2.0.0 | You cannot use BigDecimal.new and do subclassing | 2.4 .. | +| 1.4.3 | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.6 | +| 1.3.5 | You can use BigDecimal.new and subclassing without warning | .. 2.5 | + +You can select the version you want to use using `gem` method in Gemfile or scripts. +For example, you want to stick bigdecimal version 1.3.5, it works file to put the following `gem` call in you Gemfile. + +```ruby +gem 'bigdecimal', '1.3.5' +``` + ## Usage TODO: Write usage instructions here From 7fa4a82c09de0ec51edcc9bfed9392e94127b580 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 11 Jan 2019 03:55:07 +0900 Subject: [PATCH 036/546] Minimum required ruby version is 2.4 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 6782f6b5..3aa28959 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -35,7 +35,7 @@ Gem::Specification.new do |s| sample/pi.rb ] - s.required_ruby_version = Gem::Requirement.new(">= 2.3.0".freeze) + s.required_ruby_version = Gem::Requirement.new(">= 2.4.0".freeze) s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rake-compiler", ">= 0.9" From 3a8118e609dfa922867e57d478f11ae5ff7b9ecc Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 11 Jan 2019 03:55:38 +0900 Subject: [PATCH 037/546] Allow failures on 2.3 --- .travis.yml | 2 ++ appveyor.yml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index d067a8c1..27a8eb2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,3 +24,5 @@ matrix: rvm: 2.6 - name: "trunk" rvm: ruby-head + allow_failures: + - rvm: 2.3 diff --git a/appveyor.yml b/appveyor.yml index 6cbbfa52..e21d9e2f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,3 +17,7 @@ environment: - ruby_version: "24-x64" - ruby_version: "25" - ruby_version: "25-x64" +matrix: + allow_failures: + - ruby_version: "23" + - ruby_version: "23-x64" From 2b109d80cf1d8fbcc2b6b1b0bd360c148f6970f9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 11 Jan 2019 03:58:07 +0900 Subject: [PATCH 038/546] Revert "Minimum required ruby version is 2.4" This reverts commit 7fa4a82c09de0ec51edcc9bfed9392e94127b580. --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 3aa28959..6782f6b5 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -35,7 +35,7 @@ Gem::Specification.new do |s| sample/pi.rb ] - s.required_ruby_version = Gem::Requirement.new(">= 2.4.0".freeze) + s.required_ruby_version = Gem::Requirement.new(">= 2.3.0".freeze) s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rake-compiler", ">= 0.9" From 66a94e207f069314c1a0410201a754d5bd370c34 Mon Sep 17 00:00:00 2001 From: zverok Date: Sat, 12 Jan 2019 12:32:22 +0200 Subject: [PATCH 039/546] Move require to render docs properly --- lib/bigdecimal/jacobian.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/bigdecimal/jacobian.rb b/lib/bigdecimal/jacobian.rb index 84c50248..5e293042 100644 --- a/lib/bigdecimal/jacobian.rb +++ b/lib/bigdecimal/jacobian.rb @@ -1,5 +1,7 @@ # frozen_string_literal: false -# + +require 'bigdecimal' + # require 'bigdecimal/jacobian' # # Provides methods to compute the Jacobian matrix of a set of equations at a @@ -21,9 +23,6 @@ # # fx is f.values(x). # - -require 'bigdecimal' - module Jacobian module_function From fa606835d53084fccd7b22ce6b947a199b3e353d Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Wed, 17 Apr 2019 20:13:37 -0700 Subject: [PATCH 040/546] Add spec for String#to_d with a trailing decimal point The trailing decimal should be ignored. --- test/bigdecimal/test_bigdecimal_util.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index bb9ed831..353a2155 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -75,6 +75,7 @@ def test_String_to_d assert_equal(BigDecimal('0.1'), "0.1e_10".to_d) assert_equal(BigDecimal('1'), "0.1e1__0".to_d) assert_equal(BigDecimal('1.2'), "1.2.3".to_d) + assert_equal(BigDecimal('1'), "1.".to_d) assert("2.5".to_d.frozen?) end From 5ee27f3938f01250b6543d6f97203ffb1e45efbf Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Wed, 17 Apr 2019 23:25:57 -0700 Subject: [PATCH 041/546] Should not require fractional or exponential parts unless strict_p This matches the behavior of String#to_f, e.g. '1.'.to_f => 1.0 '1e'.to_f => 1.0 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 5fef625e..0ea9cc39 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4281,7 +4281,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) psz[i] = '\0'; - if (((ni == 0 || dot_seen) && nf == 0) || (exp_seen && ne == 0)) { + if (strict_p && (((ni == 0 || dot_seen) && nf == 0) || (exp_seen && ne == 0))) { VALUE str; invalid_value: if (!strict_p) { From 55eb0c30557fe19af3d5792675063b4460db6c1c Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Wed, 17 Apr 2019 23:28:09 -0700 Subject: [PATCH 042/546] Add test for '1e'.to_d as well Just to document this change. --- test/bigdecimal/test_bigdecimal_util.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 353a2155..b963fcde 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -76,6 +76,7 @@ def test_String_to_d assert_equal(BigDecimal('1'), "0.1e1__0".to_d) assert_equal(BigDecimal('1.2'), "1.2.3".to_d) assert_equal(BigDecimal('1'), "1.".to_d) + assert_equal(BigDecimal('1'), "1e".to_d) assert("2.5".to_d.frozen?) end From 277405be7e18b79e31623589576a1d2dbc432dcb Mon Sep 17 00:00:00 2001 From: Marc Riera Date: Thu, 25 Apr 2019 12:18:19 +0200 Subject: [PATCH 043/546] Update docs on `BigDecimal#round` When calling `BigDecimal#round` without any argument, it casts the result to an `Integer`, while calling it with an argument (`BigDecimal#round(n)`) keeps the result as a `BigDecimal`. I think this is an unexpected behavior, but changing it might not be backwards-compatible, so let's document it at the docs at least. --- ext/bigdecimal/bigdecimal.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0ea9cc39..57ffb97b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1756,12 +1756,16 @@ BigDecimal_fix(VALUE self) * round(n, mode) * * Round to the nearest integer (by default), returning the result as a - * BigDecimal. + * BigDecimal if n is specified, or as Integer if it isn't. + * * * BigDecimal('3.14159').round #=> 3 * BigDecimal('8.7').round #=> 9 * BigDecimal('-9.9').round #=> -10 * + * BigDecimal('3.14159').round(2).class.name #=> "BigDecimal" + * BigDecimal('3.14159').round.class.name #=> "Integer" + * * If n is specified and positive, the fractional part of the result has no * more than that many digits. * From 7d6d2f919b1eeac65ed35e58f5df9138243ad9e0 Mon Sep 17 00:00:00 2001 From: Marc Riera Date: Thu, 25 Apr 2019 14:51:12 +0200 Subject: [PATCH 044/546] Remove extra empty line --- ext/bigdecimal/bigdecimal.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 57ffb97b..1ce6c864 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1758,7 +1758,6 @@ BigDecimal_fix(VALUE self) * Round to the nearest integer (by default), returning the result as a * BigDecimal if n is specified, or as Integer if it isn't. * - * * BigDecimal('3.14159').round #=> 3 * BigDecimal('8.7').round #=> 9 * BigDecimal('-9.9').round #=> -10 From 7d2e87bc569cc33c0ee38b81f050b58a5ce2f0e3 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 21 Apr 2019 14:41:09 +0900 Subject: [PATCH 045/546] Update README for Windows users --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 9dc8e101..5ae6c9d8 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,14 @@ Or install it yourself as: $ gem install bigdecimal +### For RubyInstaller users + +If your Ruby comes from [RubyInstaller](https://rubyinstaller.org/), make sure [Devkit](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit) is available on your environment before installing bigdecimal. + +### For Chocolatey + +I don't have enough knowledge about Chocolatey. Please tell me what should I write here. + ## Which version should you select The differences among versions are given below: From c79ef2acb40cc295dea3569b070d7911a53f5a6e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 13 Apr 2019 15:54:08 +0900 Subject: [PATCH 046/546] Explicitly link util.so to bigdecimal.so --- ext/bigdecimal/util/extconf.rb | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/util/extconf.rb b/ext/bigdecimal/util/extconf.rb index b0d56358..e31315ca 100644 --- a/ext/bigdecimal/util/extconf.rb +++ b/ext/bigdecimal/util/extconf.rb @@ -1,23 +1,26 @@ # frozen_string_literal: false require 'mkmf' +require 'pathname' -checking_for(checking_message("Windows")) do - case RUBY_PLATFORM - when /cygwin|mingw/ - if ARGV.include?('-rdevkit') # check `rake -rdevkit compile` case - base_dir = File.expand_path('../../../..', __FILE__) - build_dir = File.join(base_dir, "tmp", RUBY_PLATFORM, "bigdecimal", RUBY_VERSION, "") - else - build_dir = "$(TARGET_SO_DIR)../" - end - $libs << " #{build_dir}bigdecimal.so" - true - when /mswin/ +checking_for(checking_message("bigdecimal.so")) do + if RUBY_PLATFORM =~ /mswin/ $DLDFLAGS << " -libpath:.." $libs << " bigdecimal-$(arch).lib" true else - false + base_dir = Pathname("../../../..").expand_path(__FILE__) + current_dir = Pathname.pwd.relative_path_from(base_dir) + + tmp_build_base_dir = Pathname("tmp")/RUBY_PLATFORM/"bigdecimal" + if current_dir == tmp_build_base_dir/"util"/RUBY_VERSION + lib_dir = base_dir/"lib" + bigdecimal_so = lib_dir/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" + break false unless bigdecimal_so.exist? + $libs << " #{bigdecimal_so}" + else + $libs << " $(TARGET_SO_DIR)../bigdecimal.so" + end + true end end From 920d41e1c3c58e080d4d16ff42e66e9d78da9fd8 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 14 Apr 2019 10:32:08 +0900 Subject: [PATCH 047/546] Fix for rake-compiler-dock --- ext/bigdecimal/util/extconf.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/util/extconf.rb b/ext/bigdecimal/util/extconf.rb index e31315ca..67041d02 100644 --- a/ext/bigdecimal/util/extconf.rb +++ b/ext/bigdecimal/util/extconf.rb @@ -14,7 +14,13 @@ tmp_build_base_dir = Pathname("tmp")/RUBY_PLATFORM/"bigdecimal" if current_dir == tmp_build_base_dir/"util"/RUBY_VERSION lib_dir = base_dir/"lib" - bigdecimal_so = lib_dir/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" + if RUBY_PLATFORM =~ /cygwin|mingw/ + # For rake-compiler-dock + ver = RUBY_VERSION.split('.')[0, 2].join('.') + bigdecimal_so = lib_dir/ver/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" + else + bigdecimal_so = lib_dir/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" + end break false unless bigdecimal_so.exist? $libs << " #{bigdecimal_so}" else From ddcacb84e25e7564a96c2a3c2c3e0dfcbd307078 Mon Sep 17 00:00:00 2001 From: Marc Riera Date: Wed, 8 May 2019 09:45:10 +0200 Subject: [PATCH 048/546] Improve docs wording --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1ce6c864..bf1aa109 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1756,7 +1756,7 @@ BigDecimal_fix(VALUE self) * round(n, mode) * * Round to the nearest integer (by default), returning the result as a - * BigDecimal if n is specified, or as Integer if it isn't. + * BigDecimal if n is specified, or as an Integer if it isn't. * * BigDecimal('3.14159').round #=> 3 * BigDecimal('8.7').round #=> 9 From 32474f57a1a95602826705796ce45c07f0dd03d0 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 8 May 2019 18:13:53 +0900 Subject: [PATCH 049/546] Fix for Windows --- ext/bigdecimal/util/extconf.rb | 50 ++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/ext/bigdecimal/util/extconf.rb b/ext/bigdecimal/util/extconf.rb index 67041d02..e5d2cc4a 100644 --- a/ext/bigdecimal/util/extconf.rb +++ b/ext/bigdecimal/util/extconf.rb @@ -2,32 +2,40 @@ require 'mkmf' require 'pathname' -checking_for(checking_message("bigdecimal.so")) do - if RUBY_PLATFORM =~ /mswin/ - $DLDFLAGS << " -libpath:.." - $libs << " bigdecimal-$(arch).lib" - true - else - base_dir = Pathname("../../../..").expand_path(__FILE__) - current_dir = Pathname.pwd.relative_path_from(base_dir) +def check_bigdecimal_so + checking_for(checking_message("bigdecimal.so")) do + if RUBY_PLATFORM =~ /mswin/ + $DLDFLAGS << " -libpath:.." + $libs << " bigdecimal-$(arch).lib" + true + else + base_dir = Pathname("../../../..").expand_path(__FILE__) + current_dir = Pathname.pwd.relative_path_from(base_dir) - tmp_build_base_dir = Pathname("tmp")/RUBY_PLATFORM/"bigdecimal" - if current_dir == tmp_build_base_dir/"util"/RUBY_VERSION - lib_dir = base_dir/"lib" - if RUBY_PLATFORM =~ /cygwin|mingw/ - # For rake-compiler-dock - ver = RUBY_VERSION.split('.')[0, 2].join('.') - bigdecimal_so = lib_dir/ver/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" - else + tmp_build_base_dir = Pathname("tmp")/RUBY_PLATFORM/"bigdecimal" + if current_dir == tmp_build_base_dir/"util"/RUBY_VERSION + lib_dir = base_dir/"lib" bigdecimal_so = lib_dir/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" + unless bigdecimal_so.exist? + if RUBY_PLATFORM =~ /cygwin|mingw/ + # For rake-compiler-dock + ver = RUBY_VERSION.split('.')[0, 2].join('.') + bigdecimal_so = lib_dir/ver/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" + end + break false unless bigdecimal_so.exist? + end + $libs << " #{bigdecimal_so}" + else + $libs << " $(TARGET_SO_DIR)../bigdecimal.so" end - break false unless bigdecimal_so.exist? - $libs << " #{bigdecimal_so}" - else - $libs << " $(TARGET_SO_DIR)../bigdecimal.so" + true end - true end end +unless check_bigdecimal_so + $stderr.puts "Unable to find bigdecimal.so" + abort +end + create_makefile('bigdecimal/util') From 48da8823c753809083ca47a34a63957de7da9239 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 9 May 2019 11:58:09 +0900 Subject: [PATCH 050/546] Add Ruby 2.6 in appveyor --- appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e21d9e2f..5ed15667 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,12 +11,12 @@ test_script: deploy: off environment: matrix: - - ruby_version: "23" - - ruby_version: "23-x64" - - ruby_version: "24" - - ruby_version: "24-x64" + - ruby_version: "26" + - ruby_version: "26-x64" - ruby_version: "25" - ruby_version: "25-x64" + - ruby_version: "24" + - ruby_version: "24-x64" matrix: allow_failures: - ruby_version: "23" From dd81eff92ce42401d9722e06a463b40fa4923926 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 9 May 2019 14:04:13 +0900 Subject: [PATCH 051/546] Specify Visual Studio 2017 image on AppVeyor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 5ed15667..aeea860b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,5 @@ --- +image: Visual Studio 2017 clone_depth: 10 install: - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% From 16884e2349cb6adca27f2b773272584e2900c116 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 10 May 2019 00:02:51 +0900 Subject: [PATCH 052/546] Merge pull request #142 from ruby/fix_for_gem_install Fix for gem install --- ext/bigdecimal/util/extconf.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/util/extconf.rb b/ext/bigdecimal/util/extconf.rb index e5d2cc4a..770df701 100644 --- a/ext/bigdecimal/util/extconf.rb +++ b/ext/bigdecimal/util/extconf.rb @@ -12,22 +12,20 @@ def check_bigdecimal_so base_dir = Pathname("../../../..").expand_path(__FILE__) current_dir = Pathname.pwd.relative_path_from(base_dir) - tmp_build_base_dir = Pathname("tmp")/RUBY_PLATFORM/"bigdecimal" - if current_dir == tmp_build_base_dir/"util"/RUBY_VERSION - lib_dir = base_dir/"lib" - bigdecimal_so = lib_dir/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" - unless bigdecimal_so.exist? - if RUBY_PLATFORM =~ /cygwin|mingw/ + lib_dir = base_dir/"lib" + bigdecimal_so = lib_dir/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" + unless bigdecimal_so.exist? + if RUBY_PLATFORM =~ /cygwin|mingw/ + tmp_build_base_dir = Pathname("tmp")/RUBY_PLATFORM/"bigdecimal" + if current_dir == tmp_build_base_dir/"util"/RUBY_VERSION # For rake-compiler-dock ver = RUBY_VERSION.split('.')[0, 2].join('.') bigdecimal_so = lib_dir/ver/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" end - break false unless bigdecimal_so.exist? end - $libs << " #{bigdecimal_so}" - else - $libs << " $(TARGET_SO_DIR)../bigdecimal.so" + break false unless bigdecimal_so.exist? end + $libs << " #{bigdecimal_so}" true end end From d3966214d71c53dd8ef7fa22565e5c573e6845a5 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 10 May 2019 12:43:51 +0900 Subject: [PATCH 053/546] Merge pull request #145 from ruby/fix_version Make BigDecimal::VERSION to support -dev suffix --- ext/bigdecimal/extconf.rb | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index a6a36304..68c18ded 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -1,6 +1,21 @@ # frozen_string_literal: false require 'mkmf' +def check_bigdecimal_version(gemspec_path) + message "checking RUBY_BIGDECIMAL_VERSION... " + + bigdecimal_version = + IO.readlines(gemspec_path) + .grep(/\Abigdecimal_version\s+=\s+/)[0][/\'([^\']+)\'/, 1] + + version_components = bigdecimal_version.split('.') + bigdecimal_version = version_components[0, 3].join('.') + bigdecimal_version << "-#{version_components[3]}" if version_components[3] + $defs << %Q[-DRUBY_BIGDECIMAL_VERSION=\\"#{bigdecimal_version}\\"] + + message "#{bigdecimal_version}\n" +end + gemspec_name = gemspec_path = nil unless ['', '../../'].any? {|dir| gemspec_name = "#{dir}bigdecimal.gemspec" @@ -11,11 +26,7 @@ abort end -bigdecimal_version = - IO.readlines(gemspec_path) - .grep(/\Abigdecimal_version\s+=\s+/)[0][/\'([\d\.]+)\'/, 1] - -$defs << %Q[-DRUBY_BIGDECIMAL_VERSION=\\"#{bigdecimal_version}\\"] +check_bigdecimal_version(gemspec_path) have_func("labs", "stdlib.h") have_func("llabs", "stdlib.h") From af7bfa3bbeec9f320b7450ca29f472f0ebb38cb1 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 10 May 2019 16:24:08 +0900 Subject: [PATCH 054/546] Merge pull request #146 from ruby/interpret_loosely Add BigDecimal.interpret_loosely --- CHANGES.md | 5 ++++ Rakefile | 8 ------ bigdecimal.gemspec | 7 +----- ext/bigdecimal/bigdecimal.c | 29 +++++++++++----------- ext/bigdecimal/bigdecimal.def | 3 --- ext/bigdecimal/util/extconf.rb | 39 ------------------------------ ext/bigdecimal/util/util.c | 9 ------- lib/bigdecimal/util.rb | 4 ++- test/bigdecimal/test_bigdecimal.rb | 23 ++++++++++++++++++ 9 files changed, 47 insertions(+), 80 deletions(-) delete mode 100644 ext/bigdecimal/bigdecimal.def delete mode 100644 ext/bigdecimal/util/extconf.rb delete mode 100644 ext/bigdecimal/util/util.c diff --git a/CHANGES.md b/CHANGES.md index 7508785a..016e51be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,11 @@ **Kenta Murata** +* Add BigDecimal.interpret_loosely, use it in String#to_d, + and remove bigdecimal/util.so and rmpd_util_str_to_d + + **Kenta Murata** + ## 1.4.3 * Restore subclassing support diff --git a/Rakefile b/Rakefile index 0bac782c..a49519e8 100644 --- a/Rakefile +++ b/Rakefile @@ -14,14 +14,6 @@ Rake::ExtensionTask.new('bigdecimal', spec) do |ext| s.files.concat ["lib/2.3/bigdecimal.so", "lib/2.4/bigdecimal.so", "lib/2.5/bigdecimal.so"] end end -Rake::ExtensionTask.new('bigdecimal/util', spec) do |ext| - ext.lib_dir = File.join(*['lib', ENV['FAT_DIR']].compact) - ext.cross_compile = true - ext.cross_platform = %w[x86-mingw32 x64-mingw32] - ext.cross_compiling do |s| - s.files.concat ["lib/2.3/bigdecimal/util.so", "lib/2.4/bigdecimal/util.so", "lib/2.5/bigdecimal/util.so"] - end -end desc "Compile binaries for mingw platform using rake-compiler-dock" task 'build:mingw' do diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 6782f6b5..2638645c 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -14,16 +14,11 @@ Gem::Specification.new do |s| s.license = "ruby" s.require_paths = %w[lib] - s.extensions = %w[ext/bigdecimal/extconf.rb ext/bigdecimal/util/extconf.rb] + s.extensions = %w[ext/bigdecimal/extconf.rb] s.files = %w[ bigdecimal.gemspec ext/bigdecimal/bigdecimal.c - ext/bigdecimal/bigdecimal.def ext/bigdecimal/bigdecimal.h - ext/bigdecimal/depend - ext/bigdecimal/extconf.rb - ext/bigdecimal/util/extconf.rb - ext/bigdecimal/util/util.c lib/bigdecimal.rb lib/bigdecimal/jacobian.rb lib/bigdecimal/ludcmp.rb diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index bf1aa109..648febf7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2723,6 +2723,20 @@ f_BigDecimal(int argc, VALUE *argv, VALUE self) return pv->obj = obj; } +static VALUE +BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) +{ + ENTER(1); + char const *c_str; + Real *pv; + + c_str = StringValueCStr(str); + GUARD_OBJ(pv, VpAlloc(0, c_str, 0, 1)); + pv->obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, pv); + RB_OBJ_FREEZE(pv->obj); + return pv->obj; +} + /* call-seq: * BigDecimal.limit(digits) * @@ -3143,20 +3157,6 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) return y; } -VALUE -rmpd_util_str_to_d(VALUE str) -{ - ENTER(1); - char const *c_str; - Real *pv; - - c_str = StringValueCStr(str); - GUARD_OBJ(pv, VpAlloc(0, c_str, 0, 1)); - pv->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, pv); - RB_OBJ_FREEZE(pv->obj); - return pv->obj; -} - /* Document-class: BigDecimal * BigDecimal provides arbitrary-precision floating point decimal arithmetic. * @@ -3303,6 +3303,7 @@ Init_bigdecimal(void) /* Class methods */ rb_undef_method(CLASS_OF(rb_cBigDecimal), "allocate"); rb_undef_method(CLASS_OF(rb_cBigDecimal), "new"); + rb_define_singleton_method(rb_cBigDecimal, "interpret_loosely", BigDecimal_s_interpret_loosely, 1); rb_define_singleton_method(rb_cBigDecimal, "mode", BigDecimal_mode, -1); rb_define_singleton_method(rb_cBigDecimal, "limit", BigDecimal_limit, -1); rb_define_singleton_method(rb_cBigDecimal, "double_fig", BigDecimal_double_fig, 0); diff --git a/ext/bigdecimal/bigdecimal.def b/ext/bigdecimal/bigdecimal.def deleted file mode 100644 index 615bf72e..00000000 --- a/ext/bigdecimal/bigdecimal.def +++ /dev/null @@ -1,3 +0,0 @@ -EXPORTS -rmpd_util_str_to_d -Init_bigdecimal diff --git a/ext/bigdecimal/util/extconf.rb b/ext/bigdecimal/util/extconf.rb deleted file mode 100644 index 770df701..00000000 --- a/ext/bigdecimal/util/extconf.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: false -require 'mkmf' -require 'pathname' - -def check_bigdecimal_so - checking_for(checking_message("bigdecimal.so")) do - if RUBY_PLATFORM =~ /mswin/ - $DLDFLAGS << " -libpath:.." - $libs << " bigdecimal-$(arch).lib" - true - else - base_dir = Pathname("../../../..").expand_path(__FILE__) - current_dir = Pathname.pwd.relative_path_from(base_dir) - - lib_dir = base_dir/"lib" - bigdecimal_so = lib_dir/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" - unless bigdecimal_so.exist? - if RUBY_PLATFORM =~ /cygwin|mingw/ - tmp_build_base_dir = Pathname("tmp")/RUBY_PLATFORM/"bigdecimal" - if current_dir == tmp_build_base_dir/"util"/RUBY_VERSION - # For rake-compiler-dock - ver = RUBY_VERSION.split('.')[0, 2].join('.') - bigdecimal_so = lib_dir/ver/"bigdecimal.#{RbConfig::CONFIG['DLEXT']}" - end - end - break false unless bigdecimal_so.exist? - end - $libs << " #{bigdecimal_so}" - true - end - end -end - -unless check_bigdecimal_so - $stderr.puts "Unable to find bigdecimal.so" - abort -end - -create_makefile('bigdecimal/util') diff --git a/ext/bigdecimal/util/util.c b/ext/bigdecimal/util/util.c deleted file mode 100644 index 8d38d878..00000000 --- a/ext/bigdecimal/util/util.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "ruby.h" - -RUBY_EXTERN VALUE rmpd_util_str_to_d(VALUE str); - -void -Init_util(void) -{ - rb_define_method(rb_cString, "to_d", rmpd_util_str_to_d, 0); -} diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index 88f490cb..4ece8347 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -6,7 +6,6 @@ #++ require 'bigdecimal' -require 'bigdecimal/util.so' class Integer < Numeric # call-seq: @@ -66,6 +65,9 @@ class String # # See also BigDecimal::new. # + def to_d + BigDecimal.interpret_loosely(self) + end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 28fc3c84..04947d13 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -220,6 +220,29 @@ def test_s_new assert_raise_with_message(NoMethodError, /undefined method `new'/) { BigDecimal.new("1") } end + def test_s_interpret_loosely + assert_equal(BigDecimal('1'), BigDecimal.interpret_loosely("1__1_1")) + assert_equal(BigDecimal('2.5'), BigDecimal.interpret_loosely("2.5")) + assert_equal(BigDecimal('2.5'), BigDecimal.interpret_loosely("2.5 degrees")) + assert_equal(BigDecimal('2.5e1'), BigDecimal.interpret_loosely("2.5e1 degrees")) + assert_equal(BigDecimal('0'), BigDecimal.interpret_loosely("degrees 100.0")) + assert_equal(BigDecimal('0.125'), BigDecimal.interpret_loosely("0.1_2_5")) + assert_equal(BigDecimal('0.125'), BigDecimal.interpret_loosely("0.1_2_5__")) + assert_equal(BigDecimal('1'), BigDecimal.interpret_loosely("1_.125")) + assert_equal(BigDecimal('1'), BigDecimal.interpret_loosely("1._125")) + assert_equal(BigDecimal('0.1'), BigDecimal.interpret_loosely("0.1__2_5")) + assert_equal(BigDecimal('0.1'), BigDecimal.interpret_loosely("0.1_e10")) + assert_equal(BigDecimal('0.1'), BigDecimal.interpret_loosely("0.1e_10")) + assert_equal(BigDecimal('1'), BigDecimal.interpret_loosely("0.1e1__0")) + assert_equal(BigDecimal('1.2'), BigDecimal.interpret_loosely("1.2.3")) + assert_equal(BigDecimal('1'), BigDecimal.interpret_loosely("1.")) + assert_equal(BigDecimal('1'), BigDecimal.interpret_loosely("1e")) + + assert_equal(BigDecimal('0.0'), BigDecimal.interpret_loosely("invalid")) + + assert(BigDecimal.interpret_loosely("2.5").frozen?) + end + def _test_mode(type) BigDecimal.mode(type, true) assert_raise(FloatDomainError) { yield } From f6a135cffb16596748d1bcf8e51f8ab7383e3640 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 15 May 2019 13:22:19 +0900 Subject: [PATCH 055/546] Update CHANGES.md --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 016e51be..05f8a443 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,12 @@ **Kenta Murata** +## 1.4.4 + +* Fix String#to_d against the string with trailing "e" like "1e" + + **Ibrahim Awwal** + * Add BigDecimal.interpret_loosely, use it in String#to_d, and remove bigdecimal/util.so and rmpd_util_str_to_d From 1de8f8d81cc81ca8287107c1095b5bd1c95f9cb9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 15 May 2019 13:23:56 +0900 Subject: [PATCH 056/546] [ci skip] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ae6c9d8..15d2ec74 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The differences among versions are given below: | version | characteristics | Supported ruby version range | | ------- | --------------- | ----------------------- | | 2.0.0 | You cannot use BigDecimal.new and do subclassing | 2.4 .. | -| 1.4.3 | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.6 | +| 1.4.x | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.6 | | 1.3.5 | You can use BigDecimal.new and subclassing without warning | .. 2.5 | You can select the version you want to use using `gem` method in Gemfile or scripts. From 5598ed521ab47275ff68b5e4caf41f4fd2599777 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 17 Jul 2019 12:46:19 +0900 Subject: [PATCH 057/546] Drop support for "fat gem" release "fat gem" release has never actually been done in the past, and we have no plan to do it in the near future. See GH#148 for the detailed discussion. https://github.com/ruby/bigdecimal/pull/148 --- Rakefile | 15 +-------------- bigdecimal.gemspec | 1 - lib/bigdecimal.rb | 6 +----- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/Rakefile b/Rakefile index a49519e8..671e6bb6 100644 --- a/Rakefile +++ b/Rakefile @@ -6,20 +6,7 @@ require "rake/extensiontask" require "rake/testtask" spec = eval(File.read('bigdecimal.gemspec')) -Rake::ExtensionTask.new('bigdecimal', spec) do |ext| - ext.lib_dir = File.join(*['lib', ENV['FAT_DIR']].compact) - ext.cross_compile = true - ext.cross_platform = %w[x86-mingw32 x64-mingw32] - ext.cross_compiling do |s| - s.files.concat ["lib/2.3/bigdecimal.so", "lib/2.4/bigdecimal.so", "lib/2.5/bigdecimal.so"] - end -end - -desc "Compile binaries for mingw platform using rake-compiler-dock" -task 'build:mingw' do - require 'rake_compiler_dock' - RakeCompilerDock.sh "bundle && rake cross native gem RUBY_CC_VERSION=2.4.2:2.3.0:2.5.0" -end +Rake::ExtensionTask.new('bigdecimal', spec) Rake::TestTask.new do |t| t.libs << 'test/lib' diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 2638645c..1efc7a80 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -34,7 +34,6 @@ Gem::Specification.new do |s| s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rake-compiler", ">= 0.9" - s.add_development_dependency "rake-compiler-dock", ">= 0.6.1" s.add_development_dependency "minitest", "< 5.0.0" s.add_development_dependency "pry" end diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index c9682deb..8fd2587c 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -1,5 +1 @@ -begin - require "#{RUBY_VERSION[/\d+\.\d+/]}/bigdecimal.so" -rescue LoadError - require 'bigdecimal.so' -end +require 'bigdecimal.so' From c2add4036e7efaaa98cf1fef6a2eac9feebf336f Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 9 Aug 2019 22:25:28 -0700 Subject: [PATCH 058/546] Do not mutate frozen BigDecimal argument in BigMath.exp BigMath.exp would previously mutate a BigDecimal argument if the argument was negative. This makes a internal copy of the argument to avoid the mutation. Fixes Ruby Bug 8542 --- ext/bigdecimal/bigdecimal.c | 4 ++++ test/bigdecimal/test_bigdecimal.rb | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 648febf7..8aae7f88 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2956,6 +2956,10 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) n = prec + rmpd_double_figures(); negative = BIGDECIMAL_NEGATIVE_P(vx); if (negative) { + VALUE x_zero = INT2NUM(1); + VALUE x_copy = f_BigDecimal(1, &x_zero, klass); + x = BigDecimal_initialize_copy(x_copy, x); + vx = DATA_PTR(x); VpSetSign(vx, 1); } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 04947d13..e5c1b1a0 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1606,6 +1606,13 @@ def test_exp_with_complex end end + def test_exp_with_negative + x = BigDecimal(-1) + y = BigMath.exp(x, 20) + assert_equal(y, BigMath.exp(-1, 20)) + assert_equal(BigDecimal(-1), x) + end + def test_exp_with_negative_infinite BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) From 9932c5ced7b97f3accc5fd063ecacecf65433e44 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Jul 2019 19:20:53 +0900 Subject: [PATCH 059/546] Check exception flag as a bool [Bug #15987] --- ext/bigdecimal/bigdecimal.c | 12 ++++++++++++ ext/bigdecimal/extconf.rb | 1 + 2 files changed, 13 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 648febf7..19ddcbca 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2563,6 +2563,10 @@ BigDecimal_clone(VALUE self) return self; } +#ifdef HAVE_RB_OPTS_EXCEPTION_P +int rb_opts_exception_p(VALUE opts, int default_value); +#define opts_exception_p(opts) rb_opts_exception_p((opts), 1) +#else static int opts_exception_p(VALUE opts) { @@ -2572,8 +2576,16 @@ opts_exception_p(VALUE opts) kwds[0] = rb_intern_const("exception"); } rb_get_kwargs(opts, kwds, 0, 1, &exception); + switch (exception) { + case Qtrue: case Qfalse: + break; + default: + rb_raise(rb_eArgError, "true or false is expected as exception: %+"PRIsVALUE, + flagname, obj); + } return exception != Qfalse; } +#endif static Real * VpNewVarArg(int argc, VALUE *argv) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 68c18ded..b4098fda 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -38,6 +38,7 @@ def check_bigdecimal_version(gemspec_path) have_func("rb_rational_den", "ruby.h") have_func("rb_array_const_ptr", "ruby.h") have_func("rb_sym2str", "ruby.h") +have_func("rb_opts_exception_p", "ruby.h") if File.file?(File.expand_path('../lib/bigdecimal.rb', __FILE__)) bigdecimal_rb = "$(srcdir)/lib/bigdecimal.rb" From 369bb191abae7f1b921c3fd2116e659b3ce92a37 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Jul 2019 20:19:53 +0900 Subject: [PATCH 060/546] Removed wrong argument in the fallback function [Bug #15987] --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 19ddcbca..4e37ba00 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2581,7 +2581,7 @@ opts_exception_p(VALUE opts) break; default: rb_raise(rb_eArgError, "true or false is expected as exception: %+"PRIsVALUE, - flagname, obj); + obj); } return exception != Qfalse; } From 00dda796a3954db873a9b93ac371e7506027ee95 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Jul 2019 20:21:50 +0900 Subject: [PATCH 061/546] Fixed argument in the fallback function [Bug #15987] --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4e37ba00..0a3dafed 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2581,7 +2581,7 @@ opts_exception_p(VALUE opts) break; default: rb_raise(rb_eArgError, "true or false is expected as exception: %+"PRIsVALUE, - obj); + exception); } return exception != Qfalse; } From ad8d2f62f669dcaa136da66cbae85094cd9807c0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Jul 2019 21:05:25 +0900 Subject: [PATCH 062/546] Default to true when no exception flag [Bug #15987] --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0a3dafed..7fd6049f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2575,7 +2575,7 @@ opts_exception_p(VALUE opts) if (!kwds[0]) { kwds[0] = rb_intern_const("exception"); } - rb_get_kwargs(opts, kwds, 0, 1, &exception); + if (!rb_get_kwargs(opts, kwds, 0, 1, &exception)) return 1; switch (exception) { case Qtrue: case Qfalse: break; From 6fe40d8c4820c6e6c091b1c50c8e586132da4be4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Jul 2019 07:25:06 +0900 Subject: [PATCH 063/546] Removed useless `freeze`s from gemspec files --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 1efc7a80..53dbe911 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |s| sample/pi.rb ] - s.required_ruby_version = Gem::Requirement.new(">= 2.3.0".freeze) + s.required_ruby_version = Gem::Requirement.new(">= 2.3.0") s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rake-compiler", ">= 0.9" From c0f2c59a141cec54398ed6113a748dbfd1dd4c79 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Jul 2019 13:06:22 +0900 Subject: [PATCH 064/546] Include ruby/assert.h in ruby/ruby.h so that assertions can be there --- ext/bigdecimal/depend | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/bigdecimal/depend b/ext/bigdecimal/depend index 943bd6c3..ffd61b89 100644 --- a/ext/bigdecimal/depend +++ b/ext/bigdecimal/depend @@ -4,6 +4,7 @@ Makefile: $(BIGDECIMAL_RB) # AUTOGENERATED DEPENDENCIES START bigdecimal.o: $(RUBY_EXTCONF_H) bigdecimal.o: $(arch_hdrdir)/ruby/config.h +bigdecimal.o: $(hdrdir)/ruby/assert.h bigdecimal.o: $(hdrdir)/ruby/defines.h bigdecimal.o: $(hdrdir)/ruby/intern.h bigdecimal.o: $(hdrdir)/ruby/missing.h From 91e6261fbc6e941aaf1c4c5adadab8a3a44b5554 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Wed, 7 Aug 2019 09:01:33 -0700 Subject: [PATCH 065/546] Make Kernel#{Pathname,BigDecimal,Complex} return argument if given correct type This is how Kernel#{Array,String,Float,Integer,Hash,Rational} work. BigDecimal and Complex instances are always frozen, so this should not cause backwards compatibility issues for those. Pathname instances are not frozen, so potentially this could cause backwards compatibility issues by not returning a new object. Based on a patch from Joshua Ballanco, some minor changes by me. Fixes [Bug #7522] --- ext/bigdecimal/bigdecimal.c | 3 +++ test/bigdecimal/test_bigdecimal.rb | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7fd6049f..a4f7503e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2725,6 +2725,9 @@ f_BigDecimal(int argc, VALUE *argv, VALUE self) Real *pv; VALUE obj; + if (argc > 0 && CLASS_OF(argv[0]) == rb_cBigDecimal) { + if (argc == 1 || (argc == 2 && RB_TYPE_P(argv[1], T_HASH))) return argv[0]; + } obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); pv = VpNewVarArg(argc, argv); if (pv == NULL) return Qnil; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 04947d13..fe359c62 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -55,6 +55,12 @@ def test_BigDecimal assert_equal(10**(-1), BigDecimal("1E-1"), '#4825') assert_equal(1234, BigDecimal(" \t\n\r \r1234 \t\n\r \r")) + bd = BigDecimal.new("1.12", 1) + assert_same(bd, BigDecimal(bd)) + assert_same(bd, BigDecimal(bd, exception: false)) + assert_not_same(bd, BigDecimal(bd, 1)) + assert_not_same(bd, BigDecimal(bd, 1, exception: false)) + assert_raise(ArgumentError) { BigDecimal("1", -1) } assert_raise_with_message(ArgumentError, /"1__1_1"/) { BigDecimal("1__1_1") } assert_raise_with_message(ArgumentError, /"_1_1_1"/) { BigDecimal("_1_1_1") } From 5b0fc64cef2bb36036f583f895d4c1a492e3ad64 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 7 Oct 2019 09:22:06 +0900 Subject: [PATCH 066/546] Remove assert.h from depend for ruby < 2.7 --- ext/bigdecimal/depend | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/bigdecimal/depend b/ext/bigdecimal/depend index ffd61b89..943bd6c3 100644 --- a/ext/bigdecimal/depend +++ b/ext/bigdecimal/depend @@ -4,7 +4,6 @@ Makefile: $(BIGDECIMAL_RB) # AUTOGENERATED DEPENDENCIES START bigdecimal.o: $(RUBY_EXTCONF_H) bigdecimal.o: $(arch_hdrdir)/ruby/config.h -bigdecimal.o: $(hdrdir)/ruby/assert.h bigdecimal.o: $(hdrdir)/ruby/defines.h bigdecimal.o: $(hdrdir)/ruby/intern.h bigdecimal.o: $(hdrdir)/ruby/missing.h From 1c37f921f200a5a3ebd0be90021353b6c6417204 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 7 Oct 2019 09:22:16 +0900 Subject: [PATCH 067/546] Do not use BigDecimal.new --- test/bigdecimal/test_bigdecimal.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index fe359c62..b9fad130 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -55,12 +55,6 @@ def test_BigDecimal assert_equal(10**(-1), BigDecimal("1E-1"), '#4825') assert_equal(1234, BigDecimal(" \t\n\r \r1234 \t\n\r \r")) - bd = BigDecimal.new("1.12", 1) - assert_same(bd, BigDecimal(bd)) - assert_same(bd, BigDecimal(bd, exception: false)) - assert_not_same(bd, BigDecimal(bd, 1)) - assert_not_same(bd, BigDecimal(bd, 1, exception: false)) - assert_raise(ArgumentError) { BigDecimal("1", -1) } assert_raise_with_message(ArgumentError, /"1__1_1"/) { BigDecimal("1__1_1") } assert_raise_with_message(ArgumentError, /"_1_1_1"/) { BigDecimal("_1_1_1") } @@ -78,6 +72,14 @@ def test_BigDecimal end end + def test_BigDecimal_bug7522 + bd = BigDecimal("1.12", 1) + assert_same(bd, BigDecimal(bd)) + assert_same(bd, BigDecimal(bd, exception: false)) + assert_not_same(bd, BigDecimal(bd, 1)) + assert_not_same(bd, BigDecimal(bd, 1, exception: false)) + end + def test_BigDecimal_with_invalid_string [ '', '.', 'e1', 'd1', '.e', '.d', '1.e', '1.d', '.1e', '.1d', From aaf237fa9edf47a624a090ac500bf459101a2c72 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 8 Oct 2019 15:23:46 -0700 Subject: [PATCH 068/546] Undef BigDecimal#initialize_copy Both BigDecimal#clone and BigDecimal#dup return self, there is no reason to have initialize_copy exposed as a Ruby method. The same is true for initialize_clone and initialize_dup. --- ext/bigdecimal/bigdecimal.c | 4 +++- test/bigdecimal/test_bigdecimal.rb | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b2354038..7255aea0 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3453,7 +3453,9 @@ Init_bigdecimal(void) /* instance methods */ - rb_define_method(rb_cBigDecimal, "initialize_copy", BigDecimal_initialize_copy, 1); + rb_undef_method(rb_cBigDecimal, "initialize_copy"); + rb_undef_method(rb_cBigDecimal, "initialize_clone"); + rb_undef_method(rb_cBigDecimal, "initialize_dup"); rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 6ac9ea11..89814af6 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1874,6 +1874,12 @@ def test_bug6406 EOS end + def test_no_initialize_copy + assert_equal(false, BigDecimal(1).respond_to?(:initialize_copy, true)) + assert_equal(false, BigDecimal(1).respond_to?(:initialize_dup, true)) + assert_equal(false, BigDecimal(1).respond_to?(:initialize_clone, true)) + end + def assert_no_memory_leak(code, *rest, **opt) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" super(["-rbigdecimal"], From 00795cb01fbe095a5faef8e7c142ed99b1ddb947 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 9 Oct 2019 10:27:08 +0900 Subject: [PATCH 069/546] Support a Complex in Kernel.BigDecimal() --- ext/bigdecimal/bigdecimal.c | 13 +++++++++++++ test/bigdecimal/test_bigdecimal.rb | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7255aea0..6e503465 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2631,6 +2631,7 @@ VpNewVarArg(int argc, VALUE *argv) } } + retry: switch (TYPE(iniValue)) { case T_DATA: if (is_kind_of_BigDecimal(iniValue)) { @@ -2668,6 +2669,18 @@ VpNewVarArg(int argc, VALUE *argv) } return GetVpValueWithPrec(iniValue, mf, 1); + case T_COMPLEX: + { + VALUE im; + im = rb_complex_imag(iniValue); + if (!is_zero(im)) { + rb_raise(rb_eArgError, + "Unable to make a BigDecimal from non-zero imaginary number"); + } + iniValue = rb_complex_real(iniValue); + goto retry; + } + case T_STRING: /* fall through */ default: diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 89814af6..a2f05177 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -137,6 +137,14 @@ def test_BigDecimal_with_float end end + def test_BigDecimal_with_complex + assert_equal(BigDecimal("1"), BigDecimal(Complex(1, 0))) + assert_equal(BigDecimal("0.333333333333333333333"), BigDecimal(Complex(1.quo(3), 0), 21)) + assert_equal(BigDecimal("0.1235"), BigDecimal(Complex(0.1234567, 0), 4)) + + assert_raise_with_message(ArgumentError, "Unable to make a BigDecimal from non-zero imaginary number") { BigDecimal(Complex(1, 1)) } + end + def test_BigDecimal_with_big_decimal assert_equal(BigDecimal(1), BigDecimal(BigDecimal(1))) assert_equal(BigDecimal('+0'), BigDecimal(BigDecimal('+0'))) From 97e794ac978eba43f55d57b9e5fef871c5c94956 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 9 Oct 2019 10:39:39 +0900 Subject: [PATCH 070/546] Add Complex#to_d --- lib/bigdecimal/util.rb | 33 +++++++++++++++++++++++++ test/bigdecimal/test_bigdecimal_util.rb | 12 +++++++++ 2 files changed, 45 insertions(+) diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index 4ece8347..66fff786 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -131,6 +131,39 @@ def to_d(precision) end +class Complex < Numeric + # call-seq: + # cmp.to_d -> bigdecimal + # cmp.to_d(precision) -> bigdecimal + # + # Returns the value as a BigDecimal. + # + # The +precision+ parameter is required for a rational complex number. + # This parameter is used to determine the number of significant digits + # for the result. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # Complex(0.1234567, 0).to_d(4) # => 0.1235e0 + # Complex(Rational(22, 7), 0).to_d(3) # => 0.314e1 + # + # See also BigDecimal::new. + # + def to_d(*args) + BigDecimal(self) unless self.imag.zero? # to raise eerror + + if args.length == 0 + case self.real + when Rational + BigDecimal(self.real) # to raise error + end + end + self.real.to_d(*args) + end +end + + class NilClass # call-seq: # nil.to_d -> bigdecimal diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index b963fcde..c4d58169 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -60,6 +60,18 @@ def test_Rational_to_d_with_negative_precision assert_raise(ArgumentError) { 355.quo(113).to_d(-42) } end + def test_Complex_to_d + assert_equal(BigDecimal("1"), Complex(1, 0).to_d) + assert_equal(BigDecimal("0.333333333333333333333"), + Complex(1.quo(3), 0).to_d(21)) + assert_equal(BigDecimal("0.1234567"), Complex(0.1234567, 0).to_d) + assert_equal(BigDecimal("0.1235"), Complex(0.1234567, 0).to_d(4)) + + assert_raise_with_message(ArgumentError, "can't omit precision for a Rational.") { Complex(1.quo(3), 0).to_d } + + assert_raise_with_message(ArgumentError, "Unable to make a BigDecimal from non-zero imaginary number") { Complex(1, 1).to_d } + end + def test_String_to_d assert_equal(BigDecimal('1'), "1__1_1".to_d) assert_equal(BigDecimal('2.5'), "2.5".to_d) From 61ec452599061a0f4a544aae033492c13a96ba97 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 9 Oct 2019 10:50:59 +0900 Subject: [PATCH 071/546] Support Ruby < 2.6 --- ext/bigdecimal/bigdecimal.c | 24 ++++++++++++++++++++++++ ext/bigdecimal/extconf.rb | 3 +++ 2 files changed, 27 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6e503465..fc3f38ad 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -127,6 +127,30 @@ rb_rational_den(VALUE rat) } #endif +#ifndef HAVE_RB_COMPLEX_REAL +static inline VALUE +rb_complex_real(VALUE cmp) +{ +#ifdef HAVE_TYPE_STRUCT_RCOMPLEX + return RCOMPLEX(cmp)->real; +#else + return rb_funcall(cmp, rb_intern("real"), 0); +#endif +} +#endif + +#ifndef HAVE_RB_COMPLEX_IMAG +static inline VALUE +rb_complex_imag(VALUE cmp) +{ +#ifdef HAVE_TYPE_STRUCT_RCOMPLEX + return RCOMPLEX(cmp)->imag; +#else + return rb_funcall(cmp, rb_intern("imag"), 0); +#endif +} +#endif + #define BIGDECIMAL_POSITIVE_P(bd) ((bd)->sign > 0) #define BIGDECIMAL_NEGATIVE_P(bd) ((bd)->sign < 0) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index b4098fda..fc448ed3 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -36,6 +36,9 @@ def check_bigdecimal_version(gemspec_path) have_type("struct RRational", "ruby.h") have_func("rb_rational_num", "ruby.h") have_func("rb_rational_den", "ruby.h") +have_type("struct RComplex", "ruby.h") +have_func("rb_complex_real", "ruby.h") +have_func("rb_complex_imag", "ruby.h") have_func("rb_array_const_ptr", "ruby.h") have_func("rb_sym2str", "ruby.h") have_func("rb_opts_exception_p", "ruby.h") From 79a819d205f06cf961fd16d7be1935d96a415cd1 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 9 Oct 2019 10:51:25 +0900 Subject: [PATCH 072/546] Drop Ruby 2.3 support --- .travis.yml | 4 ---- appveyor.yml | 4 ---- bigdecimal.gemspec | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 27a8eb2e..31336d8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,6 @@ script: matrix: include: - - name: "2.3" - rvm: 2.3 - name: "2.4" rvm: 2.4.5 - name: "2.5" @@ -24,5 +22,3 @@ matrix: rvm: 2.6 - name: "trunk" rvm: ruby-head - allow_failures: - - rvm: 2.3 diff --git a/appveyor.yml b/appveyor.yml index aeea860b..39034495 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,7 +18,3 @@ environment: - ruby_version: "25-x64" - ruby_version: "24" - ruby_version: "24-x64" -matrix: - allow_failures: - - ruby_version: "23" - - ruby_version: "23-x64" diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 53dbe911..e5b490a9 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |s| sample/pi.rb ] - s.required_ruby_version = Gem::Requirement.new(">= 2.3.0") + s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rake-compiler", ">= 0.9" From 05e843d8385fa438b83ee3ee54b0c50ab79dfd01 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 8 Oct 2019 15:23:46 -0700 Subject: [PATCH 073/546] Remove definition of BigDecimal#initialize_copy This leaves the default definition, which will raise FrozenError. --- ext/bigdecimal/bigdecimal.c | 3 --- test/bigdecimal/test_bigdecimal.rb | 10 ++++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index fc3f38ad..705c2a5c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3490,9 +3490,6 @@ Init_bigdecimal(void) /* instance methods */ - rb_undef_method(rb_cBigDecimal, "initialize_copy"); - rb_undef_method(rb_cBigDecimal, "initialize_clone"); - rb_undef_method(rb_cBigDecimal, "initialize_dup"); rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a2f05177..245f7c23 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1882,10 +1882,12 @@ def test_bug6406 EOS end - def test_no_initialize_copy - assert_equal(false, BigDecimal(1).respond_to?(:initialize_copy, true)) - assert_equal(false, BigDecimal(1).respond_to?(:initialize_dup, true)) - assert_equal(false, BigDecimal(1).respond_to?(:initialize_clone, true)) + def test_initialize_copy_dup_clone_frozen_error + bd = BigDecimal(1) + bd2 = BigDecimal(2) + assert_raise(FrozenError) { bd.send(:initialize_copy, bd2) } + assert_raise(FrozenError) { bd.send(:initialize_clone, bd2) } + assert_raise(FrozenError) { bd.send(:initialize_dup, bd2) } end def assert_no_memory_leak(code, *rest, **opt) From 9d19e842eef8306249209ecb797570ffb1ebf60a Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Wed, 9 Oct 2019 14:10:04 -0700 Subject: [PATCH 074/546] Make tests pass on Ruby 2.4 --- test/bigdecimal/test_bigdecimal.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 245f7c23..15cbe15d 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1885,9 +1885,10 @@ def test_bug6406 def test_initialize_copy_dup_clone_frozen_error bd = BigDecimal(1) bd2 = BigDecimal(2) - assert_raise(FrozenError) { bd.send(:initialize_copy, bd2) } - assert_raise(FrozenError) { bd.send(:initialize_clone, bd2) } - assert_raise(FrozenError) { bd.send(:initialize_dup, bd2) } + err = RUBY_VERSION >= '2.5' ? FrozenError : TypeError + assert_raise(err) { bd.send(:initialize_copy, bd2) } + assert_raise(err) { bd.send(:initialize_clone, bd2) } + assert_raise(err) { bd.send(:initialize_dup, bd2) } end def assert_no_memory_leak(code, *rest, **opt) From 1918d466f3be107e42e49c97f52f4e0af0574da2 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 18 Oct 2019 11:44:47 -0700 Subject: [PATCH 075/546] Remove taint checking This removes the taint checking. Taint support is deprecated in Ruby 2.7 and has no effect. I don't think removing the taint checks in earlier ruby versions will cause any problems. --- ext/bigdecimal/bigdecimal.c | 3 --- test/bigdecimal/test_bigdecimal.rb | 9 --------- 2 files changed, 12 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 705c2a5c..302ba80f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -300,7 +300,6 @@ GetVpValueWithPrec(VALUE v, long prec, int must) #ifdef ENABLE_NUMERIC_STRING case T_STRING: StringValueCStr(v); - rb_check_safe_obj(v); return VpCreateRbObject(RSTRING_LEN(v) + VpBaseFig() + 1, RSTRING_PTR(v)); #endif /* ENABLE_NUMERIC_STRING */ @@ -442,7 +441,6 @@ BigDecimal_load(VALUE self, VALUE str) unsigned long m=0; pch = (unsigned char *)StringValueCStr(str); - rb_check_safe_obj(str); /* First get max prec */ while((*pch) != (unsigned char)'\0' && (ch = *pch++) != (unsigned char)':') { if(!ISDIGIT(ch)) { @@ -2054,7 +2052,6 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) if (rb_scan_args(argc, argv, "01", &f) == 1) { if (RB_TYPE_P(f, T_STRING)) { psz = StringValueCStr(f); - rb_check_safe_obj(f); if (*psz == ' ') { fPlus = 1; psz++; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 15cbe15d..7eab8c95 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -158,15 +158,6 @@ def test_BigDecimal_with_big_decimal end end - def test_BigDecimal_with_tainted_string - Thread.new { - $SAFE = 1 - BigDecimal('1'.taint) - }.join - ensure - $SAFE = 0 - end - def test_BigDecimal_with_exception_keyword assert_raise(ArgumentError) { BigDecimal('.', exception: true) From 57ee92e70067d33bac89a190c6e47b969a13fc2e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 23 Dec 2019 11:07:23 +0900 Subject: [PATCH 076/546] Return US-ASCII string from BigDecimal#to_s Fixes #159 --- ext/bigdecimal/bigdecimal.c | 2 +- test/bigdecimal/test_bigdecimal.rb | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7255aea0..3204e42b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2070,7 +2070,7 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) nc += (nc + mc - 1) / mc + 1; } - str = rb_str_new(0, nc); + str = rb_usascii_str_new(0, nc); psz = RSTRING_PTR(str); if (fmt) { diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 89814af6..9ba5b23e 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1524,29 +1524,34 @@ def test_inf assert_equal(BigDecimal::SIGN_NEGATIVE_ZERO, (-1 / inf).sign) end + def assert_equal_us_ascii_string(a, b) + assert_equal(a, b) + assert_equal(Encoding::US_ASCII, b.encoding) + end + def test_to_special_string BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) nan = BigDecimal("NaN") - assert_equal("NaN", nan.to_s) + assert_equal_us_ascii_string("NaN", nan.to_s) inf = BigDecimal("Infinity") - assert_equal("Infinity", inf.to_s) - assert_equal(" Infinity", inf.to_s(" ")) - assert_equal("+Infinity", inf.to_s("+")) - assert_equal("-Infinity", (-inf).to_s) + assert_equal_us_ascii_string("Infinity", inf.to_s) + assert_equal_us_ascii_string(" Infinity", inf.to_s(" ")) + assert_equal_us_ascii_string("+Infinity", inf.to_s("+")) + assert_equal_us_ascii_string("-Infinity", (-inf).to_s) pzero = BigDecimal("0") - assert_equal("0.0", pzero.to_s) - assert_equal(" 0.0", pzero.to_s(" ")) - assert_equal("+0.0", pzero.to_s("+")) - assert_equal("-0.0", (-pzero).to_s) + assert_equal_us_ascii_string("0.0", pzero.to_s) + assert_equal_us_ascii_string(" 0.0", pzero.to_s(" ")) + assert_equal_us_ascii_string("+0.0", pzero.to_s("+")) + assert_equal_us_ascii_string("-0.0", (-pzero).to_s) end def test_to_string - assert_equal("0.01", BigDecimal("0.01").to_s("F")) + assert_equal_us_ascii_string("0.01", BigDecimal("0.01").to_s("F")) s = "0." + "0" * 100 + "1" - assert_equal(s, BigDecimal(s).to_s("F")) + assert_equal_us_ascii_string(s, BigDecimal(s).to_s("F")) s = "1" + "0" * 100 + ".0" - assert_equal(s, BigDecimal(s).to_s("F")) + assert_equal_us_ascii_string(s, BigDecimal(s).to_s("F")) end def test_ctov From 5cc033719dac9e5e51d051a74dd55b6c843eb6ce Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 25 Dec 2019 15:24:09 +0900 Subject: [PATCH 077/546] Revert "Merge pull request #160 from ruby/return_us_ascii_string" This reverts commit 23a458becb96dc01bea444650ef3f18258eb60d3, reversing changes made to 05850475e01e9584b72c14ba369331179d4b0910. --- ext/bigdecimal/bigdecimal.c | 2 +- test/bigdecimal/test_bigdecimal.rb | 29 ++++++++++++----------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 12cebb95..302ba80f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2091,7 +2091,7 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) nc += (nc + mc - 1) / mc + 1; } - str = rb_usascii_str_new(0, nc); + str = rb_str_new(0, nc); psz = RSTRING_PTR(str); if (fmt) { diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index d52232d9..7eab8c95 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1523,34 +1523,29 @@ def test_inf assert_equal(BigDecimal::SIGN_NEGATIVE_ZERO, (-1 / inf).sign) end - def assert_equal_us_ascii_string(a, b) - assert_equal(a, b) - assert_equal(Encoding::US_ASCII, b.encoding) - end - def test_to_special_string BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) nan = BigDecimal("NaN") - assert_equal_us_ascii_string("NaN", nan.to_s) + assert_equal("NaN", nan.to_s) inf = BigDecimal("Infinity") - assert_equal_us_ascii_string("Infinity", inf.to_s) - assert_equal_us_ascii_string(" Infinity", inf.to_s(" ")) - assert_equal_us_ascii_string("+Infinity", inf.to_s("+")) - assert_equal_us_ascii_string("-Infinity", (-inf).to_s) + assert_equal("Infinity", inf.to_s) + assert_equal(" Infinity", inf.to_s(" ")) + assert_equal("+Infinity", inf.to_s("+")) + assert_equal("-Infinity", (-inf).to_s) pzero = BigDecimal("0") - assert_equal_us_ascii_string("0.0", pzero.to_s) - assert_equal_us_ascii_string(" 0.0", pzero.to_s(" ")) - assert_equal_us_ascii_string("+0.0", pzero.to_s("+")) - assert_equal_us_ascii_string("-0.0", (-pzero).to_s) + assert_equal("0.0", pzero.to_s) + assert_equal(" 0.0", pzero.to_s(" ")) + assert_equal("+0.0", pzero.to_s("+")) + assert_equal("-0.0", (-pzero).to_s) end def test_to_string - assert_equal_us_ascii_string("0.01", BigDecimal("0.01").to_s("F")) + assert_equal("0.01", BigDecimal("0.01").to_s("F")) s = "0." + "0" * 100 + "1" - assert_equal_us_ascii_string(s, BigDecimal(s).to_s("F")) + assert_equal(s, BigDecimal(s).to_s("F")) s = "1" + "0" * 100 + ".0" - assert_equal_us_ascii_string(s, BigDecimal(s).to_s("F")) + assert_equal(s, BigDecimal(s).to_s("F")) end def test_ctov From 730da7899522e9bb70defd3d6be9bd9b9ad24a74 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 25 Dec 2019 15:33:03 +0900 Subject: [PATCH 078/546] Update README for 2.0.0 --- CHANGES.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 05f8a443..00612bcb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,40 @@ # CHANGES -## master +## 2.0.0 * Remove `BigDecimal.new` **Kenta Murata** +* Drop fat-gem support + + **Akira Matsuda** + +* Do not mutate frozen BigDecimal argument in BigMath.exp + + **Jeremy Evans** + +* Make Kernel#BigDecimal return argument if given correct type + [Bug #7522] + + **Jeremy Evans** + +* Undef BigDecimal#initialize_copy + + **Jeremy Evans** + +* Support conversion from Complex without the imaginary part + + **Kenta Murata** + +* Remove taint checking + + **Jeremy Evans** + +* Code maintenance + + **Nobuyoshi Nakada** + ## 1.4.4 * Fix String#to_d against the string with trailing "e" like "1e" From b4a9f984e8256e961b10f2c44bf6a689ce2c3a6b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 25 Dec 2019 15:24:54 +0900 Subject: [PATCH 079/546] Version 2.0.0 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index e5b490a9..30107dd5 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '2.0.0.dev' +bigdecimal_version = '2.0.0' Gem::Specification.new do |s| s.name = "bigdecimal" From 6c2c911c48497c9103df019a8f86e821f4ea3151 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 25 Dec 2019 15:25:32 +0900 Subject: [PATCH 080/546] Revert "Revert "Merge pull request #160 from ruby/return_us_ascii_string"" This reverts commit 5cc033719dac9e5e51d051a74dd55b6c843eb6ce. --- ext/bigdecimal/bigdecimal.c | 2 +- test/bigdecimal/test_bigdecimal.rb | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 302ba80f..12cebb95 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2091,7 +2091,7 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) nc += (nc + mc - 1) / mc + 1; } - str = rb_str_new(0, nc); + str = rb_usascii_str_new(0, nc); psz = RSTRING_PTR(str); if (fmt) { diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 7eab8c95..d52232d9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1523,29 +1523,34 @@ def test_inf assert_equal(BigDecimal::SIGN_NEGATIVE_ZERO, (-1 / inf).sign) end + def assert_equal_us_ascii_string(a, b) + assert_equal(a, b) + assert_equal(Encoding::US_ASCII, b.encoding) + end + def test_to_special_string BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) nan = BigDecimal("NaN") - assert_equal("NaN", nan.to_s) + assert_equal_us_ascii_string("NaN", nan.to_s) inf = BigDecimal("Infinity") - assert_equal("Infinity", inf.to_s) - assert_equal(" Infinity", inf.to_s(" ")) - assert_equal("+Infinity", inf.to_s("+")) - assert_equal("-Infinity", (-inf).to_s) + assert_equal_us_ascii_string("Infinity", inf.to_s) + assert_equal_us_ascii_string(" Infinity", inf.to_s(" ")) + assert_equal_us_ascii_string("+Infinity", inf.to_s("+")) + assert_equal_us_ascii_string("-Infinity", (-inf).to_s) pzero = BigDecimal("0") - assert_equal("0.0", pzero.to_s) - assert_equal(" 0.0", pzero.to_s(" ")) - assert_equal("+0.0", pzero.to_s("+")) - assert_equal("-0.0", (-pzero).to_s) + assert_equal_us_ascii_string("0.0", pzero.to_s) + assert_equal_us_ascii_string(" 0.0", pzero.to_s(" ")) + assert_equal_us_ascii_string("+0.0", pzero.to_s("+")) + assert_equal_us_ascii_string("-0.0", (-pzero).to_s) end def test_to_string - assert_equal("0.01", BigDecimal("0.01").to_s("F")) + assert_equal_us_ascii_string("0.01", BigDecimal("0.01").to_s("F")) s = "0." + "0" * 100 + "1" - assert_equal(s, BigDecimal(s).to_s("F")) + assert_equal_us_ascii_string(s, BigDecimal(s).to_s("F")) s = "1" + "0" * 100 + ".0" - assert_equal(s, BigDecimal(s).to_s("F")) + assert_equal_us_ascii_string(s, BigDecimal(s).to_s("F")) end def test_ctov From 14156fb10f8531eb25985d2ae54c8885da731454 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 25 Dec 2019 15:33:53 +0900 Subject: [PATCH 081/546] Update README for 2.0.1 --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 00612bcb..638a1d12 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 2.0.1 + +* Let BigDecimal#to_s return US-ASCII string + + **Kenta Murata** + ## 2.0.0 * Remove `BigDecimal.new` From 3fa4f2ac6787cee5bf037b4d833631d61c4a79a2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 25 Dec 2019 15:26:23 +0900 Subject: [PATCH 082/546] Version 2.0.1 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 30107dd5..6fa2b8d1 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '2.0.0' +bigdecimal_version = '2.0.1' Gem::Specification.new do |s| s.name = "bigdecimal" From 0e3fb22a4e816e28235e2541bcca8381232b5fe2 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 24 Sep 2019 20:59:12 -0700 Subject: [PATCH 083/546] Deprecate taint/trust and related methods, and make the methods no-ops This removes the related tests, and puts the related specs behind version guards. This affects all code in lib, including some libraries that may want to support older versions of Ruby. --- test/bigdecimal/test_bigdecimal.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index d52232d9..92541a60 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -158,6 +158,15 @@ def test_BigDecimal_with_big_decimal end end + def test_BigDecimal_with_tainted_string + Thread.new { + $SAFE = 1 + BigDecimal('1'.taint) + }.join + ensure + $SAFE = 0 + end + def test_BigDecimal_with_exception_keyword assert_raise(ArgumentError) { BigDecimal('.', exception: true) From a2f2860464fe597883d90cd473543533daec1b1f Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 14 Nov 2019 18:54:13 -0800 Subject: [PATCH 084/546] More fixes for $SAFE/taint post merging --- test/bigdecimal/test_bigdecimal.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 92541a60..4d535757 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -158,13 +158,15 @@ def test_BigDecimal_with_big_decimal end end - def test_BigDecimal_with_tainted_string - Thread.new { - $SAFE = 1 - BigDecimal('1'.taint) - }.join - ensure - $SAFE = 0 + if RUBY_VERSION < '2.7' + def test_BigDecimal_with_tainted_string + Thread.new { + $SAFE = 1 + BigDecimal('1'.taint) + }.join + ensure + $SAFE = 0 + end end def test_BigDecimal_with_exception_keyword From 283c9b4b87ad9ebd58013f9d9331d39188f1c097 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 28 Feb 2020 21:04:53 -0800 Subject: [PATCH 085/546] Suppress security alerts https://github.com/advisories/GHSA-jppv-gw3r-w3q8 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 6fa2b8d1..24042878 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -32,7 +32,7 @@ Gem::Specification.new do |s| s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") - s.add_development_dependency "rake", "~> 10.0" + s.add_development_dependency "rake", ">= 12.3.3" s.add_development_dependency "rake-compiler", ">= 0.9" s.add_development_dependency "minitest", "< 5.0.0" s.add_development_dependency "pry" From e64bc32cd4409980f97e5e3a6f4a1e3549955cc3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 8 Apr 2020 16:06:30 +0900 Subject: [PATCH 086/546] Suppress -Wshorten-64-to-32 warnings --- ext/bigdecimal/bigdecimal.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 12cebb95..4949c606 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -367,8 +367,8 @@ BigDecimal_prec(VALUE self) VALUE obj; GUARD_OBJ(p, GetVpValue(self, 1)); - obj = rb_assoc_new(INT2NUM(p->Prec*VpBaseFig()), - INT2NUM(p->MaxPrec*VpBaseFig())); + obj = rb_assoc_new(SIZET2NUM(p->Prec*VpBaseFig()), + SIZET2NUM(p->MaxPrec*VpBaseFig())); return obj; } @@ -2156,7 +2156,7 @@ BigDecimal_split(VALUE self) rb_ary_push(obj, str); rb_str_resize(str, strlen(psz1)); rb_ary_push(obj, INT2FIX(10)); - rb_ary_push(obj, INT2NUM(e)); + rb_ary_push(obj, SSIZET2NUM(e)); return obj; } @@ -2169,7 +2169,7 @@ static VALUE BigDecimal_exponent(VALUE self) { ssize_t e = VpExponent10(GetVpValue(self, 1)); - return INT2NUM(e); + return SSIZET2NUM(e); } /* Returns a string representation of self. @@ -2802,7 +2802,7 @@ static VALUE BigDecimal_limit(int argc, VALUE *argv, VALUE self) { VALUE nFig; - VALUE nCur = INT2NUM(VpGetPrecLimit()); + VALUE nCur = SIZET2NUM(VpGetPrecLimit()); if (rb_scan_args(argc, argv, "01", &nFig) == 1) { int nf; From 352429bd7f25e03487a2a91222c47062aee0dd64 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 13 May 2020 13:49:21 +0900 Subject: [PATCH 087/546] ext/bigdecimal/bigdecimal.c, ext/date/date_core.c: undef NDEBUG `#define NDEBUG` produces "macro redefined" warnings when it is already defined via cppflags --- ext/bigdecimal/bigdecimal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4949c606..e0e1c683 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -14,6 +14,7 @@ #include "ruby/util.h" #ifndef BIGDECIMAL_DEBUG +# undef NDEBUG # define NDEBUG #endif #include From 77e2d7b3d64ac665ee141fe8a28289b7284602d9 Mon Sep 17 00:00:00 2001 From: pavel Date: Sun, 16 Aug 2020 21:18:10 +0200 Subject: [PATCH 088/546] remove duplicated include --- ext/bigdecimal/bigdecimal.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e0e1c683..c1c0f65f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -25,7 +25,6 @@ #include #include #include -#include "math.h" #ifdef HAVE_IEEEFP_H #include From 4c4402f48608dec290a27de20d1baa6b9a8111ea Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 18 Aug 2020 20:22:07 +0900 Subject: [PATCH 089/546] Fixed the invalid SPDX identifier on gemspec --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 24042878..5cf0726e 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.summary = "Arbitrary-precision decimal floating-point number library." s.description = "This library provides arbitrary-precision decimal floating-point number class." s.homepage = "https://github.com/ruby/bigdecimal" - s.license = "ruby" + s.license = "Ruby" s.require_paths = %w[lib] s.extensions = %w[ext/bigdecimal/extconf.rb] From e8fc98050167fd943574609988b8754414e0a7c1 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 11 Sep 2020 16:12:40 -0700 Subject: [PATCH 090/546] Make BigDecimal#round with argument < 1 return Integer Fixes [Bug #12780] --- ext/bigdecimal/bigdecimal.c | 9 ++++++--- test/bigdecimal/test_bigdecimal.rb | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e0e1c683..0c37e71d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1792,10 +1792,10 @@ BigDecimal_fix(VALUE self) * more than that many digits. * * If n is specified and negative, at least that many digits to the left of the - * decimal point will be 0 in the result. + * decimal point will be 0 in the result, and return value will be an Integer. * * BigDecimal('3.14159').round(3) #=> 3.142 - * BigDecimal('13345.234').round(-2) #=> 13300.0 + * BigDecimal('13345.234').round(-2) #=> 13300 * * The value of the optional mode argument can be used to determine how * rounding is performed; see BigDecimal.mode. @@ -1808,6 +1808,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) int iLoc = 0; VALUE vLoc; VALUE vRound; + int round_to_int = 0; size_t mx, pl; unsigned short sw = VpGetRoundMode(); @@ -1815,6 +1816,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) switch (rb_scan_args(argc, argv, "02", &vLoc, &vRound)) { case 0: iLoc = 0; + round_to_int = 1; break; case 1: if (RB_TYPE_P(vLoc, T_HASH)) { @@ -1822,6 +1824,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) } else { iLoc = NUM2INT(vLoc); + if (iLoc < 1) round_to_int = 1; } break; case 2: @@ -1843,7 +1846,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) GUARD_OBJ(c, VpCreateRbObject(mx, "0")); VpSetPrecLimit(pl); VpActiveRound(c, a, sw, iLoc); - if (argc == 0) { + if (round_to_int) { return BigDecimal_to_i(ToValue(c)); } return ToValue(c); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 4d535757..bb8d9a72 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1100,6 +1100,11 @@ def test_round assert_equal(-1, x.round(0, BigDecimal::ROUND_HALF_DOWN), bug3803) assert_equal(-1, x.round(0, BigDecimal::ROUND_HALF_EVEN), bug3803) end + + assert_instance_of(Integer, x.round) + assert_instance_of(Integer, x.round(0)) + assert_instance_of(Integer, x.round(-1)) + assert_instance_of(BigDecimal, x.round(1)) end def test_round_half_even From 78b513d4e0f9b49681ec82a244ca7a36464305dd Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 14 Dec 2020 14:31:13 +0900 Subject: [PATCH 091/546] Stop using Travis CI --- .travis.yml | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 31336d8d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -notification: - email: - - mrkn@ruby-lang.org - -language: ruby - -before_install: - - gem install bundler -v 1.17.2 - -script: - - rake travis - - rake install - -matrix: - include: - - name: "2.4" - rvm: 2.4.5 - - name: "2.5" - rvm: 2.5.2 - - name: "2.6" - rvm: 2.6 - - name: "trunk" - rvm: ruby-head From 6fbf59909acaf8e1d60358af752e04ccdfbc4141 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 14 Dec 2020 14:31:24 +0900 Subject: [PATCH 092/546] Stop using Appveyor --- appveyor.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 39034495..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -image: Visual Studio 2017 -clone_depth: 10 -install: - - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - - bundle install -build_script: - - rake -rdevkit compile -test_script: - - rake test - - rake install -deploy: off -environment: - matrix: - - ruby_version: "26" - - ruby_version: "26-x64" - - ruby_version: "25" - - ruby_version: "25-x64" - - ruby_version: "24" - - ruby_version: "24-x64" From 73e25132bcd81869e3f16df9eb221e1603e36dde Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 14 Dec 2020 14:31:43 +0900 Subject: [PATCH 093/546] Use GitHub Actions for CI --- .github/workflows/ci.yml | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..4b7a5488 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI + +on: +- push +- pull_request + +jobs: + host: + name: ${{ matrix.os }} ${{ matrix.ruby }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-20.04 + - ubuntu-18.04 + - macos-11.0 + - macos-10.15 + - windows-latest + ruby: + - 3.0.0-preview2 + - 2.7 + - 2.6 + - 2.5 + - 2.4 + - debug + include: + - { os: windows-latest , ruby: mingw } + - { os: windows-latest , ruby: mswin } + exclude: + - { os: windows-latest , ruby: 3.0.0-preview2 } + - { os: windows-latest , ruby: debug } + + steps: + - uses: actions/checkout@v2 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - run: bundle install + + - run: rake compile + + - run: rake build + + - run: rake test + + - run: gem install pkg/*.gem From 6e8944e130abd3c0fc8ddcbfb2120d9fd97338bb Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 14 Dec 2020 15:10:17 +0900 Subject: [PATCH 094/546] Replace the status badge --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 15d2ec74..75a72aff 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # BigDecimal -[![Build Status](https://travis-ci.org/ruby/bigdecimal.svg?branch=master)](https://travis-ci.org/ruby/bigdecimal) -[![Build status](https://ci.appveyor.com/api/projects/status/4j596bhldmmtcjpf/branch/master?svg=true)](https://ci.appveyor.com/project/ruby/bigdecimal/branch/master) +![CI](https://github.com/ruby/bigdecimal/workflows/CI/badge.svg?branch=master&event=push) BigDecimal provides an arbitrary-precision decimal floating-point number class. From b718dba11430b735fe32cdd2cdb43b2875aa8d24 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 24 Nov 2020 09:52:45 -0800 Subject: [PATCH 095/546] Use a higher default precision for BigDecimal#power and #** When a fractional power is given, increase the precision if the precision isn't specified via power's second argument: Float: increase by 15 (rough number of decimal precision in float) BigDecimal: increase by adding similar precision modifier as done to calculate the base precision. Rational: double the precision, since a BigDecimal is created, but the created BigDecimal uses the same precision. Increasing the precision for these power calculations has the obvious tradeoff of making the calculations slower. Fixes Ruby Bug #17264 --- ext/bigdecimal/bigdecimal.c | 10 ++++++++++ test/bigdecimal/test_bigdecimal.rb | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0c37e71d..293a2792 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2366,6 +2366,9 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) } goto retry; } + if (NIL_P(prec)) { + n += 15; + } exp = GetVpValueWithPrec(vexp, DBL_DIG+1, 1); break; @@ -2381,6 +2384,9 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) goto retry; } exp = GetVpValueWithPrec(vexp, n, 1); + if (NIL_P(prec)) { + n += n; + } break; case T_DATA: @@ -2391,6 +2397,10 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) vexp = BigDecimal_to_i(vexp); goto retry; } + if (NIL_P(prec)) { + GUARD_OBJ(y, GetVpValue(vexp, 1)); + n += y->Prec*VpBaseFig(); + } exp = DATA_PTR(vexp); break; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index bb8d9a72..7ac38131 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1457,8 +1457,13 @@ def test_power_of_negative_infinity def test_power_without_prec pi = BigDecimal("3.14159265358979323846264338327950288419716939937511") e = BigDecimal("2.71828182845904523536028747135266249775724709369996") - pow = BigDecimal("22.4591577183610454734271522045437350275893151339967843873233068") + pow = BigDecimal("0.2245915771836104547342715220454373502758931513399678438732330680117143493477164265678321738086407229773690574073268002736527e2") assert_equal(pow, pi.power(e)) + + n = BigDecimal("2222") + assert_equal(BigDecimal("0.517135308457252589249242e12"), (n ** 3.5)) + assert_equal(BigDecimal("0.517135308457252592e12"), (n ** 3.5r)) + assert_equal(BigDecimal("0.517135308457252589249241582e12"), (n ** BigDecimal("3.5",15))) end def test_power_with_prec From 4b4d0f7f851c7d2e54d6b26af5623ae3e266b678 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 14 Dec 2020 14:23:24 +0900 Subject: [PATCH 096/546] Use DBLE_FIG for a Float value --- ext/bigdecimal/bigdecimal.c | 4 ++-- test/bigdecimal/test_bigdecimal.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 293a2792..c58877ca 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2367,9 +2367,9 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) goto retry; } if (NIL_P(prec)) { - n += 15; + n += DBLE_FIG; } - exp = GetVpValueWithPrec(vexp, DBL_DIG+1, 1); + exp = GetVpValueWithPrec(vexp, DBLE_FIG, 1); break; case T_RATIONAL: diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 7ac38131..2b115728 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1461,7 +1461,7 @@ def test_power_without_prec assert_equal(pow, pi.power(e)) n = BigDecimal("2222") - assert_equal(BigDecimal("0.517135308457252589249242e12"), (n ** 3.5)) + assert_equal(BigDecimal("0.5171353084572525892492416e12"), (n ** 3.5)) assert_equal(BigDecimal("0.517135308457252592e12"), (n ** 3.5r)) assert_equal(BigDecimal("0.517135308457252589249241582e12"), (n ** BigDecimal("3.5",15))) end From f90d4fb7545127942279a78701d5eebbcbd323dc Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Sat, 10 Oct 2020 15:20:42 +0900 Subject: [PATCH 097/546] Fixed typo in comment alway -> always --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 10323d5c..6a3e7ae1 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4183,7 +4183,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) /* at least mx digits. */ /* szVal==NULL ==> allocate zero value. */ vp = VpAllocReal(mx); - /* xmalloc() alway returns(or throw interruption) */ + /* xmalloc() always returns(or throw interruption) */ vp->MaxPrec = mx; /* set max precision */ VpSetZero(vp, 1); /* initialize vp to zero. */ return vp; @@ -4359,7 +4359,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) nalloc = Max(nalloc, mx); mx = nalloc; vp = VpAllocReal(mx); - /* xmalloc() alway returns(or throw interruption) */ + /* xmalloc() always returns(or throw interruption) */ vp->MaxPrec = mx; /* set max precision */ VpSetZero(vp, sign); VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne); From 326884e86ff670fd2d8ebf444d1421d05d96c293 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 12 Jul 2020 17:30:29 +0900 Subject: [PATCH 098/546] bidecimal: improve tests' independence (#3297) Tests depending on the rounding mode must specify the appropriate rounding mode and restore to the original mode at the end. --- test/bigdecimal/test_bigdecimal.rb | 4 ++++ test/bigdecimal/test_bigdecimal_util.rb | 18 +++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 2b115728..c8e70acf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -334,6 +334,8 @@ def test_save_exception_mode end def test_save_rounding_mode + saved_mode = BigDecimal.mode(BigDecimal::ROUND_MODE) + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_FLOOR) BigDecimal.save_rounding_mode do BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_EVEN) @@ -341,6 +343,8 @@ def test_save_rounding_mode assert_equal(BigDecimal::ROUND_FLOOR, BigDecimal.mode(BigDecimal::ROUND_MODE)) assert_equal(42, BigDecimal.save_rounding_mode { 42 }) + ensure + BigDecimal.mode(BigDecimal::ROUND_MODE, saved_mode) end def test_save_limit diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index c4d58169..7c0830e9 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -61,15 +61,19 @@ def test_Rational_to_d_with_negative_precision end def test_Complex_to_d - assert_equal(BigDecimal("1"), Complex(1, 0).to_d) - assert_equal(BigDecimal("0.333333333333333333333"), - Complex(1.quo(3), 0).to_d(21)) - assert_equal(BigDecimal("0.1234567"), Complex(0.1234567, 0).to_d) - assert_equal(BigDecimal("0.1235"), Complex(0.1234567, 0).to_d(4)) + BigDecimal.save_rounding_mode do + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_EVEN) - assert_raise_with_message(ArgumentError, "can't omit precision for a Rational.") { Complex(1.quo(3), 0).to_d } + assert_equal(BigDecimal("1"), Complex(1, 0).to_d) + assert_equal(BigDecimal("0.333333333333333333333"), + Complex(1.quo(3), 0).to_d(21)) + assert_equal(BigDecimal("0.1234567"), Complex(0.1234567, 0).to_d) + assert_equal(BigDecimal("0.1235"), Complex(0.1234567, 0).to_d(4)) - assert_raise_with_message(ArgumentError, "Unable to make a BigDecimal from non-zero imaginary number") { Complex(1, 1).to_d } + assert_raise_with_message(ArgumentError, "can't omit precision for a Rational.") { Complex(1.quo(3), 0).to_d } + + assert_raise_with_message(ArgumentError, "Unable to make a BigDecimal from non-zero imaginary number") { Complex(1, 1).to_d } + end end def test_String_to_d From 4aa047d91edf6dcd377c8d9a362c23311e2c564f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 15 Dec 2020 12:33:51 +0900 Subject: [PATCH 099/546] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75a72aff..3fdaf698 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The differences among versions are given below: | version | characteristics | Supported ruby version range | | ------- | --------------- | ----------------------- | -| 2.0.0 | You cannot use BigDecimal.new and do subclassing | 2.4 .. | +| 2.0.x | You cannot use BigDecimal.new and do subclassing | 2.4 .. | | 1.4.x | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.6 | | 1.3.5 | You can use BigDecimal.new and subclassing without warning | .. 2.5 | From 2182dac7154ea973808b0b87d8ef69d51fd1959a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 15 Dec 2020 12:39:09 +0900 Subject: [PATCH 100/546] Version 2.0.1 --- CHANGES.md | 15 +++++++++++++++ bigdecimal.gemspec | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 638a1d12..d8817989 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,20 @@ # CHANGES +## 2.0.2 + +* Deprecate taint/trust and related methods, and make the methods no-ops + + **Jeremy Evans** + +* Make BigDecimal#round with argument < 1 return Integer + + **Jeremy Evans** + +* Use higher default precision for BigDecimal#power and #** + + **Jeremy Evans** + **Kenta Murata** + ## 2.0.1 * Let BigDecimal#to_s return US-ASCII string diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 5cf0726e..980deb07 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '2.0.1' +bigdecimal_version = '2.0.2' Gem::Specification.new do |s| s.name = "bigdecimal" From 480a8e225e553203e83d9b0a5205a1896e84c5c8 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 18 Dec 2020 23:46:26 +0900 Subject: [PATCH 101/546] Fix BigDecimal#* with a Float Fix https://github.com/ruby/bigdecimal/issues/70 [Bug #13331] --- ext/bigdecimal/bigdecimal.c | 2 +- test/bigdecimal/test_bigdecimal.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index bc7fcc6e..3d6bc22f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1278,7 +1278,7 @@ BigDecimal_mult(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBL_DIG+1, 1); + b = GetVpValueWithPrec(r, DBL_DIG, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index c8e70acf..a4843a97 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -860,6 +860,12 @@ def test_mult_with_float assert_kind_of(BigDecimal, BigDecimal("3") * 1.5) end + def test_mult_with_float_bug13331 + assert_equal(BigDecimal(64.4, Float::DIG), + BigDecimal(1) * 64.4, + "[ruby-core:80234] [Bug #13331]") + end + def test_mult_with_rational assert_kind_of(BigDecimal, BigDecimal("3") * 1.quo(3)) end From 7860c5b1b219ecd79a71e650bfc75d64cb35d405 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 00:31:20 +0900 Subject: [PATCH 102/546] Revert "Fix BigDecimal#* with a Float" This reverts commit 480a8e225e553203e83d9b0a5205a1896e84c5c8. --- ext/bigdecimal/bigdecimal.c | 2 +- test/bigdecimal/test_bigdecimal.rb | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 3d6bc22f..bc7fcc6e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1278,7 +1278,7 @@ BigDecimal_mult(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBL_DIG, 1); + b = GetVpValueWithPrec(r, DBL_DIG+1, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a4843a97..c8e70acf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -860,12 +860,6 @@ def test_mult_with_float assert_kind_of(BigDecimal, BigDecimal("3") * 1.5) end - def test_mult_with_float_bug13331 - assert_equal(BigDecimal(64.4, Float::DIG), - BigDecimal(1) * 64.4, - "[ruby-core:80234] [Bug #13331]") - end - def test_mult_with_rational assert_kind_of(BigDecimal, BigDecimal("3") * 1.quo(3)) end From aa536cd4b585ff6045cf91f396bc3eef993b91f2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 00:34:40 +0900 Subject: [PATCH 103/546] Fix the default precision of Float#to_d Fix https://github.com/ruby/bigdecimal/issues/70 [Bug #13331] --- ext/bigdecimal/bigdecimal.c | 2 +- lib/bigdecimal/util.rb | 2 +- test/bigdecimal/test_bigdecimal_util.rb | 16 ++++++++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index bc7fcc6e..c1a2fc26 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1278,7 +1278,7 @@ BigDecimal_mult(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBL_DIG+1, 1); + b = GetVpValueWithPrec(r, DBLE_FIG, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index 66fff786..00a3e967 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -43,7 +43,7 @@ class Float < Numeric # # See also BigDecimal::new. # - def to_d(precision=Float::DIG) + def to_d(precision=Float::DIG+1) BigDecimal(self, precision) end end diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 7c0830e9..b855fd58 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -17,10 +17,12 @@ def test_Integer_to_d end def test_Float_to_d_without_precision - delta = 1.0/10**(Float::DIG) - assert_in_delta(BigDecimal(0.5, Float::DIG), 0.5.to_d, delta) - assert_in_delta(BigDecimal(355.0/113.0, Float::DIG), (355.0/113.0).to_d, delta) - assert_equal(9.05.to_d.to_s('F'), "9.05") + delta = 1.0/10**(Float::DIG+1) + assert_in_delta(BigDecimal(0.5, Float::DIG+1), 0.5.to_d, delta) + assert_in_delta(BigDecimal(355.0/113.0, Float::DIG+1), (355.0/113.0).to_d, delta) + + assert_equal(9.05, 9.05.to_d.to_f) + assert_equal("9.050000000000001", 9.05.to_d.to_s('F')) bug9214 = '[ruby-core:58858]' assert_equal((-0.0).to_d.sign, -1, bug9214) @@ -43,6 +45,12 @@ def test_Float_to_d_with_precision assert(1.1.to_d(digits).frozen?) end + def test_Float_to_d_bug13331 + assert_equal(64.4.to_d, + 1.to_d * 64.4, + "[ruby-core:80234] [Bug #13331]") + end + def test_Rational_to_d digits = 100 delta = 1.0/10**(digits) From 12296dcb9063f20d39a247306e170fe91efb6eed Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 01:30:37 +0900 Subject: [PATCH 104/546] Use DBLE_FIG --- ext/bigdecimal/bigdecimal.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c1a2fc26..25417242 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -77,7 +77,7 @@ static ID id_half; #define BASE1 (BASE/10) #ifndef DBLE_FIG -#define DBLE_FIG (DBL_DIG+1) /* figure of double */ +#define DBLE_FIG rmpd_double_figures() /* figure of double */ #endif #ifndef RRATIONAL_ZERO_P @@ -252,7 +252,7 @@ GetVpValueWithPrec(VALUE v, long prec, int must) switch(TYPE(v)) { case T_FLOAT: if (prec < 0) goto unable_to_coerce_without_prec; - if (prec > DBL_DIG+1) goto SomeOneMayDoIt; + if (prec > (long)DBLE_FIG) goto SomeOneMayDoIt; d = RFLOAT_VALUE(v); if (!isfinite(d)) { pv = VpCreateRbObject(1, NULL); @@ -899,7 +899,7 @@ BigDecimal_coerce(VALUE self, VALUE other) Real *b; if (RB_TYPE_P(other, T_FLOAT)) { - GUARD_OBJ(b, GetVpValueWithPrec(other, DBL_DIG+1, 1)); + GUARD_OBJ(b, GetVpValueWithPrec(other, DBLE_FIG, 1)); obj = rb_assoc_new(ToValue(b), self); } else { @@ -957,7 +957,7 @@ BigDecimal_add(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBL_DIG+1, 1); + b = GetVpValueWithPrec(r, DBLE_FIG, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1015,7 +1015,7 @@ BigDecimal_sub(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self,1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBL_DIG+1, 1); + b = GetVpValueWithPrec(r, DBLE_FIG, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1065,7 +1065,7 @@ BigDecimalCmp(VALUE self, VALUE r,char op) break; case T_FLOAT: - GUARD_OBJ(b, GetVpValueWithPrec(r, DBL_DIG+1, 0)); + GUARD_OBJ(b, GetVpValueWithPrec(r, DBLE_FIG, 0)); break; case T_RATIONAL: @@ -1306,7 +1306,7 @@ BigDecimal_divide(Real **c, Real **res, Real **div, VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBL_DIG+1, 1); + b = GetVpValueWithPrec(r, DBLE_FIG, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1372,7 +1372,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBL_DIG+1, 1); + b = GetVpValueWithPrec(r, DBLE_FIG, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1473,7 +1473,7 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBL_DIG+1, 1); + b = GetVpValueWithPrec(r, DBLE_FIG, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -2685,7 +2685,7 @@ VpNewVarArg(int argc, VALUE *argv) VpDtoV(pv, d); return pv; } - if (mf > DBL_DIG+1) { + if (mf > DBLE_FIG) { if (!exc) { return NULL; } @@ -2980,7 +2980,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) infinite = isinf(flo); nan = isnan(flo); if (!infinite && !nan) { - vx = GetVpValueWithPrec(x, DBL_DIG+1, 0); + vx = GetVpValueWithPrec(x, DBLE_FIG, 0); } break; @@ -3133,7 +3133,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) infinite = isinf(flo); nan = isnan(flo); if (!zero && !negative && !infinite && !nan) { - vx = GetVpValueWithPrec(x, DBL_DIG+1, 1); + vx = GetVpValueWithPrec(x, DBLE_FIG, 1); } break; @@ -4023,7 +4023,7 @@ VpNumOfChars(Real *vp,const char *pszFmt) * by one BDIGIT word in the computer used. * * [Returns] - * 1+DBL_DIG ... OK + * DBLE_FIG ... OK */ VP_EXPORT size_t VpInit(BDIGIT BaseVal) From 486bb20d9d6ba8149676a5a86931688d714d360b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 02:29:18 +0900 Subject: [PATCH 105/546] Use rb_undef_alloc_func to undefine allocate https://github.com/ruby/ruby/commit/d5ab8e8562 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 25417242..9952193e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3367,7 +3367,7 @@ Init_bigdecimal(void) rb_define_global_function("BigDecimal", f_BigDecimal, -1); /* Class methods */ - rb_undef_method(CLASS_OF(rb_cBigDecimal), "allocate"); + rb_undef_alloc_func(rb_cBigDecimal); rb_undef_method(CLASS_OF(rb_cBigDecimal), "new"); rb_define_singleton_method(rb_cBigDecimal, "interpret_loosely", BigDecimal_s_interpret_loosely, 1); rb_define_singleton_method(rb_cBigDecimal, "mode", BigDecimal_mode, -1); From fb2c7b008ffd1592dfa9cfa2bdeb0394ac9be573 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 02:44:20 +0900 Subject: [PATCH 106/546] Fix test for 486bb20d9d https://github.com/ruby/ruby/commit/7b06085c7b --- test/bigdecimal/test_bigdecimal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index c8e70acf..a2af5533 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -231,7 +231,7 @@ def test_s_ver end def test_s_allocate - assert_raise_with_message(NoMethodError, /undefined method `allocate'/) { BigDecimal.allocate } + assert_raise_with_message(TypeError, /allocator undefined for BigDecimal/) { BigDecimal.allocate } end def test_s_new From 93fc39264089c4fb50311f619949e51a0280e30f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 02:45:47 +0900 Subject: [PATCH 107/546] Make bigdecimal Ractor safe --- ext/bigdecimal/bigdecimal.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 9952193e..039a4d73 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -189,11 +189,16 @@ BigDecimal_memsize(const void *ptr) return (sizeof(*pv) + pv->MaxPrec * sizeof(BDIGIT)); } +#ifndef HAVE_RB_EXT_RACTOR_SAFE +# undef RUBY_TYPED_FROZEN_SHAREABLE +# define RUBY_TYPED_FROZEN_SHAREABLE 0 +#endif + static const rb_data_type_t BigDecimal_data_type = { "BigDecimal", { 0, BigDecimal_delete, BigDecimal_memsize, }, #ifdef RUBY_TYPED_FREE_IMMEDIATELY - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE #endif }; @@ -3351,6 +3356,9 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) void Init_bigdecimal(void) { +#ifdef HAVE_RB_EXT_RACTOR_SAFE + rb_ext_ractor_safe(true); +#endif VALUE arg; id_BigDecimal_exception_mode = rb_intern_const("BigDecimal.exception_mode"); @@ -3617,6 +3625,9 @@ static void VpFormatSt(char *psz, size_t fFmt); static int VpRdup(Real *m, size_t ind_m); #ifdef BIGDECIMAL_DEBUG +# ifdef HAVE_RB_EXT_RACTOR_SAFE +# error Need to make rewiting gnAlloc atomic +# endif static int gnAlloc = 0; /* Memory allocation counter */ #endif /* BIGDECIMAL_DEBUG */ From a90d13c4d0ae28e8412130ddfd918eab738a96e7 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 04:24:57 +0900 Subject: [PATCH 108/546] Add a test of Ractor --- test/bigdecimal/test_ractor.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/bigdecimal/test_ractor.rb diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb new file mode 100644 index 00000000..3ccd7c80 --- /dev/null +++ b/test/bigdecimal/test_ractor.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +require_relative "testbase" + +class TestBigDecimalRactor < Test::Unit::TestCase + include TestBigDecimalBase + + def setup + super + skip unless defined? Ractor + end + + def test_ractor_shareable + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + require "bigdecimal" + r = Ractor.new BigDecimal(Math::PI, Float::DIG+1) do |pi| + BigDecimal('2.0')*pi + end + assert_equal(2*Math::PI, r.take) + end; + end +end From 3b5bc9b918c3fdc3edf52e464f166ef60f491358 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 11:17:21 +0900 Subject: [PATCH 109/546] Stop using 3.0.0-preview2 for CI --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b7a5488..ad0328be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ jobs: - macos-10.15 - windows-latest ruby: - - 3.0.0-preview2 - 2.7 - 2.6 - 2.5 From 458eb66c49e227a0349c1f9d5b85614703f3d08f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 04:24:15 +0900 Subject: [PATCH 110/546] Add BigDecimal#precision --- ext/bigdecimal/bigdecimal.c | 68 ++++++++++++++++++++++++++++++ test/bigdecimal/test_bigdecimal.rb | 49 +++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 039a4d73..ef975a01 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -377,6 +377,73 @@ BigDecimal_prec(VALUE self) return obj; } +/* + * call-seq: + * big_decimal.precision -> intreger + * + * Returns the number of decimal digits in this number. + * + * Example: + * + * BigDecimal("0").precision # => 0 + * BigDecimal("1").precision # => 1 + * BigDecimal("-1e20").precision # => 21 + * BigDecimal("1e-20").precision # => 20 + * BigDecimal("Infinity").precision # => 0 + * BigDecimal("-Infinity").precision # => 0 + * BigDecimal("NaN").precision # => 0 + */ +static VALUE +BigDecimal_precision(VALUE self) +{ + ENTER(1); + + Real *p; + GUARD_OBJ(p, GetVpValue(self, 1)); + + /* + * The most significant digit is frac[0], and the least significant digit is frac[Prec-1]. + * When the exponent is zero, the decimal point is located just before frac[0]. + * When the exponent is negative, the decimal point moves to leftward. + * Conversely, when the exponent is positive, the decimal point moves to rightward. + * + * | frac[0] frac[1] frac[2] . frac[3] frac[4] ... frac[Prec-1] + * |------------------------> exponent == 3 + */ + + ssize_t ex = p->exponent; + ssize_t precision; + if (ex < 0) { + precision = (-ex + 1) * BASE_FIG; /* 1 is for p->frac[0] */ + ex = 0; + } + else if (p->Prec > 0) { + BDIGIT x = p->frac[0]; + for (precision = 0; x > 0; x /= 10) { + ++precision; + } + } + + if (ex > (ssize_t)p->Prec) { + precision += (ex - 1) * BASE_FIG; + } + else if (p->Prec > 0) { + ssize_t n = (ssize_t)p->Prec - 1; + while (n > 0 && p->frac[n] == 0) --n; + + precision += n * BASE_FIG; + + if (ex < (ssize_t)p->Prec) { + BDIGIT x = p->frac[n]; + for (; x > 0 && x % 10 == 0; x /= 10) { + --precision; + } + } + } + + return SSIZET2NUM(precision); +} + /* * call-seq: hash * @@ -3509,6 +3576,7 @@ Init_bigdecimal(void) /* instance methods */ rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); + rb_define_method(rb_cBigDecimal, "precision", BigDecimal_precision, 0); rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2); rb_define_method(rb_cBigDecimal, "sub", BigDecimal_sub2, 2); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a2af5533..59726ada 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1903,6 +1903,55 @@ def test_bug6406 EOS end + def test_precision_only_integer + assert_equal(0, BigDecimal(0).precision) + assert_equal(1, BigDecimal(1).precision) + assert_equal(1, BigDecimal(-1).precision) + assert_equal(2, BigDecimal(10).precision) + assert_equal(2, BigDecimal(-10).precision) + assert_equal(21, BigDecimal(100_000_000_000_000_000_000).precision) + assert_equal(21, BigDecimal(-100_000_000_000_000_000_000).precision) + assert_equal(103, BigDecimal("111e100").precision) + assert_equal(103, BigDecimal("-111e100").precision) + end + + def test_precision_only_fraction + assert_equal(1, BigDecimal("0.1").precision) + assert_equal(1, BigDecimal("-0.1").precision) + assert_equal(1, BigDecimal("0.01").precision) + assert_equal(1, BigDecimal("-0.01").precision) + assert_equal(2, BigDecimal("0.11").precision) + assert_equal(2, BigDecimal("-0.11").precision) + assert_equal(21, BigDecimal("0.000_000_000_000_000_000_001").precision) + assert_equal(21, BigDecimal("-0.000_000_000_000_000_000_001").precision) + assert_equal(100, BigDecimal("111e-100").precision) + assert_equal(100, BigDecimal("-111e-100").precision) + end + + def test_precision_full + assert_equal(1, BigDecimal("0.1").precision) + assert_equal(1, BigDecimal("-0.1").precision) + assert_equal(1, BigDecimal("0.01").precision) + assert_equal(1, BigDecimal("-0.01").precision) + assert_equal(2, BigDecimal("0.11").precision) + assert_equal(2, BigDecimal("-0.11").precision) + assert_equal(5, BigDecimal("11111e-2").precision) + assert_equal(5, BigDecimal("-11111e-2").precision) + assert_equal(21, BigDecimal("100.000_000_000_000_000_001").precision) + assert_equal(21, BigDecimal("-100.000_000_000_000_000_001").precision) + end + + def test_precision_special + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + + assert_equal(0, BigDecimal("Infinity").precision) + assert_equal(0, BigDecimal("-Infinity").precision) + assert_equal(0, BigDecimal("NaN").precision) + end + end + def test_initialize_copy_dup_clone_frozen_error bd = BigDecimal(1) bd2 = BigDecimal(2) From 7e80e6e1457b468d3f8fb66d227d85609ac2b392 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 11:55:46 +0900 Subject: [PATCH 111/546] Make BigDecimal#precs deprecated --- ext/bigdecimal/bigdecimal.c | 4 ++++ ext/bigdecimal/bigdecimal.h | 4 ++++ ext/bigdecimal/extconf.rb | 2 ++ test/bigdecimal/test_bigdecimal.rb | 22 ++++++++++++++++------ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index ef975a01..f84aea4f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -371,6 +371,10 @@ BigDecimal_prec(VALUE self) Real *p; VALUE obj; + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, + "BigDecimal#precs is deprecated and will be removed in the future; " + "use BigDecimal#precision instead."); + GUARD_OBJ(p, GetVpValue(self, 1)); obj = rb_assoc_new(SIZET2NUM(p->Prec*VpBaseFig()), SIZET2NUM(p->MaxPrec*VpBaseFig())); diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index e3eae06e..28f3363b 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -159,6 +159,10 @@ rb_sym2str(VALUE sym) # define vabs llabs #endif +#if !defined(HAVE_RB_CATEGORY_WARN) || !defined(HAVE_CONST_RB_WARN_CATEGORY_DEPRECATED) +# define rb_category_warn(category, ...) rb_warn(__VA_ARGS__) +#endif + extern VALUE rb_cBigDecimal; #if 0 || SIZEOF_BDIGITS >= 16 diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index fc448ed3..63123e28 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -42,6 +42,8 @@ def check_bigdecimal_version(gemspec_path) have_func("rb_array_const_ptr", "ruby.h") have_func("rb_sym2str", "ruby.h") have_func("rb_opts_exception_p", "ruby.h") +have_func("rb_category_warn", "ruby.h") +have_const("RB_WARN_CATEGORY_DEPRECATED", "ruby.h") if File.file?(File.expand_path('../lib/bigdecimal.rb', __FILE__)) bigdecimal_rb = "$(srcdir)/lib/bigdecimal.rb" diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 59726ada..3003e44a 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -610,12 +610,22 @@ def test_cmp_data assert_operator(BigDecimal((2**100).to_s), :==, d) end + def test_precs_deprecated + assert_warn(/BigDecimal#precs is deprecated and will be removed in the future/) do + BigDecimal("1").precs + end + end + def test_precs - a = BigDecimal("1").precs - assert_instance_of(Array, a) - assert_equal(2, a.size) - assert_kind_of(Integer, a[0]) - assert_kind_of(Integer, a[1]) + assert_separately(["-rbigdecimal"], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + a = BigDecimal("1").precs + assert_instance_of(Array, a) + assert_equal(2, a.size) + assert_kind_of(Integer, a[0]) + assert_kind_of(Integer, a[1]) + end; end def test_hash @@ -764,7 +774,7 @@ def test_coerce assert_equal(BigDecimal("0.1"), a, '[ruby-core:34318]') a, b = BigDecimal("0.11111").coerce(1.quo(3)) - assert_equal(BigDecimal("0." + "3"*a.precs[0]), a) + assert_equal(BigDecimal("0." + "3"*a.precision), a) assert_nothing_raised(TypeError, '#7176') do BigDecimal('1') + Rational(1) From 0ed7846e8c700d2f5b2de901fcac325014a403b8 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 12:10:25 +0900 Subject: [PATCH 112/546] Fix the document of BigDecimal#precs --- ext/bigdecimal/bigdecimal.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f84aea4f..fa003955 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -355,11 +355,12 @@ BigDecimal_double_fig(VALUE self) /* call-seq: * big_decimal.precs -> array * - * Returns an Array of two Integer values. + * Returns an Array of two Integer values that represent platform-dependent + * internal storage properties. * - * The first value is the current number of significant digits in the - * BigDecimal. The second value is the maximum number of significant digits - * for the BigDecimal. + * This method is deprecated and will be removed in the future. + * Instead, use BigDecimal#precision for obtaining the number of decimal + * digits. * * BigDecimal('5').precs #=> [9, 18] */ From 981dc48f95cdb214d887d3d6be604da463ead21a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 17:06:14 +0900 Subject: [PATCH 113/546] Add BigDecimal#n_significant_digits --- ext/bigdecimal/bigdecimal.c | 27 ++++++++++++++++ test/bigdecimal/test_bigdecimal.rb | 51 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index fa003955..6e854913 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -449,6 +449,32 @@ BigDecimal_precision(VALUE self) return SSIZET2NUM(precision); } +static VALUE +BigDecimal_n_significant_digits(VALUE self) +{ + ENTER(1); + + Real *p; + GUARD_OBJ(p, GetVpValue(self, 1)); + + ssize_t n = p->Prec; + while (n > 0 && p->frac[n-1] == 0) --n; + if (n <= 0) { + return INT2FIX(0); + } + + int nlz, ntz; + + BDIGIT x = p->frac[0]; + for (nlz = BASE_FIG; x > 0; x /= 10) --nlz; + + x = p->frac[n-1]; + for (ntz = 0; x > 0 && x % 10 == 0; x /= 10) ++ntz; + + ssize_t n_digits = BASE_FIG * n - nlz - ntz; + return SSIZET2NUM(n_digits); +} + /* * call-seq: hash * @@ -3582,6 +3608,7 @@ Init_bigdecimal(void) /* instance methods */ rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); rb_define_method(rb_cBigDecimal, "precision", BigDecimal_precision, 0); + rb_define_method(rb_cBigDecimal, "n_significant_digits", BigDecimal_n_significant_digits, 0); rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2); rb_define_method(rb_cBigDecimal, "sub", BigDecimal_sub2, 2); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 3003e44a..445a3f2e 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1962,6 +1962,57 @@ def test_precision_special end end + def test_n_significant_digits_only_integer + assert_equal(0, BigDecimal(0).n_significant_digits) + assert_equal(1, BigDecimal(1).n_significant_digits) + assert_equal(1, BigDecimal(-1).n_significant_digits) + assert_equal(1, BigDecimal(10).n_significant_digits) + assert_equal(1, BigDecimal(-10).n_significant_digits) + assert_equal(3, BigDecimal(101).n_significant_digits) + assert_equal(3, BigDecimal(-101).n_significant_digits) + assert_equal(1, BigDecimal(100_000_000_000_000_000_000).n_significant_digits) + assert_equal(1, BigDecimal(-100_000_000_000_000_000_000).n_significant_digits) + assert_equal(21, BigDecimal(100_000_000_000_000_000_001).n_significant_digits) + assert_equal(21, BigDecimal(-100_000_000_000_000_000_001).n_significant_digits) + assert_equal(3, BigDecimal("111e100").n_significant_digits) + assert_equal(3, BigDecimal("-111e100").n_significant_digits) + end + + def test_n_significant_digits_only_fraction + assert_equal(1, BigDecimal("0.1").n_significant_digits) + assert_equal(1, BigDecimal("-0.1").n_significant_digits) + assert_equal(1, BigDecimal("0.01").n_significant_digits) + assert_equal(1, BigDecimal("-0.01").n_significant_digits) + assert_equal(2, BigDecimal("0.11").n_significant_digits) + assert_equal(2, BigDecimal("-0.11").n_significant_digits) + assert_equal(1, BigDecimal("0.000_000_000_000_000_000_001").n_significant_digits) + assert_equal(1, BigDecimal("-0.000_000_000_000_000_000_001").n_significant_digits) + assert_equal(3, BigDecimal("111e-100").n_significant_digits) + assert_equal(3, BigDecimal("-111e-100").n_significant_digits) + end + + def test_n_significant_digits_full + assert_equal(2, BigDecimal("1.1").n_significant_digits) + assert_equal(2, BigDecimal("-1.1").n_significant_digits) + assert_equal(3, BigDecimal("1.01").n_significant_digits) + assert_equal(3, BigDecimal("-1.01").n_significant_digits) + assert_equal(5, BigDecimal("11111e-2").n_significant_digits) + assert_equal(5, BigDecimal("-11111e-2").n_significant_digits) + assert_equal(21, BigDecimal("100.000_000_000_000_000_001").n_significant_digits) + assert_equal(21, BigDecimal("-100.000_000_000_000_000_001").n_significant_digits) + end + + def test_n_significant_digits_special + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + + assert_equal(0, BigDecimal("Infinity").n_significant_digits) + assert_equal(0, BigDecimal("-Infinity").n_significant_digits) + assert_equal(0, BigDecimal("NaN").n_significant_digits) + end + end + def test_initialize_copy_dup_clone_frozen_error bd = BigDecimal(1) bd2 = BigDecimal(2) From ecf880ec04eb2c46cfaeb001ee100e25f7d300ed Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 18:24:59 +0900 Subject: [PATCH 114/546] Fix the document of precs --- ext/bigdecimal/bigdecimal.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6e854913..4e49de8e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -359,8 +359,9 @@ BigDecimal_double_fig(VALUE self) * internal storage properties. * * This method is deprecated and will be removed in the future. - * Instead, use BigDecimal#precision for obtaining the number of decimal - * digits. + * Instead, use BigDecimal#n_significant_digits for obtaining the number of + * significant digits in scientific notation, and BigDecimal#precision for + * obtaining the number of digits in decimal notation. * * BigDecimal('5').precs #=> [9, 18] */ From e68f1eb33a170aa91fc0b088f59df8e2c66a760d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 19:21:56 +0900 Subject: [PATCH 115/546] Version 3.0.0 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 980deb07..7e5388d8 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '2.0.2' +bigdecimal_version = '3.0.0' Gem::Specification.new do |s| s.name = "bigdecimal" From 86ccaf35c8f303a0f6df0cec6dee05a7b0845233 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 20:15:12 +0900 Subject: [PATCH 116/546] Update CHANGES [ci skip] --- CHANGES.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index d8817989..015e7d35 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,32 @@ # CHANGES +## 3.0.0 + +* Deprecate `BigDecimal#precs`. + + **Kenta Murata** + +* Add `BigDecimal#n_significant_digits`. + + **Kenta Murata** + +* Add `BigDecimal#precision`. + + **Kenta Murata** + +* Ractor support. + + **Kenta Murata** + +* Fix a bug of the way to undefine `allocate` method. + + **Kenta Murata** + +* FIx the defaullt precision of `Float#to_d`. + [Bug #13331] + + **Kenta Murata** + ## 2.0.2 * Deprecate taint/trust and related methods, and make the methods no-ops From 2ec99e92c2c8a59304062e176e3db20ce7e24112 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 20:34:04 +0900 Subject: [PATCH 117/546] Add version 3.0.0 in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3fdaf698..a062bb71 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ The differences among versions are given below: | version | characteristics | Supported ruby version range | | ------- | --------------- | ----------------------- | +| 3.0.0 | You can use BigDecimal with Ractor on Ruby 3.0 | 2.5 .. | | 2.0.x | You cannot use BigDecimal.new and do subclassing | 2.4 .. | | 1.4.x | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.6 | | 1.3.5 | You can use BigDecimal.new and subclassing without warning | .. 2.5 | From 43990eee4097e61eb8bbff5e02f70d003dea7e51 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 20 Dec 2020 03:13:33 +0900 Subject: [PATCH 118/546] bigdecimal: initialize conditionally assigned variable --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4e49de8e..7fdf4012 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -418,7 +418,7 @@ BigDecimal_precision(VALUE self) */ ssize_t ex = p->exponent; - ssize_t precision; + ssize_t precision = 0; if (ex < 0) { precision = (-ex + 1) * BASE_FIG; /* 1 is for p->frac[0] */ ex = 0; From 94b60a10fd3bea61547678950c69594a2b51dd11 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 21:13:33 +0900 Subject: [PATCH 119/546] [bigdecimal] Fix deprecation warning test --- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 445a3f2e..16f55057 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -611,9 +611,13 @@ def test_cmp_data end def test_precs_deprecated + saved = Warning[:deprecated] + Warning[:deprecated] = true assert_warn(/BigDecimal#precs is deprecated and will be removed in the future/) do BigDecimal("1").precs end + ensure + Warning[:deprecated] = saved end def test_precs From e5e50f779a68dd4e00ab1e2e34f2332c810d50fe Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 26 Dec 2020 17:44:20 +0900 Subject: [PATCH 120/546] Fix for old versions of Ruby --- test/bigdecimal/test_bigdecimal.rb | 5 +- test/lib/envutil.rb | 248 ++++++++++++++++++++--------- test/lib/test/unit/assertions.rb | 222 ++++++++++++++++++-------- 3 files changed, 333 insertions(+), 142 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 16f55057..6ce9c3b8 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -611,13 +611,10 @@ def test_cmp_data end def test_precs_deprecated - saved = Warning[:deprecated] - Warning[:deprecated] = true assert_warn(/BigDecimal#precs is deprecated and will be removed in the future/) do + Warning[:deprecated] = true if defined?(Warning.[]) BigDecimal("1").precs end - ensure - Warning[:deprecated] = saved end def test_precs diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb index a0b907c2..937e1128 100644 --- a/test/lib/envutil.rb +++ b/test/lib/envutil.rb @@ -1,8 +1,16 @@ # -*- coding: us-ascii -*- -# frozen_string_literal: false +# frozen_string_literal: true require "open3" require "timeout" require_relative "find_executable" +begin + require 'rbconfig' +rescue LoadError +end +begin + require "rbconfig/sizeof" +rescue LoadError +end module EnvUtil def rubybin @@ -34,12 +42,95 @@ def rubybin DEFAULT_SIGNALS = Signal.list DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM + RUBYLIB = ENV["RUBYLIB"] + + class << self + attr_accessor :timeout_scale + attr_reader :original_internal_encoding, :original_external_encoding, + :original_verbose, :original_warning + + def capture_global_values + @original_internal_encoding = Encoding.default_internal + @original_external_encoding = Encoding.default_external + @original_verbose = $VERBOSE + @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil + end + end + + def apply_timeout_scale(t) + if scale = EnvUtil.timeout_scale + t * scale + else + t + end + end + module_function :apply_timeout_scale + + def timeout(sec, klass = nil, message = nil, &blk) + return yield(sec) if sec == nil or sec.zero? + sec = apply_timeout_scale(sec) + Timeout.timeout(sec, klass, message, &blk) + end + module_function :timeout + + def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) + reprieve = apply_timeout_scale(reprieve) if reprieve + + signals = Array(signal).select do |sig| + DEFAULT_SIGNALS[sig.to_s] or + DEFAULT_SIGNALS[Signal.signame(sig)] rescue false + end + signals |= [:ABRT, :KILL] + case pgroup + when 0, true + pgroup = -pid + when nil, false + pgroup = pid + end + + lldb = true if /darwin/ =~ RUBY_PLATFORM + + while signal = signals.shift + + if lldb and [:ABRT, :KILL].include?(signal) + lldb = false + # sudo -n: --non-interactive + # lldb -p: attach + # -o: run command + system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) + true + end + + begin + Process.kill signal, pgroup + rescue Errno::EINVAL + next + rescue Errno::ESRCH + break + end + if signals.empty? or !reprieve + Process.wait(pid) + else + begin + Timeout.timeout(reprieve) {Process.wait(pid)} + rescue Timeout::Error + else + break + end + end + end + $? + end + module_function :terminate + def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, stdout_filter: nil, stderr_filter: nil, signal: :TERM, - rubybin: EnvUtil.rubybin, + rubybin: EnvUtil.rubybin, precommand: nil, **opt) + timeout = apply_timeout_scale(timeout) + in_c, in_p = IO.pipe out_p, out_c = IO.pipe if capture_stdout err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout @@ -56,11 +147,17 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = if Array === args and Hash === args.first child_env.update(args.shift) end + if RUBYLIB and lib = child_env["RUBYLIB"] + child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) + end + child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS'] args = [args] if args.kind_of?(String) - pid = spawn(child_env, rubybin, *args, **opt) + pid = spawn(child_env, *precommand, rubybin, *args, **opt) in_c.close - out_c.close if capture_stdout - err_c.close if capture_stderr && capture_stderr != :merge_to_stdout + out_c&.close + out_c = nil + err_c&.close + err_c = nil if block_given? return yield in_p, out_p, err_p, pid else @@ -71,35 +168,8 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) timeout_error = nil else - signals = Array(signal).select do |sig| - DEFAULT_SIGNALS[sig.to_s] or - DEFAULT_SIGNALS[Signal.signame(sig)] rescue false - end - signals |= [:ABRT, :KILL] - case pgroup = opt[:pgroup] - when 0, true - pgroup = -pid - when nil, false - pgroup = pid - end - while signal = signals.shift - begin - Process.kill signal, pgroup - rescue Errno::EINVAL - next - rescue Errno::ESRCH - break - end - if signals.empty? or !reprieve - Process.wait(pid) - else - begin - Timeout.timeout(reprieve) {Process.wait(pid)} - rescue Timeout::Error - end - end - end - status = $? + status = terminate(pid, signal, opt[:pgroup], reprieve) + terminated = Time.now end stdout = th_stdout.value if capture_stdout stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout @@ -110,8 +180,8 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = stderr = stderr_filter.call(stderr) if stderr_filter if timeout_error bt = caller_locations - msg = "execution of #{bt.shift.label} expired" - msg = Test::Unit::Assertions::FailDesc[status, msg, [stdout, stderr].join("\n")].() + msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" + msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n")) raise timeout_error, msg, bt.map(&:to_s) end return stdout, stderr, status @@ -121,7 +191,7 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = th.kill if th end [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| - io.close if io && !io.closed? + io&.close end [th_stdout, th_stderr].each do |th| th.join if th @@ -129,36 +199,35 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = end module_function :invoke_ruby - alias rubyexec invoke_ruby - class << self - alias rubyexec invoke_ruby - end - def verbose_warning - class << (stderr = "") - alias write << + class << (stderr = "".dup) + alias write concat + def flush; end end - stderr, $stderr, verbose, $VERBOSE = $stderr, stderr, $VERBOSE, true + stderr, $stderr = $stderr, stderr + $VERBOSE = true yield stderr return $stderr ensure - stderr, $stderr, $VERBOSE = $stderr, stderr, verbose + stderr, $stderr = $stderr, stderr + $VERBOSE = EnvUtil.original_verbose + EnvUtil.original_warning&.each {|i, v| Warning[i] = v} end module_function :verbose_warning def default_warning - verbose, $VERBOSE = $VERBOSE, false + $VERBOSE = false yield ensure - $VERBOSE = verbose + $VERBOSE = EnvUtil.original_verbose end module_function :default_warning def suppress_warning - verbose, $VERBOSE = $VERBOSE, nil + $VERBOSE = nil yield ensure - $VERBOSE = verbose + $VERBOSE = EnvUtil.original_verbose end module_function :suppress_warning @@ -171,32 +240,28 @@ def under_gc_stress(stress = true) module_function :under_gc_stress def with_default_external(enc) - verbose, $VERBOSE = $VERBOSE, nil - origenc, Encoding.default_external = Encoding.default_external, enc - $VERBOSE = verbose + suppress_warning { Encoding.default_external = enc } yield ensure - verbose, $VERBOSE = $VERBOSE, nil - Encoding.default_external = origenc - $VERBOSE = verbose + suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding } end module_function :with_default_external def with_default_internal(enc) - verbose, $VERBOSE = $VERBOSE, nil - origenc, Encoding.default_internal = Encoding.default_internal, enc - $VERBOSE = verbose + suppress_warning { Encoding.default_internal = enc } yield ensure - verbose, $VERBOSE = $VERBOSE, nil - Encoding.default_internal = origenc - $VERBOSE = verbose + suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding } end module_function :with_default_internal def labeled_module(name, &block) Module.new do - singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} + singleton_class.class_eval { + define_method(:to_s) {name} + alias inspect to_s + alias name to_s + } class_eval(&block) if block end end @@ -204,7 +269,11 @@ def labeled_module(name, &block) def labeled_class(name, superclass = Object, &block) Class.new(superclass) do - singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} + singleton_class.class_eval { + define_method(:to_s) {name} + alias inspect to_s + alias name to_s + } class_eval(&block) if block end end @@ -213,9 +282,12 @@ def labeled_class(name, superclass = Object, &block) if /darwin/ =~ RUBY_PLATFORM DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' - def self.diagnostic_reports(signame, cmd, pid, now) + @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] + + def self.diagnostic_reports(signame, pid, now) return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) - cmd = File.basename(cmd) + cmd = File.basename(rubybin) + cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd path = DIAGNOSTIC_REPORTS_PATH timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" @@ -234,8 +306,39 @@ def self.diagnostic_reports(signame, cmd, pid, now) nil end else - def self.diagnostic_reports(signame, cmd, pid, now) + def self.diagnostic_reports(signame, pid, now) + end + end + + def self.failure_description(status, now, message = "", out = "") + pid = status.pid + if signo = status.termsig + signame = Signal.signame(signo) + sigdesc = "signal #{signo}" + end + log = diagnostic_reports(signame, pid, now) + if signame + sigdesc = "SIG#{signame} (#{sigdesc})" + end + if status.coredump? + sigdesc = "#{sigdesc} (core dumped)" end + full_message = ''.dup + message = message.call if Proc === message + if message and !message.empty? + full_message << message << "\n" + end + full_message << "pid #{pid}" + full_message << " exit #{status.exitstatus}" if status.exited? + full_message << " killed by #{sigdesc}" if sigdesc + if out and !out.empty? + full_message << "\n" << out.b.gsub(/^/, '| ') + full_message.sub!(/(? e + @failures[key] = [@count, e] + end + end + end + def message i = 0 total = @count.to_s fmt = "%#{total.size}d" @failures.map {|k, (n, v)| - "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.message.gsub(/^/, ' | ')}" + "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.message.b.gsub(/^/, ' | ')}" }.join("\n") end @@ -810,12 +897,21 @@ def pass? end end - def all_assertions(msg = nil) + def assert_all_assertions(msg = nil) all = AllFailures.new yield all ensure - assert(all.pass?, message(msg) {all.message}) + assert(all.pass?, message(msg) {all.message.chomp(".")}) + end + alias all_assertions assert_all_assertions + + def assert_all_assertions_foreach(msg = nil, *keys, &block) + all = AllFailures.new + all.foreach(*keys, &block) + ensure + assert(all.pass?, message(msg) {all.message.chomp(".")}) end + alias all_assertions_foreach assert_all_assertions_foreach def build_message(head, template=nil, *arguments) #:nodoc: template &&= template.chomp From 95d27914258f00a014633b1d34c390acb9447b08 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 27 Dec 2020 21:27:24 +0900 Subject: [PATCH 121/546] CI: Add Ruby 3.0 --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad0328be..e6698250 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: - macos-10.15 - windows-latest ruby: + - 3.0 - 2.7 - 2.6 - 2.5 @@ -27,7 +28,7 @@ jobs: - { os: windows-latest , ruby: mingw } - { os: windows-latest , ruby: mswin } exclude: - - { os: windows-latest , ruby: 3.0.0-preview2 } + - { os: windows-latest , ruby: 3.0 } - { os: windows-latest , ruby: debug } steps: From d40d7edf9adc6d6797e399321209f6e18b1dac3a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:12:36 +0900 Subject: [PATCH 122/546] Bump version --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 7e5388d8..eec0bd0b 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '3.0.0' +bigdecimal_version = '3.0.1' Gem::Specification.new do |s| s.name = "bigdecimal" From 3e33a48015f399a2de8f65ec5f595a45e632b09f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:14:37 +0900 Subject: [PATCH 123/546] Bump version to 3.0.1-dev --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index eec0bd0b..425faaff 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '3.0.1' +bigdecimal_version = '3.0.1.dev' Gem::Specification.new do |s| s.name = "bigdecimal" From a1d7cec0d1d6aaec9b90f09680aee5af3987d153 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:27:25 +0900 Subject: [PATCH 124/546] Use benchmark_driver --- bigdecimal.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 425faaff..15d4e146 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -32,6 +32,7 @@ Gem::Specification.new do |s| s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") + s.add_development_dependency "benchmark_driver" s.add_development_dependency "rake", ">= 12.3.3" s.add_development_dependency "rake-compiler", ">= 0.9" s.add_development_dependency "minitest", "< 5.0.0" From 731b00fbd6f8c598e2b3386636f12dce167d04cd Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:27:39 +0900 Subject: [PATCH 125/546] Add benchmark task --- Rakefile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Rakefile b/Rakefile index 671e6bb6..f4a67197 100644 --- a/Rakefile +++ b/Rakefile @@ -16,3 +16,28 @@ end task travis: :test task test: :compile + +benchmark_tasks = [] +namespace :benchmark do + Dir.glob("benchmark/*.yml") do |benchmark| + name = File.basename(benchmark, ".*") + env = { + "RUBYLIB" => nil, + "BUNDLER_ORIG_RUBYLIB" => nil, + } + command_line = [ + RbConfig.ruby, "-v", "-S", "benchmark-driver", File.expand_path(benchmark) + ] + + desc "Run #{name} benchmark" + task name do + puts("```") + sh(env, *command_line) + puts("```") + end + benchmark_tasks << "benchmark:#{name}" + end +end + +desc "Run all benchmarks" +task benchmark: benchmark_tasks From e26995eff1310e7177970ce792f24898e20fe3a2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:27:59 +0900 Subject: [PATCH 126/546] Add simple benchmark of creation from integers --- benchmark/from_integer.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 benchmark/from_integer.yml diff --git a/benchmark/from_integer.yml b/benchmark/from_integer.yml new file mode 100644 index 00000000..0a7f173b --- /dev/null +++ b/benchmark/from_integer.yml @@ -0,0 +1,15 @@ +loop_count: 100 + +contexts: +- gems: + bigdecimal: 3.0.1.dev +- name: "master" + prelude: |- + $LOAD_PATH.unshift(File.expand_path("lib")) + require "bigdecimal" + +prelude: |- + big_1e100 = 10**100 + +benchmark: + big_1e100: BigDecimal(big_1e100) From 751fd89e64cac00129a7d1c0e3f90824914ef452 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:29:46 +0900 Subject: [PATCH 127/546] Add benchmark job --- .github/workflows/benchmark.yml | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..7ae02d23 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,43 @@ +name: Benchmarking + +on: +- push +- pull_request + +jobs: + host: + name: ${{ matrix.os }} ${{ matrix.ruby }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-10.15 + - windows-latest + ruby: + - 3.0 + - 2.7 + - head + include: + - { os: windows-latest , ruby: mingw } + - { os: windows-latest , ruby: mswin } + exclude: + - { os: windows-latest , ruby: 3.0 } + - { os: windows-latest , ruby: debug } + + steps: + - uses: actions/checkout@v2 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - name: Install dependencies + run: | + bundle install + gem install bigdecimal -v 3.0.0 + rake install + + - run: rake benchmark From 741fb3e00f8777a244e3ace79f52065c81a0d385 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 20:26:28 +0900 Subject: [PATCH 128/546] Reduce conditional branch count in VpNewVarArg --- ext/bigdecimal/bigdecimal.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7fdf4012..1e6de0e2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2753,20 +2753,16 @@ VpNewVarArg(int argc, VALUE *argv) mf = (size_t)n; } - if (SPECIAL_CONST_P(iniValue)) { - switch (iniValue) { - case Qnil: - if (!exc) return NULL; - rb_raise(rb_eTypeError, "can't convert nil into BigDecimal"); - case Qtrue: - if (!exc) return NULL; - rb_raise(rb_eTypeError, "can't convert true into BigDecimal"); - case Qfalse: - if (!exc) return NULL; - rb_raise(rb_eTypeError, "can't convert false into BigDecimal"); - default: - break; - } + switch (iniValue) { + case Qnil: + case Qtrue: + case Qfalse: + if (!exc) return NULL; + rb_raise(rb_eTypeError, + "can't convert %"PRIsVALUE" into BigDecimal", iniValue); + + default: + break; } retry: From 905d0345ec35dbb717beaf5bc1dcebde614bde58 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 21:25:20 +0900 Subject: [PATCH 129/546] [Doc] Fix the document of BigDecimal() [ci-skip] --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1e6de0e2..e544bc7d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2830,7 +2830,7 @@ VpNewVarArg(int argc, VALUE *argv) } /* call-seq: - * BigDecimal(initial, digits, exception: true) + * BigDecimal(initial, digits=0, exception: true) * * Create a new BigDecimal object. * From 5c808eeabb092be7074805255b044fce337c439d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 22:44:50 +0900 Subject: [PATCH 130/546] Reduce needless object allocation in f_BigDecimal --- ext/bigdecimal/bigdecimal.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e544bc7d..f89a66ed 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2870,19 +2870,17 @@ f_BigDecimal(int argc, VALUE *argv, VALUE self) { ENTER(1); Real *pv; - VALUE obj; if (argc > 0 && CLASS_OF(argv[0]) == rb_cBigDecimal) { if (argc == 1 || (argc == 2 && RB_TYPE_P(argv[1], T_HASH))) return argv[0]; } - obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); pv = VpNewVarArg(argc, argv); if (pv == NULL) return Qnil; SAVE(pv); if (ToValue(pv)) pv = VpCopy(NULL, pv); - RTYPEDDATA_DATA(obj) = pv; - RB_OBJ_FREEZE(obj); - return pv->obj = obj; + pv->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, pv); + RB_OBJ_FREEZE(pv->obj); + return pv->obj; } static VALUE From 7504871c480c5236d390812017d7c282b646bb94 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 03:36:31 +0900 Subject: [PATCH 131/546] Refactor to decompose VpNewVarArg into small functions --- ext/bigdecimal/bigdecimal.c | 232 +++++++++++++++++++++--------------- 1 file changed, 137 insertions(+), 95 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f89a66ed..a7b099b1 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2725,108 +2725,138 @@ opts_exception_p(VALUE opts) } #endif -static Real * -VpNewVarArg(int argc, VALUE *argv) +static VALUE +check_exception(VALUE bd) { - size_t mf; - VALUE opts = Qnil; - VALUE nFig; - VALUE iniValue; - double d; - int exc; + assert(is_kind_of_BigDecimal(bd)); + + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + ToValue(vp); /* ToValue performs exception check */ + + return bd; +} + +static VALUE +rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + Real *vp = GetVpValue(val, 1); + return check_exception(vp->obj); +} - argc = rb_scan_args(argc, argv, "11:", &iniValue, &nFig, &opts); - exc = opts_exception_p(opts); +static VALUE +rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + double d = RFLOAT_VALUE(val); + if (!isfinite(d)) { + Real *vp = VpCreateRbObject(1, NULL); /* vp->obj is allocated */ + VpDtoV(vp, d); + return check_exception(vp->obj); + } - if (argc == 1) { - mf = 0; + if (digs == SIZE_MAX) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, + "can't omit precision for a %"PRIsVALUE".", + CLASS_OF(val)); } - else { - /* expand GetPrecisionInt for exception suppression */ - ssize_t n = NUM2INT(nFig); - if (n < 0) { - if (!exc) { - return NULL; - } - rb_raise(rb_eArgError, "negative precision"); - } - mf = (size_t)n; + else if (digs > DBLE_FIG) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, "precision too large."); } - switch (iniValue) { + Real *vp = GetVpValueWithPrec(val, digs, 1); + return check_exception(vp->obj); +} + +static VALUE +rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + if (digs == SIZE_MAX) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, + "can't omit precision for a %"PRIsVALUE".", + CLASS_OF(val)); + } + Real *vp = GetVpValueWithPrec(val, digs, 1); + return check_exception(vp->obj); +} + +static VALUE +rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + if (digs == SIZE_MAX) + digs = 0; + + const char *c_str = StringValueCStr(val); + Real *vp = VpAlloc(digs, c_str, 1, raise_exception); + if (!vp) + return Qnil; + vp->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, vp); + RB_OBJ_FREEZE(vp->obj); + return check_exception(vp->obj); +} + +static VALUE +rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + switch (val) { case Qnil: case Qtrue: case Qfalse: - if (!exc) return NULL; + if (!raise_exception) + return Qnil; rb_raise(rb_eTypeError, - "can't convert %"PRIsVALUE" into BigDecimal", iniValue); + "can't convert %"PRIsVALUE" into BigDecimal", val); default: break; } - retry: - switch (TYPE(iniValue)) { - case T_DATA: - if (is_kind_of_BigDecimal(iniValue)) { - return DATA_PTR(iniValue); - } - break; - - case T_FIXNUM: - /* fall through */ - case T_BIGNUM: - return GetVpValue(iniValue, 1); - - case T_FLOAT: - d = RFLOAT_VALUE(iniValue); - if (!isfinite(d)) { - Real *pv = VpCreateRbObject(1, NULL); - VpDtoV(pv, d); - return pv; - } - if (mf > DBLE_FIG) { - if (!exc) { - return NULL; - } - rb_raise(rb_eArgError, "precision too large."); - } - /* fall through */ - case T_RATIONAL: - if (NIL_P(nFig)) { - if (!exc) { - return NULL; - } - rb_raise(rb_eArgError, - "can't omit precision for a %"PRIsVALUE".", - RB_OBJ_CLASSNAME(iniValue)); - } - return GetVpValueWithPrec(iniValue, mf, 1); + if (is_kind_of_BigDecimal(val)) { + if (digs == SIZE_MAX) + return check_exception(val); - case T_COMPLEX: - { - VALUE im; - im = rb_complex_imag(iniValue); - if (!is_zero(im)) { - rb_raise(rb_eArgError, - "Unable to make a BigDecimal from non-zero imaginary number"); - } - iniValue = rb_complex_real(iniValue); - goto retry; + Real *vp; + TypedData_Get_Struct(val, Real, &BigDecimal_data_type, vp); + vp = VpCopy(NULL, vp); + vp->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, vp); + RB_OBJ_FREEZE(vp->obj); + return check_exception(vp->obj); + } + else if (RB_INTEGER_TYPE_P(val)) { + return rb_inum_convert_to_BigDecimal(val, digs, raise_exception); + } + else if (RB_FLOAT_TYPE_P(val)) { + return rb_float_convert_to_BigDecimal(val, digs, raise_exception); + } + else if (RB_TYPE_P(val, T_RATIONAL)) { + return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); + } + else if (RB_TYPE_P(val, T_COMPLEX)) { + VALUE im = rb_complex_imag(val); + if (!is_zero(im)) { + /* TODO: handle raise_exception */ + rb_raise(rb_eArgError, + "Unable to make a BigDecimal from non-zero imaginary number"); } - - case T_STRING: - /* fall through */ - default: - break; + return rb_convert_to_BigDecimal(rb_complex_real(val), digs, raise_exception); } - /* TODO: support to_d */ - if (!exc) { - iniValue = rb_check_convert_type(iniValue, T_STRING, "String", "to_str"); - if (NIL_P(iniValue)) return NULL; + else if (RB_TYPE_P(val, T_STRING)) { + return rb_str_convert_to_BigDecimal(val, digs, raise_exception); } - StringValueCStr(iniValue); - return VpAlloc(mf, RSTRING_PTR(iniValue), 1, exc); + /* TODO: chheck to_d */ + /* TODO: chheck to_int */ + if (!raise_exception) { + VALUE str = rb_check_convert_type(val, T_STRING, "String", "to_str"); + if (NIL_P(str)) + return Qnil; + val = str; + } + return rb_str_convert_to_BigDecimal(val, digs, raise_exception); } /* call-seq: @@ -2868,19 +2898,31 @@ VpNewVarArg(int argc, VALUE *argv) static VALUE f_BigDecimal(int argc, VALUE *argv, VALUE self) { - ENTER(1); - Real *pv; - - if (argc > 0 && CLASS_OF(argv[0]) == rb_cBigDecimal) { - if (argc == 1 || (argc == 2 && RB_TYPE_P(argv[1], T_HASH))) return argv[0]; + VALUE val, digs_v, opts = Qnil; + argc = rb_scan_args(argc, argv, "11:", &val, &digs_v, &opts); + int exception = opts_exception_p(opts); + + size_t digs = SIZE_MAX; /* this means digs is omitted */ + if (argc > 1) { + digs_v = rb_to_int(digs_v); + if (FIXNUM_P(digs_v)) { + long n = FIX2LONG(digs_v); + if (n < 0) + goto negative_digs; + digs = (size_t)n; + } + else { + if (RBIGNUM_NEGATIVE_P(digs_v)) { + negative_digs: + if (!exception) + return Qnil; + rb_raise(rb_eArgError, "negative precision"); + } + digs = NUM2SIZET(digs_v); + } } - pv = VpNewVarArg(argc, argv); - if (pv == NULL) return Qnil; - SAVE(pv); - if (ToValue(pv)) pv = VpCopy(NULL, pv); - pv->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, pv); - RB_OBJ_FREEZE(pv->obj); - return pv->obj; + + return rb_convert_to_BigDecimal(val, digs, exception); } static VALUE From 48583417907f2b9c4ded10e4376498c6e4215884 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 12:46:37 +0900 Subject: [PATCH 132/546] CI: Stop using macos-11.0 due to the unstability --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6698250..106b8040 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ jobs: os: - ubuntu-20.04 - ubuntu-18.04 - - macos-11.0 - macos-10.15 - windows-latest ruby: From 205a8a618341fdd117f89fa821487d78fce0d184 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 13:12:48 +0900 Subject: [PATCH 133/546] Benchmark: Use bigdecimal 3.0.0 for the baseline --- benchmark/from_integer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/from_integer.yml b/benchmark/from_integer.yml index 0a7f173b..e0896f33 100644 --- a/benchmark/from_integer.yml +++ b/benchmark/from_integer.yml @@ -2,7 +2,7 @@ loop_count: 100 contexts: - gems: - bigdecimal: 3.0.1.dev + bigdecimal: 3.0.0 - name: "master" prelude: |- $LOAD_PATH.unshift(File.expand_path("lib")) From d109c9b0e6d918a8e5338e295b7cd9a014bd9af4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 13:31:51 +0900 Subject: [PATCH 134/546] Benchmark: Use larger number --- benchmark/from_integer.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/benchmark/from_integer.yml b/benchmark/from_integer.yml index e0896f33..f98c5722 100644 --- a/benchmark/from_integer.yml +++ b/benchmark/from_integer.yml @@ -9,7 +9,9 @@ contexts: require "bigdecimal" prelude: |- - big_1e100 = 10**100 + figs = (0..9).to_a + + big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: - big_1e100: BigDecimal(big_1e100) + big_n10000: BigDecimal(big_n10000) From 6fd171308ba0c046a93b5a5b37be2099ab0b3e8e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 16:34:23 +0900 Subject: [PATCH 135/546] Refactor to extract VpCheckException --- ext/bigdecimal/bigdecimal.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a7b099b1..f659359c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -208,21 +208,29 @@ is_kind_of_BigDecimal(VALUE const v) return rb_typeddata_is_kind_of(v, &BigDecimal_data_type); } -static VALUE -ToValue(Real *p) +static void +VpCheckException(Real *p, bool always) { if (VpIsNaN(p)) { - VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'(Not a Number)", 0); + VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'(Not a Number)", always); } else if (VpIsPosInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", 0); + VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", always); } else if (VpIsNegInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", 0); + VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", always); } +} + +static VALUE +VpCheckGetValue(Real *p) +{ + VpCheckException(p, false); return p->obj; } +#define ToValue(p) VpCheckGetValue(p) + NORETURN(static void cannot_be_coerced_into_BigDecimal(VALUE, VALUE)); static void @@ -838,15 +846,7 @@ BigDecimal_IsFinite(VALUE self) static void BigDecimal_check_num(Real *p) { - if (VpIsNaN(p)) { - VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'(Not a Number)", 1); - } - else if (VpIsPosInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", 1); - } - else if (VpIsNegInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", 1); - } + VpCheckException(p, true); } static VALUE BigDecimal_split(VALUE self); From 2c5a288caf33ee7309186765d988d2990ca8191f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 16:35:25 +0900 Subject: [PATCH 136/546] Alloc wrapper object before VpAlloc Calling TypedData_Wrap_Struct after VpAlloc may cause memory leak. This commit reverts 5c808eeabb092be7074805255b044fce337c439d. --- ext/bigdecimal/bigdecimal.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f659359c..89653f49 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2792,12 +2792,14 @@ rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) digs = 0; const char *c_str = StringValueCStr(val); + VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); Real *vp = VpAlloc(digs, c_str, 1, raise_exception); if (!vp) return Qnil; - vp->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, vp); - RB_OBJ_FREEZE(vp->obj); - return check_exception(vp->obj); + RTYPEDDATA_DATA(obj) = vp; + vp->obj = obj; + RB_OBJ_FREEZE(obj); + return VpCheckGetValue(vp); } static VALUE @@ -2822,10 +2824,13 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) Real *vp; TypedData_Get_Struct(val, Real, &BigDecimal_data_type, vp); + + VALUE copy = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); vp = VpCopy(NULL, vp); - vp->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, vp); + RTYPEDDATA_DATA(copy) = vp; + vp->obj = copy; RB_OBJ_FREEZE(vp->obj); - return check_exception(vp->obj); + return VpCheckGetValue(vp); } else if (RB_INTEGER_TYPE_P(val)) { return rb_inum_convert_to_BigDecimal(val, digs, raise_exception); @@ -2929,14 +2934,16 @@ static VALUE BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) { ENTER(1); - char const *c_str; - Real *pv; - c_str = StringValueCStr(str); + char const *c_str = StringValueCStr(str); + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + + Real *pv; GUARD_OBJ(pv, VpAlloc(0, c_str, 0, 1)); - pv->obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, pv); + RTYPEDDATA_DATA(obj) = pv; + pv->obj = obj; RB_OBJ_FREEZE(pv->obj); - return pv->obj; + return obj; } /* call-seq: From a6d3bd2d442c8051e5de12d373c674527b52c612 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 17:14:36 +0900 Subject: [PATCH 137/546] Define bool, true, and false for old Ruby --- ext/bigdecimal/bigdecimal.h | 14 ++++++++++++++ ext/bigdecimal/extconf.rb | 2 ++ 2 files changed, 16 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 28f3363b..c89f212a 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -14,6 +14,20 @@ #include "ruby/ruby.h" #include +#if defined(__bool_true_false_are_defined) +# /* Take that. */ + +#elif defined(HAVE_STDBOOL_H) +# include + +#else +typedef unsigned char _Bool; +# define bool _Bool +# define true ((_Bool)+1) +# define false ((_Bool)-1) +# define __bool_true_false_are_defined +#endif + #ifndef RB_UNUSED_VAR # ifdef __GNUC__ # define RB_UNUSED_VAR(x) x __attribute__ ((unused)) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 63123e28..41b8df8d 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -28,6 +28,8 @@ def check_bigdecimal_version(gemspec_path) check_bigdecimal_version(gemspec_path) +have_header("stdbool.h") + have_func("labs", "stdlib.h") have_func("llabs", "stdlib.h") have_func("finite", "math.h") From 507f0a6a64a3f43b679553be6ae8f7b5c7c1fd40 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 22:36:51 +0900 Subject: [PATCH 138/546] Remove needless pointer checks xmalloc and xrealloc return non-NULL pointers or raise memory error. --- ext/bigdecimal/bigdecimal.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 89653f49..65f412e6 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3779,9 +3779,6 @@ VP_EXPORT void * VpMemAlloc(size_t mb) { void *p = xmalloc(mb); - if (!p) { - VpException(VP_EXCEPTION_MEMORY, "failed to allocate memory", 1); - } memset(p, 0, mb); #ifdef BIGDECIMAL_DEBUG gnAlloc++; /* Count allocation call */ @@ -3792,11 +3789,7 @@ VpMemAlloc(size_t mb) VP_EXPORT void * VpMemRealloc(void *ptr, size_t mb) { - void *p = xrealloc(ptr, mb); - if (!p) { - VpException(VP_EXCEPTION_MEMORY, "failed to allocate memory", 1); - } - return p; + return xrealloc(ptr, mb); } VP_EXPORT void @@ -4348,7 +4341,6 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) /* at least mx digits. */ /* szVal==NULL ==> allocate zero value. */ vp = VpAllocReal(mx); - /* xmalloc() always returns(or throw interruption) */ vp->MaxPrec = mx; /* set max precision */ VpSetZero(vp, 1); /* initialize vp to zero. */ return vp; @@ -4524,7 +4516,6 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) nalloc = Max(nalloc, mx); mx = nalloc; vp = VpAllocReal(mx); - /* xmalloc() always returns(or throw interruption) */ vp->MaxPrec = mx; /* set max precision */ VpSetZero(vp, sign); VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne); From 7d463f802b0387c04f92ae00d255f059726f784f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 30 Dec 2020 00:19:10 +0900 Subject: [PATCH 139/546] Remove VP_EXCEPTION_MEMORY It is no longer used due to the previous commit. --- ext/bigdecimal/bigdecimal.c | 3 +-- ext/bigdecimal/bigdecimal.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 65f412e6..888b21f2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4007,7 +4007,7 @@ VpException(unsigned short f, const char *str,int always) { unsigned short const exception_mode = VpGetException(); - if (f == VP_EXCEPTION_OP || f == VP_EXCEPTION_MEMORY) always = 1; + if (f == VP_EXCEPTION_OP) always = 1; if (always || (exception_mode & f)) { switch(f) { @@ -4019,7 +4019,6 @@ VpException(unsigned short f, const char *str,int always) case VP_EXCEPTION_OP: rb_raise(rb_eFloatDomainError, "%s", str); break; - case VP_EXCEPTION_MEMORY: default: rb_fatal("%s", str); } diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index c89f212a..80278738 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -221,7 +221,6 @@ extern VALUE rb_cBigDecimal; /* Following 2 exceptions can't controlled by user */ #define VP_EXCEPTION_OP ((unsigned short)0x0020) -#define VP_EXCEPTION_MEMORY ((unsigned short)0x0040) #define RMPD_EXCEPTION_MODE_DEFAULT 0U From 97e9feeebd4f9576b4522d0a6a05dcd7d48c51b2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 31 Dec 2020 01:43:08 +0900 Subject: [PATCH 140/546] Remove ToValue --- ext/bigdecimal/bigdecimal.c | 120 ++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 888b21f2..13b2cdcf 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -229,8 +229,6 @@ VpCheckGetValue(Real *p) return p->obj; } -#define ToValue(p) VpCheckGetValue(p) - NORETURN(static void cannot_be_coerced_into_BigDecimal(VALUE, VALUE)); static void @@ -289,7 +287,7 @@ GetVpValueWithPrec(VALUE v, long prec, int must) pv = GetVpValueWithPrec(num, -1, must); if (pv == NULL) goto SomeOneMayDoIt; - v = BigDecimal_div2(ToValue(pv), rb_rational_den(v), LONG2NUM(prec)); + v = BigDecimal_div2(VpCheckGetValue(pv), rb_rational_den(v), LONG2NUM(prec)); goto again; } @@ -566,7 +564,7 @@ BigDecimal_load(VALUE self, VALUE str) if (m && pv->MaxPrec > m) { pv->MaxPrec = m+1; } - return ToValue(pv); + return VpCheckGetValue(pv); } static unsigned short @@ -1004,7 +1002,7 @@ BigDecimal_coerce(VALUE self, VALUE other) if (RB_TYPE_P(other, T_FLOAT)) { GUARD_OBJ(b, GetVpValueWithPrec(other, DBLE_FIG, 1)); - obj = rb_assoc_new(ToValue(b), self); + obj = rb_assoc_new(VpCheckGetValue(b), self); } else { if (RB_TYPE_P(other, T_RATIONAL)) { @@ -1090,7 +1088,7 @@ BigDecimal_add(VALUE self, VALUE r) VpAddSub(c, a, b, 1); } } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1148,7 +1146,7 @@ BigDecimal_sub(VALUE self, VALUE r) VpAddSub(c, a, b, -1); } } - return ToValue(c); + return VpCheckGetValue(c); } static VALUE @@ -1355,7 +1353,7 @@ BigDecimal_neg(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); GUARD_OBJ(c, VpCreateRbObject(a->Prec *(VpBaseFig() + 1), "0")); VpAsgn(c, a, -1); - return ToValue(c); + return VpCheckGetValue(c); } /* @@ -1397,7 +1395,7 @@ BigDecimal_mult(VALUE self, VALUE r) mx = a->Prec + b->Prec; GUARD_OBJ(c, VpCreateRbObject(mx *(VpBaseFig() + 1), "0")); VpMult(c, a, b); - return ToValue(c); + return VpCheckGetValue(c); } static VALUE @@ -1459,7 +1457,7 @@ BigDecimal_div(VALUE self, VALUE r) if (VpHasVal(div)) { /* frac[0] must be zero for NaN,INF,Zero */ VpInternalRound(c, 0, c->frac[c->Prec-1], (BDIGIT)(VpBaseVal() * (BDIGIT_DBL)res->frac[0] / div->frac[0])); } - return ToValue(c); + return VpCheckGetValue(c); } /* @@ -1562,7 +1560,7 @@ BigDecimal_mod(VALUE self, VALUE r) /* %: a%b = a - (a.to_f/b).floor * b */ if (BigDecimal_DoDivmod(self, r, &div, &mod)) { SAVE(div); SAVE(mod); - return ToValue(mod); + return VpCheckGetValue(mod); } return DoSomeOne(self, r, '%'); } @@ -1627,7 +1625,7 @@ BigDecimal_remainder(VALUE self, VALUE r) /* remainder */ Real *d, *rv = 0; f = BigDecimal_divremain(self, r, &d, &rv); if (!NIL_P(f)) return f; - return ToValue(rv); + return VpCheckGetValue(rv); } /* call-seq: @@ -1660,7 +1658,7 @@ BigDecimal_divmod(VALUE self, VALUE r) if (BigDecimal_DoDivmod(self, r, &div, &mod)) { SAVE(div); SAVE(mod); - return rb_assoc_new(ToValue(div), ToValue(mod)); + return rb_assoc_new(VpCheckGetValue(div), VpCheckGetValue(mod)); } return DoSomeOne(self,r,rb_intern("divmod")); } @@ -1678,7 +1676,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) Real *div = NULL; Real *mod; if (BigDecimal_DoDivmod(self, b, &div, &mod)) { - return BigDecimal_to_i(ToValue(div)); + return BigDecimal_to_i(VpCheckGetValue(div)); } return DoSomeOne(self, b, rb_intern("div")); } @@ -1703,7 +1701,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) VpDivd(cv, res, av, bv); VpSetPrecLimit(pl); VpLeftRound(cv, VpGetRoundMode(), ix); - return ToValue(cv); + return VpCheckGetValue(cv); } } @@ -1761,7 +1759,7 @@ BigDecimal_add2(VALUE self, VALUE b, VALUE n) VpSetPrecLimit(pl); GUARD_OBJ(cv, GetVpValue(c, 1)); VpLeftRound(cv, VpGetRoundMode(), mx); - return ToValue(cv); + return VpCheckGetValue(cv); } } @@ -1791,7 +1789,7 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) VpSetPrecLimit(pl); GUARD_OBJ(cv, GetVpValue(c, 1)); VpLeftRound(cv, VpGetRoundMode(), mx); - return ToValue(cv); + return VpCheckGetValue(cv); } } @@ -1809,7 +1807,7 @@ BigDecimal_mult2(VALUE self, VALUE b, VALUE n) VpSetPrecLimit(pl); GUARD_OBJ(cv, GetVpValue(c, 1)); VpLeftRound(cv, VpGetRoundMode(), mx); - return ToValue(cv); + return VpCheckGetValue(cv); } } @@ -1835,7 +1833,7 @@ BigDecimal_abs(VALUE self) GUARD_OBJ(c, VpCreateRbObject(mx, "0")); VpAsgn(c, a, 1); VpChangeSign(c, 1); - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1859,7 +1857,7 @@ BigDecimal_sqrt(VALUE self, VALUE nFig) if (mx <= n) mx = n; GUARD_OBJ(c, VpCreateRbObject(mx, "0")); VpSqrt(c, a); - return ToValue(c); + return VpCheckGetValue(c); } /* Return the integer part of the number, as a BigDecimal. @@ -1875,7 +1873,7 @@ BigDecimal_fix(VALUE self) mx = a->Prec *(VpBaseFig() + 1); GUARD_OBJ(c, VpCreateRbObject(mx, "0")); VpActiveRound(c, a, VP_ROUND_DOWN, 0); /* 0: round off */ - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1950,9 +1948,9 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) VpSetPrecLimit(pl); VpActiveRound(c, a, sw, iLoc); if (round_to_int) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1996,9 +1994,9 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self) VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_DOWN, iLoc); /* 0: truncate */ if (argc == 0) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* Return the fractional part of the number, as a BigDecimal. @@ -2014,7 +2012,7 @@ BigDecimal_frac(VALUE self) mx = a->Prec * (VpBaseFig() + 1); GUARD_OBJ(c, VpCreateRbObject(mx, "0")); VpFrac(c, a); - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -2059,9 +2057,9 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) VPrint(stderr, "floor: c=%\n", c); #endif if (argc == 0) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -2102,9 +2100,9 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_CEIL, iLoc); if (argc == 0) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -2406,7 +2404,7 @@ rmpd_power_by_big_decimal(Real const* x, Real const* exp, ssize_t const n) volatile VALUE obj = exp->obj; if (VpIsZero(exp)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1")); } log_x = BigMath_log(x->obj, SSIZET2NUM(n+1)); @@ -2447,7 +2445,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) y = VpCreateRbObject(n, "0"); RB_GC_GUARD(y->obj); VpSetNaN(y); - return ToValue(y); + return VpCheckGetValue(y); } retry: @@ -2538,18 +2536,18 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) /* (+0) ** (-num) -> Infinity */ VpSetPosInf(y); } - return ToValue(y); + return VpCheckGetValue(y); } else if (is_zero(vexp)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1")); } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } } if (is_zero(vexp)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1")); } else if (is_one(vexp)) { return self; @@ -2561,20 +2559,20 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (is_integer(vexp)) { if (is_even(vexp)) { /* (-Infinity) ** (-even_integer) -> +0 */ - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } else { /* (-Infinity) ** (-odd_integer) -> -0 */ - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0")); } } else { /* (-Infinity) ** (-non_integer) -> -0 */ - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0")); } } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } } else { @@ -2597,7 +2595,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { VpSetPosInf(y); } - return ToValue(y); + return VpCheckGetValue(y); } } @@ -2607,7 +2605,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else if (RB_TYPE_P(vexp, T_BIGNUM)) { VALUE abs_value = BigDecimal_abs(self); if (is_one(abs_value)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1")); } else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { if (is_negative(vexp)) { @@ -2618,13 +2616,13 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { VpSetInf(y, -VpGetSign(x)); } - return ToValue(y); + return VpCheckGetValue(y); } else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0")); } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } } else { @@ -2636,13 +2634,13 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { VpSetInf(y, -VpGetSign(x)); } - return ToValue(y); + return VpCheckGetValue(y); } else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0")); } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } } } @@ -2663,7 +2661,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (!NIL_P(prec) && VpIsDef(y)) { VpMidRound(y, VpGetRoundMode(), n); } - return ToValue(y); + return VpCheckGetValue(y); } /* call-seq: @@ -2732,7 +2730,7 @@ check_exception(VALUE bd) Real *vp; TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); - ToValue(vp); /* ToValue performs exception check */ + VpCheckGetValue(vp); /* VpCheckGetValue performs exception check */ return bd; } @@ -3140,14 +3138,14 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } if (infinite) { if (negative) { - return ToValue(GetVpValueWithPrec(INT2FIX(0), prec, 1)); + return VpCheckGetValue(GetVpValueWithPrec(INT2FIX(0), prec, 1)); } else { Real* vy; vy = VpCreateRbObject(prec, "#0"); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); RB_GC_GUARD(vy->obj); - return ToValue(vy); + return VpCheckGetValue(vy); } } else if (nan) { @@ -3155,7 +3153,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) vy = VpCreateRbObject(prec, "#0"); VpSetNaN(vy); RB_GC_GUARD(vy->obj); - return ToValue(vy); + return VpCheckGetValue(vy); } else if (vx == NULL) { cannot_be_coerced_into_BigDecimal(rb_eArgError, x); @@ -3172,7 +3170,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) VpSetSign(vx, 1); } - one = ToValue(VpCreateRbObject(1, "1")); + one = VpCheckGetValue(VpCreateRbObject(1, "1")); y = one; d = y; i = 1; @@ -3303,14 +3301,14 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) vy = VpCreateRbObject(prec, "#0"); RB_GC_GUARD(vy->obj); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); - return ToValue(vy); + return VpCheckGetValue(vy); } else if (nan) { Real* vy; vy = VpCreateRbObject(prec, "#0"); RB_GC_GUARD(vy->obj); VpSetNaN(vy); - return ToValue(vy); + return VpCheckGetValue(vy); } else if (zero || negative) { rb_raise(rb_eMathDomainError, @@ -3319,10 +3317,10 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) else if (vx == NULL) { cannot_be_coerced_into_BigDecimal(rb_eArgError, x); } - x = ToValue(vx); + x = VpCheckGetValue(vx); - RB_GC_GUARD(one) = ToValue(VpCreateRbObject(1, "1")); - RB_GC_GUARD(two) = ToValue(VpCreateRbObject(1, "2")); + RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1")); + RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2")); n = prec + rmpd_double_figures(); RB_GC_GUARD(vn) = SSIZET2NUM(n); @@ -3330,7 +3328,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) if (expo < 0 || expo >= 3) { char buf[DECIMAL_SIZE_OF_BITS(SIZEOF_VALUE * CHAR_BIT) + 4]; snprintf(buf, sizeof(buf), "1E%"PRIdVALUE, -expo); - x = BigDecimal_mult2(x, ToValue(VpCreateRbObject(1, buf)), vn); + x = BigDecimal_mult2(x, VpCheckGetValue(VpCreateRbObject(1, buf)), vn); } else { expo = 0; @@ -3362,7 +3360,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) if (expo != 0) { VALUE log10, vexpo, dy; log10 = BigMath_s_log(klass, INT2FIX(10), vprec); - vexpo = ToValue(GetVpValue(SSIZET2NUM(expo), 1)); + vexpo = VpCheckGetValue(GetVpValue(SSIZET2NUM(expo), 1)); dy = BigDecimal_mult(log10, vexpo); y = BigDecimal_add(y, dy); } From 271cebe56738c32ae2ab4f7aa3c0f9d7b6503bec Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 31 Dec 2020 02:01:35 +0900 Subject: [PATCH 141/546] Refactor object allocation --- ext/bigdecimal/bigdecimal.c | 203 +++++++++++++++++++----------------- ext/bigdecimal/bigdecimal.h | 5 +- 2 files changed, 107 insertions(+), 101 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 13b2cdcf..96fe253d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -266,7 +266,7 @@ GetVpValueWithPrec(VALUE v, long prec, int must) if (prec > (long)DBLE_FIG) goto SomeOneMayDoIt; d = RFLOAT_VALUE(v); if (!isfinite(d)) { - pv = VpCreateRbObject(1, NULL); + pv = VpCreateRbObject(1, NULL, true); VpDtoV(pv, d); return pv; } @@ -275,9 +275,9 @@ GetVpValueWithPrec(VALUE v, long prec, int must) goto again; } if (1/d < 0.0) { - return VpCreateRbObject(prec, "-0"); + return VpCreateRbObject(prec, "-0", true); } - return VpCreateRbObject(prec, "0"); + return VpCreateRbObject(prec, "0", true); case T_RATIONAL: if (prec < 0) goto unable_to_coerce_without_prec; @@ -306,20 +306,20 @@ GetVpValueWithPrec(VALUE v, long prec, int must) case T_FIXNUM: sprintf(szD, "%ld", FIX2LONG(v)); - return VpCreateRbObject(VpBaseFig() * 2 + 1, szD); + return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); #ifdef ENABLE_NUMERIC_STRING case T_STRING: StringValueCStr(v); - return VpCreateRbObject(RSTRING_LEN(v) + VpBaseFig() + 1, - RSTRING_PTR(v)); + return VpCreateRbObject(RSTRING_LEN(v) + VpBaseFig() + 1, + RSTRING_PTR(v), true); #endif /* ENABLE_NUMERIC_STRING */ case T_BIGNUM: bg = rb_big2str(v, 10); PUSH(bg); - return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, - RSTRING_PTR(bg)); + return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, + RSTRING_PTR(bg), true); default: goto SomeOneMayDoIt; } @@ -559,7 +559,7 @@ BigDecimal_load(VALUE self, VALUE str) m = m*10 + (unsigned long)(ch-'0'); } if (m > VpBaseFig()) m -= VpBaseFig(); - GUARD_OBJ(pv, VpNewRbClass(m, (char *)pch, self)); + GUARD_OBJ(pv, VpNewRbClass(m, (char *)pch, self, true, true)); m /= VpBaseFig(); if (m && pv->MaxPrec > m) { pv->MaxPrec = m+1; @@ -774,21 +774,39 @@ GetPrecisionInt(VALUE v) return n; } +static VALUE +BigDecimal_wrap_struct(VALUE obj, Real *vp) +{ + assert(is_kind_of_BigDecimal(obj)); + assert(vp != NULL); + + if (vp->obj == obj && RTYPEDDATA_DATA(obj) == vp) + return obj; + + assert(RTYPEDDATA_DATA(obj) == NULL); + assert(vp->obj == 0); + + RTYPEDDATA_DATA(obj) = vp; + vp->obj = obj; + RB_OBJ_FREEZE(obj); + return obj; +} + VP_EXPORT Real * -VpNewRbClass(size_t mx, const char *str, VALUE klass) +VpNewRbClass(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception) { VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); - Real *pv = VpAlloc(mx, str, 1, 1); - RTYPEDDATA_DATA(obj) = pv; - pv->obj = obj; - RB_OBJ_FREEZE(obj); + Real *pv = VpAlloc(mx, str, strict_p, raise_exception); + if (!pv) + return NULL; + BigDecimal_wrap_struct(obj, pv); return pv; } VP_EXPORT Real * -VpCreateRbObject(size_t mx, const char *str) +VpCreateRbObject(size_t mx, const char *str, bool raise_exception) { - return VpNewRbClass(mx, str, rb_cBigDecimal); + return VpNewRbClass(mx, str, rb_cBigDecimal, true, raise_exception); } #define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(BDIGIT)) @@ -1076,11 +1094,11 @@ BigDecimal_add(VALUE self, VALUE r) mx = GetAddSubPrec(a, b); if (mx == (size_t)-1L) { - GUARD_OBJ(c,VpCreateRbObject(VpBaseFig() + 1, "0")); + GUARD_OBJ(c, VpCreateRbObject(VpBaseFig() + 1, "0", true)); VpAddSub(c, a, b, 1); } else { - GUARD_OBJ(c, VpCreateRbObject(mx * (VpBaseFig() + 1), "0")); + GUARD_OBJ(c, VpCreateRbObject(mx * (VpBaseFig() + 1), "0", true)); if(!mx) { VpSetInf(c, VpGetSign(a)); } @@ -1134,11 +1152,11 @@ BigDecimal_sub(VALUE self, VALUE r) mx = GetAddSubPrec(a,b); if (mx == (size_t)-1L) { - GUARD_OBJ(c,VpCreateRbObject(VpBaseFig() + 1, "0")); + GUARD_OBJ(c, VpCreateRbObject(VpBaseFig() + 1, "0", true)); VpAddSub(c, a, b, -1); } else { - GUARD_OBJ(c,VpCreateRbObject(mx *(VpBaseFig() + 1), "0")); + GUARD_OBJ(c,VpCreateRbObject(mx *(VpBaseFig() + 1), "0", true)); if (!mx) { VpSetInf(c,VpGetSign(a)); } @@ -1351,7 +1369,7 @@ BigDecimal_neg(VALUE self) ENTER(5); Real *c, *a; GUARD_OBJ(a, GetVpValue(self, 1)); - GUARD_OBJ(c, VpCreateRbObject(a->Prec *(VpBaseFig() + 1), "0")); + GUARD_OBJ(c, VpCreateRbObject(a->Prec *(VpBaseFig() + 1), "0", true)); VpAsgn(c, a, -1); return VpCheckGetValue(c); } @@ -1393,7 +1411,7 @@ BigDecimal_mult(VALUE self, VALUE r) SAVE(b); mx = a->Prec + b->Prec; - GUARD_OBJ(c, VpCreateRbObject(mx *(VpBaseFig() + 1), "0")); + GUARD_OBJ(c, VpCreateRbObject(mx *(VpBaseFig() + 1), "0", true)); VpMult(c, a, b); return VpCheckGetValue(c); } @@ -1426,8 +1444,8 @@ BigDecimal_divide(Real **c, Real **res, Real **div, VALUE self, VALUE r) mx++; /* NOTE: An additional digit is needed for the compatibility to the version 1.2.1 and the former. */ mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ((*c), VpCreateRbObject(mx, "#0")); - GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0")); + GUARD_OBJ((*c), VpCreateRbObject(mx, "#0", true)); + GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); VpDivd(*c, *res, a, b); return Qnil; } @@ -1492,22 +1510,22 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) rb_raise(rb_eZeroDivError, "divided by 0"); } if (VpIsInf(a)) { - GUARD_OBJ(d, VpCreateRbObject(1, "0")); + GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); VpSetInf(d, (SIGNED_VALUE)(VpGetSign(a) == VpGetSign(b) ? 1 : -1)); - GUARD_OBJ(c, VpCreateRbObject(1, "NaN")); + GUARD_OBJ(c, VpCreateRbObject(1, "NaN", true)); *div = d; *mod = c; return Qtrue; } if (VpIsInf(b)) { - GUARD_OBJ(d, VpCreateRbObject(1, "0")); + GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); *div = d; *mod = a; return Qtrue; } if (VpIsZero(a)) { - GUARD_OBJ(c, VpCreateRbObject(1, "0")); - GUARD_OBJ(d, VpCreateRbObject(1, "0")); + GUARD_OBJ(c, VpCreateRbObject(1, "0", true)); + GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); *div = d; *mod = c; return Qtrue; @@ -1516,17 +1534,17 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) mx = a->Prec + vabs(a->exponent); if (mxPrec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); VpDivd(c, res, a, b); mx = c->Prec * (VpBaseFig() + 1); - GUARD_OBJ(d, VpCreateRbObject(mx, "0")); + GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); VpMult(res, d, b); VpAddSub(c, a, res, -1); if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0")); + GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); VpAddSub(d, c, b, 1); *div = res; *mod = d; @@ -1537,8 +1555,8 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) return Qtrue; NaN: - GUARD_OBJ(c, VpCreateRbObject(1, "NaN")); - GUARD_OBJ(d, VpCreateRbObject(1, "NaN")); + GUARD_OBJ(c, VpCreateRbObject(1, "NaN", true)); + GUARD_OBJ(d, VpCreateRbObject(1, "NaN", true)); *div = d; *mod = c; return Qtrue; @@ -1588,17 +1606,17 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) SAVE(b); mx = (a->MaxPrec + b->MaxPrec) *VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0")); - GUARD_OBJ(rr, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0")); - GUARD_OBJ(ff, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); + GUARD_OBJ(rr, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); + GUARD_OBJ(ff, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); VpDivd(c, res, a, b); mx = c->Prec *(VpBaseFig() + 1); - GUARD_OBJ(d, VpCreateRbObject(mx, "0")); - GUARD_OBJ(f, VpCreateRbObject(mx, "0")); + GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(f, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); /* 0: round off */ @@ -1692,12 +1710,12 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) size_t mx = ix + VpBaseFig()*2; size_t pl = VpSetPrecLimit(0); - GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0")); + GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0", true)); GUARD_OBJ(av, GetVpValue(self, 1)); GUARD_OBJ(bv, GetVpValue(b, 1)); mx = av->Prec + bv->Prec + 2; if (mx <= cv->MaxPrec) mx = cv->MaxPrec + 1; - GUARD_OBJ(res, VpCreateRbObject((mx * 2 + 2)*VpBaseFig(), "#0")); + GUARD_OBJ(res, VpCreateRbObject((mx * 2 + 2)*VpBaseFig(), "#0", true)); VpDivd(cv, res, av, bv); VpSetPrecLimit(pl); VpLeftRound(cv, VpGetRoundMode(), ix); @@ -1830,7 +1848,7 @@ BigDecimal_abs(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec *(VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpAsgn(c, a, 1); VpChangeSign(c, 1); return VpCheckGetValue(c); @@ -1855,7 +1873,7 @@ BigDecimal_sqrt(VALUE self, VALUE nFig) n = GetPrecisionInt(nFig) + VpDblFig() + BASE_FIG; if (mx <= n) mx = n; - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSqrt(c, a); return VpCheckGetValue(c); } @@ -1871,7 +1889,7 @@ BigDecimal_fix(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec *(VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpActiveRound(c, a, VP_ROUND_DOWN, 0); /* 0: round off */ return VpCheckGetValue(c); } @@ -1944,7 +1962,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) pl = VpSetPrecLimit(0); GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSetPrecLimit(pl); VpActiveRound(c, a, sw, iLoc); if (round_to_int) { @@ -1990,7 +2008,7 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_DOWN, iLoc); /* 0: truncate */ if (argc == 0) { @@ -2010,7 +2028,7 @@ BigDecimal_frac(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpFrac(c, a); return VpCheckGetValue(c); } @@ -2050,7 +2068,7 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_FLOOR, iLoc); #ifdef BIGDECIMAL_DEBUG @@ -2096,7 +2114,7 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_CEIL, iLoc); if (argc == 0) { @@ -2404,7 +2422,7 @@ rmpd_power_by_big_decimal(Real const* x, Real const* exp, ssize_t const n) volatile VALUE obj = exp->obj; if (VpIsZero(exp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } log_x = BigMath_log(x->obj, SSIZET2NUM(n+1)); @@ -2442,7 +2460,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) n = NIL_P(prec) ? (ssize_t)(x->Prec*VpBaseFig()) : NUM2SSIZET(prec); if (VpIsNaN(x)) { - y = VpCreateRbObject(n, "0"); + y = VpCreateRbObject(n, "0", true); RB_GC_GUARD(y->obj); VpSetNaN(y); return VpCheckGetValue(y); @@ -2514,7 +2532,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (VpIsZero(x)) { if (is_negative(vexp)) { - y = VpCreateRbObject(n, "#0"); + y = VpCreateRbObject(n, "#0", true); RB_GC_GUARD(y->obj); if (BIGDECIMAL_NEGATIVE_P(x)) { if (is_integer(vexp)) { @@ -2539,15 +2557,15 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) return VpCheckGetValue(y); } else if (is_zero(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } else { - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } if (is_zero(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } else if (is_one(vexp)) { return self; @@ -2559,24 +2577,24 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (is_integer(vexp)) { if (is_even(vexp)) { /* (-Infinity) ** (-even_integer) -> +0 */ - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } else { /* (-Infinity) ** (-odd_integer) -> -0 */ - return VpCheckGetValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } } else { /* (-Infinity) ** (-non_integer) -> -0 */ - return VpCheckGetValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } } else { - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } else { - y = VpCreateRbObject(n, "0"); + y = VpCreateRbObject(n, "0", true); if (BIGDECIMAL_NEGATIVE_P(x)) { if (is_integer(vexp)) { if (is_even(vexp)) { @@ -2605,11 +2623,11 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else if (RB_TYPE_P(vexp, T_BIGNUM)) { VALUE abs_value = BigDecimal_abs(self); if (is_one(abs_value)) { - return VpCheckGetValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { if (is_negative(vexp)) { - y = VpCreateRbObject(n, "0"); + y = VpCreateRbObject(n, "0", true); if (is_even(vexp)) { VpSetInf(y, VpGetSign(x)); } @@ -2619,15 +2637,15 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) return VpCheckGetValue(y); } else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } else { - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } else { if (is_positive(vexp)) { - y = VpCreateRbObject(n, "0"); + y = VpCreateRbObject(n, "0", true); if (is_even(vexp)) { VpSetInf(y, VpGetSign(x)); } @@ -2637,10 +2655,10 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) return VpCheckGetValue(y); } else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } else { - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } } @@ -2652,10 +2670,10 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (VpIsDef(x)) { mp = x->Prec * (VpBaseFig() + 1); - GUARD_OBJ(y, VpCreateRbObject(mp * (ma + 1), "0")); + GUARD_OBJ(y, VpCreateRbObject(mp * (ma + 1), "0", true)); } else { - GUARD_OBJ(y, VpCreateRbObject(1, "0")); + GUARD_OBJ(y, VpCreateRbObject(1, "0", true)); } VpPower(y, x, int_exp); if (!NIL_P(prec) && VpIsDef(y)) { @@ -2747,7 +2765,7 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { double d = RFLOAT_VALUE(val); if (!isfinite(d)) { - Real *vp = VpCreateRbObject(1, NULL); /* vp->obj is allocated */ + Real *vp = VpCreateRbObject(1, NULL, true); /* vp->obj is allocated */ VpDtoV(vp, d); return check_exception(vp->obj); } @@ -2790,13 +2808,9 @@ rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) digs = 0; const char *c_str = StringValueCStr(val); - VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); - Real *vp = VpAlloc(digs, c_str, 1, raise_exception); + Real *vp = VpCreateRbObject(digs, c_str, raise_exception); if (!vp) return Qnil; - RTYPEDDATA_DATA(obj) = vp; - vp->obj = obj; - RB_OBJ_FREEZE(obj); return VpCheckGetValue(vp); } @@ -2825,9 +2839,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) VALUE copy = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); vp = VpCopy(NULL, vp); - RTYPEDDATA_DATA(copy) = vp; - vp->obj = copy; - RB_OBJ_FREEZE(vp->obj); + BigDecimal_wrap_struct(copy, vp); return VpCheckGetValue(vp); } else if (RB_INTEGER_TYPE_P(val)) { @@ -2931,17 +2943,12 @@ f_BigDecimal(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) { - ENTER(1); - char const *c_str = StringValueCStr(str); - VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); - - Real *pv; - GUARD_OBJ(pv, VpAlloc(0, c_str, 0, 1)); - RTYPEDDATA_DATA(obj) = pv; - pv->obj = obj; - RB_OBJ_FREEZE(pv->obj); - return obj; + Real *vp = VpNewRbClass(0, c_str, klass, false, true); + if (!vp) + return Qnil; + else + return VpCheckGetValue(vp); } /* call-seq: @@ -3142,7 +3149,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } else { Real* vy; - vy = VpCreateRbObject(prec, "#0"); + vy = VpCreateRbObject(prec, "#0", true); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); RB_GC_GUARD(vy->obj); return VpCheckGetValue(vy); @@ -3150,7 +3157,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } else if (nan) { Real* vy; - vy = VpCreateRbObject(prec, "#0"); + vy = VpCreateRbObject(prec, "#0", true); VpSetNaN(vy); RB_GC_GUARD(vy->obj); return VpCheckGetValue(vy); @@ -3170,7 +3177,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) VpSetSign(vx, 1); } - one = VpCheckGetValue(VpCreateRbObject(1, "1")); + one = VpCheckGetValue(VpCreateRbObject(1, "1", true)); y = one; d = y; i = 1; @@ -3298,14 +3305,14 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) } if (infinite && !negative) { Real* vy; - vy = VpCreateRbObject(prec, "#0"); + vy = VpCreateRbObject(prec, "#0", true); RB_GC_GUARD(vy->obj); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); return VpCheckGetValue(vy); } else if (nan) { Real* vy; - vy = VpCreateRbObject(prec, "#0"); + vy = VpCreateRbObject(prec, "#0", true); RB_GC_GUARD(vy->obj); VpSetNaN(vy); return VpCheckGetValue(vy); @@ -3319,8 +3326,8 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) } x = VpCheckGetValue(vx); - RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1")); - RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2")); + RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1", true)); + RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2", true)); n = prec + rmpd_double_figures(); RB_GC_GUARD(vn) = SSIZET2NUM(n); @@ -3328,7 +3335,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) if (expo < 0 || expo >= 3) { char buf[DECIMAL_SIZE_OF_BITS(SIZEOF_VALUE * CHAR_BIT) + 4]; snprintf(buf, sizeof(buf), "1E%"PRIdVALUE, -expo); - x = BigDecimal_mult2(x, VpCheckGetValue(VpCreateRbObject(1, buf)), vn); + x = BigDecimal_mult2(x, VpCheckGetValue(VpCreateRbObject(1, buf, true)), vn); } else { expo = 0; diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 80278738..900ebd41 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -285,10 +285,9 @@ typedef struct { * ------------------ */ -VP_EXPORT Real * -VpNewRbClass(size_t mx, char const *str, VALUE klass); +VP_EXPORT Real *VpNewRbClass(size_t mx, char const *str, VALUE klass, bool strict_p, bool raise_exception); -VP_EXPORT Real *VpCreateRbObject(size_t mx,const char *str); +VP_EXPORT Real *VpCreateRbObject(size_t mx, const char *str, bool raise_exception); static inline BDIGIT rmpd_base_value(void) { return RMPD_BASE; } From 1d30d5ad6d6c0485d2ccda27ecef0bb51396d3bc Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 1 Jan 2021 04:12:27 +0900 Subject: [PATCH 142/546] Benchmark: Add creation from small integers --- benchmark/from_integer.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/benchmark/from_integer.yml b/benchmark/from_integer.yml index f98c5722..f17c8374 100644 --- a/benchmark/from_integer.yml +++ b/benchmark/from_integer.yml @@ -11,7 +11,11 @@ contexts: prelude: |- figs = (0..9).to_a + big_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + big_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: + big_n9: BigDecimal(big_n9) + big_n19: BigDecimal(big_n19) big_n10000: BigDecimal(big_n10000) From 3429bd7e6f7059a1e25a0b6d54b692b8bfc65ca7 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 1 Jan 2021 04:13:12 +0900 Subject: [PATCH 143/546] Implement special conversions for 64-bit integers This change improves the conversion speed from small integers. ``` Comparison: big_n9 master: 4003688.9 i/s bigdecimal 3.0.0: 1270551.0 i/s - 3.15x slower big_n19 master: 5410096.4 i/s bigdecimal 3.0.0: 1000250.3 i/s - 5.41x slower ``` --- bigdecimal.gemspec | 3 + ext/bigdecimal/bigdecimal.c | 85 ++++++++++++++++-- ext/bigdecimal/bits.h | 137 +++++++++++++++++++++++++++++ ext/bigdecimal/extconf.rb | 21 +++++ ext/bigdecimal/feature.h | 68 ++++++++++++++ ext/bigdecimal/static_assert.h | 54 ++++++++++++ test/bigdecimal/test_bigdecimal.rb | 17 ++++ 7 files changed, 378 insertions(+), 7 deletions(-) create mode 100644 ext/bigdecimal/bits.h create mode 100644 ext/bigdecimal/feature.h create mode 100644 ext/bigdecimal/static_assert.h diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 15d4e146..c8be0770 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -19,6 +19,9 @@ Gem::Specification.new do |s| bigdecimal.gemspec ext/bigdecimal/bigdecimal.c ext/bigdecimal/bigdecimal.h + ext/bigdecimal/bits.h + ext/bigdecimal/feature.h + ext/bigdecimal/static_assert.h lib/bigdecimal.rb lib/bigdecimal/jacobian.rb lib/bigdecimal/ludcmp.rb diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 96fe253d..5a692428 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -30,18 +30,19 @@ #include #endif +#include "bits.h" +#include "static_assert.h" + /* #define ENABLE_NUMERIC_STRING */ -#define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ - (a) == 0 ? 0 : \ - (a) == -1 ? (b) < -(max) : \ - (a) > 0 ? \ - ((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \ - ((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b))) #define SIGNED_VALUE_MAX INTPTR_MAX #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) +#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) +#define roomof(x, y) (((x) + (y) - 1) / (y)) +#define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) + VALUE rb_cBigDecimal; VALUE rb_mBigMath; @@ -80,6 +81,8 @@ static ID id_half; #define DBLE_FIG rmpd_double_figures() /* figure of double */ #endif +#define LOG10_2 0.3010299956639812 + #ifndef RRATIONAL_ZERO_P # define RRATIONAL_ZERO_P(x) (FIXNUM_P(rb_rational_num(x)) && \ FIX2LONG(rb_rational_num(x)) == 0) @@ -2754,12 +2757,80 @@ check_exception(VALUE bd) } static VALUE -rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); + + Real *vp; + if (uval == 0) { + vp = VpAllocReal(1); + vp->MaxPrec = 1; + vp->Prec = 1; + vp->exponent = 1; + VpSetZero(vp, 1); + vp->frac[0] = 0; + } + else if (uval < BASE) { + vp = VpAllocReal(1); + vp->MaxPrec = 1; + vp->Prec = 1; + vp->exponent = 1; + VpSetSign(vp, 1); + vp->frac[0] = (BDIGIT)uval; + } + else { + const size_t len10 = ceil(LOG10_2 * bit_length(uval)); + size_t len = roomof(len10, BASE_FIG); + + vp = VpAllocReal(len); + vp->MaxPrec = len; + vp->Prec = len; + vp->exponent = len; + VpSetSign(vp, 1); + + size_t i; + for (i = 0; i < len; ++i) { + BDIGIT r = uval % BASE; + vp->frac[len - i - 1] = r; + uval /= BASE; + } + } + + return BigDecimal_wrap_struct(obj, vp); +} + +static VALUE +rb_int64_convert_to_BigDecimal(int64_t ival, size_t digs, int raise_exception) +{ + const uint64_t uval = (ival < 0) ? (((uint64_t)-(ival+1))+1) : (uint64_t)ival; + VALUE bd = rb_uint64_convert_to_BigDecimal(uval, digs, raise_exception); + if (ival < 0) { + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + VpSetSign(vp, -1); + } + return bd; +} + +static VALUE +rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) { Real *vp = GetVpValue(val, 1); return check_exception(vp->obj); } +static VALUE +rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + assert(RB_INTEGER_TYPE_P(val)); + if (FIXNUM_P(val)) { + return rb_int64_convert_to_BigDecimal(FIX2LONG(val), digs, raise_exception); + } + else { + return rb_big_convert_to_BigDecimal(val, digs, raise_exception); + } +} + static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h new file mode 100644 index 00000000..5dfda497 --- /dev/null +++ b/ext/bigdecimal/bits.h @@ -0,0 +1,137 @@ +#ifndef BIGDECIMAL_BITS_H +#define BIGDECIMAL_BITS_H + +#include "feature.h" +#include "static_assert.h" + +#if defined(HAVE_X86INTRIN_H) +# include /* for _lzcnt_u64 */ +#elif defined(_MSC_VER) && _MSC_VER >= 1310 +# include /* for the following intrinsics */ +#endif + +#if defined(_MSC_VER) && defined(__AVX2__) +# pragma intrinsic(__lzcnt) +# pragma intrinsic(__lzcnt64) +#endif + +#define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ + (a) == 0 ? 0 : \ + (a) == -1 ? (b) < -(max) : \ + (a) > 0 ? \ + ((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \ + ((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b))) + +#ifdef HAVE_UINT128_T +# define bit_length(x) \ + (unsigned int) \ + (sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \ + sizeof(x) <= sizeof(int64_t) ? 64 - nlz_int64((uint64_t)(x)) : \ + 128 - nlz_int128((uint128_t)(x))) +#else +# define bit_length(x) \ + (unsigned int) \ + (sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \ + 64 - nlz_int64((uint64_t)(x))) +#endif + +static inline unsigned nlz_int32(uint32_t x); +static inline unsigned nlz_int64(uint64_t x); +#ifdef HAVE_UINT128_T +static inline unsigned nlz_int128(uint128_t x); +#endif + +static inline unsigned int +nlz_int32(uint32_t x) +{ +#if defined(_MSC_VER) && defined(__AVX2__) + /* Note: It seems there is no such thing like __LZCNT__ predefined in MSVC. + * AMD CPUs have had this instruction for decades (since K10) but for + * Intel, Haswell is the oldest one. We need to use __AVX2__ for maximum + * safety. */ + return (unsigned int)__lzcnt(x); + +#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */ + return (unsigned int)_lzcnt_u32(x); + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */ + unsigned long r; + return _BitScanReverse(&r, x) ? (31 - (int)r) : 32; + +#elif __has_builtin(__builtin_clz) + STATIC_ASSERT(sizeof_int, sizeof(int) * CHAR_BIT == 32); + return x ? (unsigned int)__builtin_clz(x) : 32; + +#else + uint32_t y; + unsigned n = 32; + y = x >> 16; if (y) {n -= 16; x = y;} + y = x >> 8; if (y) {n -= 8; x = y;} + y = x >> 4; if (y) {n -= 4; x = y;} + y = x >> 2; if (y) {n -= 2; x = y;} + y = x >> 1; if (y) {return n - 2;} + return (unsigned int)(n - x); +#endif +} + +static inline unsigned int +nlz_int64(uint64_t x) +{ +#if defined(_MSC_VER) && defined(__AVX2__) + return (unsigned int)__lzcnt64(x); + +#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */ + return (unsigned int)_lzcnt_u64(x); + +#elif defined(_WIN64) && defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */ + unsigned long r; + return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; + +#elif __has_builtin(__builtin_clzl) + if (x == 0) { + return 64; + } + else if (sizeof(long) * CHAR_BIT == 64) { + return (unsigned int)__builtin_clzl((unsigned long)x); + } + else if (sizeof(long long) * CHAR_BIT == 64) { + return (unsigned int)__builtin_clzll((unsigned long long)x); + } + else { + /* :FIXME: Is there a way to make this branch a compile-time error? */ + __builtin_unreachable(); + } + +#else + uint64_t y; + unsigned int n = 64; + y = x >> 32; if (y) {n -= 32; x = y;} + y = x >> 16; if (y) {n -= 16; x = y;} + y = x >> 8; if (y) {n -= 8; x = y;} + y = x >> 4; if (y) {n -= 4; x = y;} + y = x >> 2; if (y) {n -= 2; x = y;} + y = x >> 1; if (y) {return n - 2;} + return (unsigned int)(n - x); + +#endif +} + +#ifdef HAVE_UINT128_T +static inline unsigned int +nlz_int128(uint128_t x) +{ + uint64_t y = (uint64_t)(x >> 64); + + if (x == 0) { + return 128; + } + else if (y == 0) { + return (unsigned int)nlz_int64(x) + 64; + } + else { + return (unsigned int)nlz_int64(y); + } +} +#endif + +#endif /* BIGDECIMAL_BITS_H */ diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 41b8df8d..ab856b8d 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -16,6 +16,20 @@ def check_bigdecimal_version(gemspec_path) message "#{bigdecimal_version}\n" end +def have_builtin_func(name, check_expr, opt = "", &b) + checking_for checking_message(name.funcall_style, nil, opt) do + if try_compile(< +#endif + +#ifdef RBIMPL_HAS_BUILTIN +# define BIGDECIMAL_HAS_BUILTIN(...) RBIMPL_HAS_BUILTIN(__VA_ARGS__) + +#else +# /* The following section is copied from CRuby's builtin.h */ +# +# ifdef __has_builtin +# if defined(__INTEL_COMPILER) +# /* :TODO: Intel C Compiler has __has_builtin (since 19.1 maybe?), and is +# * reportedly broken. We have to skip them. However the situation can +# * change. They might improve someday. We need to revisit here later. */ +# elif defined(__GNUC__) && ! __has_builtin(__builtin_alloca) +# /* FreeBSD's defines its own *broken* version of +# * __has_builtin. Cygwin copied that content to be a victim of the +# * broken-ness. We don't take them into account. */ +# else +# define HAVE___HAS_BUILTIN 1 +# endif +# endif +# +# if defined(HAVE___HAS_BUILTIN) +# define BIGDECIMAL_HAS_BUILTIN(_) __has_builtin(_) +# +# elif defined(__GNUC__) +# define BIGDECIMAL_HAS_BUILTIN(_) BIGDECIMAL_HAS_BUILTIN_ ## _ +# if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 6)) +# define BIGDECIMAL_HAS_BUILTIN___builtin_clz 1 +# define BIGDECIMAL_HAS_BUILTIN___builtin_clzl 1 +# else +# define BIGDECIMAL_HAS_BUILTIN___builtin_clz 0 +# define BIGDECIMAL_HAS_BUILTIN___builtin_clzl 0 +# endif +# elif defined(_MSC_VER) +# define BIGDECIMAL_HAS_BUILTIN(_) 0 +# +# else +# define BIGDECIMAL_HAS_BUILTIN(_) BIGDECIMAL_HAS_BUILTIN_ ## _ +# define BIGDECIMAL_HAS_BUILTIN___builtin_clz HAVE_BUILTIN___BUILTIN_CLZ +# define BIGDECIMAL_HAS_BUILTIN___builtin_clzl HAVE_BUILTIN___BUILTIN_CLZL +# endif +#endif /* RBIMPL_HAS_BUILTIN */ + +#ifndef __has_builtin +# define __has_builtin(...) BIGDECIMAL_HAS_BUILTIN(__VA_ARGS__) +#endif + +#endif /* BIGDECIMAL_HAS_FEATURE_H */ diff --git a/ext/bigdecimal/static_assert.h b/ext/bigdecimal/static_assert.h new file mode 100644 index 00000000..9295729b --- /dev/null +++ b/ext/bigdecimal/static_assert.h @@ -0,0 +1,54 @@ +#ifndef BIGDECIMAL_STATIC_ASSERT_H +#define BIGDECIMAL_STATIC_ASSERT_H + +#include "feature.h" + +#ifdef HAVE_RUBY_INTERNAL_STATIC_ASSERT_H +# include +#endif + +#ifdef RBIMPL_STATIC_ASSERT +# define STATIC_ASSERT RBIMPL_STATIC_ASSERT +#endif + +#ifndef STATIC_ASSERT +# /* The following section is copied from CRuby's static_assert.h */ + +# if defined(__cplusplus) && defined(__cpp_static_assert) +# /* https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations */ +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER >= 1600 +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__INTEL_CXX11_MODE__) +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__cplusplus) && __cplusplus >= 201103L +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__cplusplus) && __has_extension(cxx_static_assert) +# define BIGDECIMAL_STATIC_ASSERT0 __extension__ static_assert + +# elif defined(__STDC_VERSION__) && __has_extension(c_static_assert) +# define BIGDECIMAL_STATIC_ASSERT0 __extension__ _Static_assert + +# elif defined(__STDC_VERSION__) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# define BIGDECIMAL_STATIC_ASSERT0 __extension__ _Static_assert +#endif + +# if defined(__DOXYGEN__) +# define STATIC_ASSERT static_assert + +# elif defined(BIGDECIMAL_STATIC_ASSERT0) +# define STATIC_ASSERT(name, expr) \ + BIGDECIMAL_STATIC_ASSERT0(expr, #name ": " #expr) + +# else +# define STATIC_ASSERT(name, expr) \ + typedef int static_assert_ ## name ## _check[1 - 2 * !(expr)] +# endif +#endif /* STATIC_ASSERT */ + + +#endif /* BIGDECIMAL_STATIC_ASSERT_H */ diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 6ce9c3b8..8d90972b 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1,6 +1,7 @@ # frozen_string_literal: false require_relative "testbase" require 'bigdecimal/math' +require 'rbconfig/sizeof' class TestBigDecimal < Test::Unit::TestCase include TestBigDecimalBase @@ -104,10 +105,26 @@ def test_BigDecimal_with_invalid_string end def test_BigDecimal_with_integer + assert_equal(BigDecimal("0"), BigDecimal(0)) assert_equal(BigDecimal("1"), BigDecimal(1)) assert_equal(BigDecimal("-1"), BigDecimal(-1)) assert_equal(BigDecimal((2**100).to_s), BigDecimal(2**100)) assert_equal(BigDecimal((-2**100).to_s), BigDecimal(-2**100)) + + assert_equal(BigDecimal(RbConfig::LIMITS["FIXNUM_MIN"].to_s), + BigDecimal(RbConfig::LIMITS["FIXNUM_MIN"])) + + assert_equal(BigDecimal(RbConfig::LIMITS["FIXNUM_MAX"].to_s), + BigDecimal(RbConfig::LIMITS["FIXNUM_MAX"])) + + assert_equal(BigDecimal(RbConfig::LIMITS["INT64_MIN"].to_s), + BigDecimal(RbConfig::LIMITS["INT64_MIN"])) + + assert_equal(BigDecimal(RbConfig::LIMITS["INT64_MAX"].to_s), + BigDecimal(RbConfig::LIMITS["INT64_MAX"])) + + assert_equal(BigDecimal(RbConfig::LIMITS["UINT64_MAX"].to_s), + BigDecimal(RbConfig::LIMITS["UINT64_MAX"])) end def test_BigDecimal_with_rational From c8087523b0498f650b9e5ea06b00a345da9d07bc Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 1 Jan 2021 16:00:23 +0900 Subject: [PATCH 144/546] Fix test for Ruby 2.4 Ruby 2.4 does not have RbConfig::LIMITS. --- bigdecimal.gemspec | 1 + test/bigdecimal/test_bigdecimal.rb | 30 ++++++++++++++++++++---------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index c8be0770..ed33de8b 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -36,6 +36,7 @@ Gem::Specification.new do |s| s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") s.add_development_dependency "benchmark_driver" + s.add_development_dependency "fiddle" s.add_development_dependency "rake", ">= 12.3.3" s.add_development_dependency "rake-compiler", ">= 0.9" s.add_development_dependency "minitest", "< 5.0.0" diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 8d90972b..070d426e 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -6,6 +6,21 @@ class TestBigDecimal < Test::Unit::TestCase include TestBigDecimalBase + if defined? RbConfig::LIMITS + LIMITS = RbConfig::LIMITS + else + require 'fiddle' + LONG_MAX = (1 << (Fiddle::SIZEOF_LONG*8 - 1)) - 1 + LONG_MIN = [LONG_MAX + 1].pack("L!").unpack("l!")[0] + LIMITS = { + "FIXNUM_MIN" => LONG_MIN / 2, + "FIXNUM_MAX" => LONG_MAX / 2, + "INT64_MIN" => -9223372036854775808, + "INT64_MAX" => 9223372036854775807, + "UINT64_MAX" => 18446744073709551615, + }.freeze + end + ROUNDING_MODE_MAP = [ [ BigDecimal::ROUND_UP, :up], [ BigDecimal::ROUND_DOWN, :down], @@ -111,20 +126,15 @@ def test_BigDecimal_with_integer assert_equal(BigDecimal((2**100).to_s), BigDecimal(2**100)) assert_equal(BigDecimal((-2**100).to_s), BigDecimal(-2**100)) - assert_equal(BigDecimal(RbConfig::LIMITS["FIXNUM_MIN"].to_s), - BigDecimal(RbConfig::LIMITS["FIXNUM_MIN"])) + assert_equal(BigDecimal(LIMITS["FIXNUM_MIN"].to_s), BigDecimal(LIMITS["FIXNUM_MIN"])) - assert_equal(BigDecimal(RbConfig::LIMITS["FIXNUM_MAX"].to_s), - BigDecimal(RbConfig::LIMITS["FIXNUM_MAX"])) + assert_equal(BigDecimal(LIMITS["FIXNUM_MAX"].to_s), BigDecimal(LIMITS["FIXNUM_MAX"])) - assert_equal(BigDecimal(RbConfig::LIMITS["INT64_MIN"].to_s), - BigDecimal(RbConfig::LIMITS["INT64_MIN"])) + assert_equal(BigDecimal(LIMITS["INT64_MIN"].to_s), BigDecimal(LIMITS["INT64_MIN"])) - assert_equal(BigDecimal(RbConfig::LIMITS["INT64_MAX"].to_s), - BigDecimal(RbConfig::LIMITS["INT64_MAX"])) + assert_equal(BigDecimal(LIMITS["INT64_MAX"].to_s), BigDecimal(LIMITS["INT64_MAX"])) - assert_equal(BigDecimal(RbConfig::LIMITS["UINT64_MAX"].to_s), - BigDecimal(RbConfig::LIMITS["UINT64_MAX"])) + assert_equal(BigDecimal(LIMITS["UINT64_MAX"].to_s), BigDecimal(LIMITS["UINT64_MAX"])) end def test_BigDecimal_with_rational From 9bf7a04a3bb31f67fbe7770142c090c9ae7a0dc8 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 1 Jan 2021 16:06:29 +0900 Subject: [PATCH 145/546] Fix benchmark workflow --- .github/workflows/benchmark.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 7ae02d23..8c9aa8d8 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -38,6 +38,7 @@ jobs: run: | bundle install gem install bigdecimal -v 3.0.0 - rake install + + - run: rake compile - run: rake benchmark From bdc1cc6585cf6b1384d5252586403fb26784fb7d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 2 Jan 2021 00:27:46 +0900 Subject: [PATCH 146/546] Fix test_limit Keep the default value of BigDecimal.limit by BigDecimal.save_limit to avoid failures of the other test methods due to the unexpected limit. --- test/bigdecimal/test_bigdecimal.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 070d426e..e9a86cfb 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1519,16 +1519,17 @@ def test_power_with_prec end def test_limit - BigDecimal.limit(1) - x = BigDecimal("3") - assert_equal(90, x ** 4) # OK? must it be 80? - # 3 * 3 * 3 * 3 = 10 * 3 * 3 = 30 * 3 = 90 ??? - assert_raise(ArgumentError) { BigDecimal.limit(-1) } - - bug7458 = '[ruby-core:50269] [#7458]' - one = BigDecimal('1') - epsilon = BigDecimal('0.7E-18') BigDecimal.save_limit do + BigDecimal.limit(1) + x = BigDecimal("3") + assert_equal(90, x ** 4) # OK? must it be 80? + # 3 * 3 * 3 * 3 = 10 * 3 * 3 = 30 * 3 = 90 ??? + assert_raise(ArgumentError) { BigDecimal.limit(-1) } + + bug7458 = '[ruby-core:50269] [#7458]' + one = BigDecimal('1') + epsilon = BigDecimal('0.7E-18') + BigDecimal.limit(0) assert_equal(BigDecimal("1.0000000000000000007"), one + epsilon, "limit(0) #{bug7458}") From 61853fb6d63df41bae6ae0c5e719437affc8984e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 2 Jan 2021 01:09:22 +0900 Subject: [PATCH 147/546] Benchmark: Split into small and large integers --- benchmark/from_large_integer.yml | 17 +++++++++++++++++ ...{from_integer.yml => from_small_integer.yml} | 4 +--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 benchmark/from_large_integer.yml rename benchmark/{from_integer.yml => from_small_integer.yml} (75%) diff --git a/benchmark/from_large_integer.yml b/benchmark/from_large_integer.yml new file mode 100644 index 00000000..5d65938c --- /dev/null +++ b/benchmark/from_large_integer.yml @@ -0,0 +1,17 @@ +loop_count: 1000 + +contexts: +- gems: + bigdecimal: 3.0.0 +- name: "master" + prelude: |- + $LOAD_PATH.unshift(File.expand_path("lib")) + require "bigdecimal" + +prelude: |- + figs = (0..9).to_a + + big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + +benchmark: + big_n10000: BigDecimal(big_n10000) diff --git a/benchmark/from_integer.yml b/benchmark/from_small_integer.yml similarity index 75% rename from benchmark/from_integer.yml rename to benchmark/from_small_integer.yml index f17c8374..021aa7d9 100644 --- a/benchmark/from_integer.yml +++ b/benchmark/from_small_integer.yml @@ -1,4 +1,4 @@ -loop_count: 100 +loop_count: 100000 contexts: - gems: @@ -13,9 +13,7 @@ prelude: |- big_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } big_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } - big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: big_n9: BigDecimal(big_n9) big_n19: BigDecimal(big_n19) - big_n10000: BigDecimal(big_n10000) From 164e0361f9a1d9a8f3dfce6fdbc359ca0f4cdaef Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 2 Jan 2021 09:48:42 +0900 Subject: [PATCH 148/546] Check if x86intrin.h is available not only existing https://github.com/ruby/ruby/commit/5aa28d9d6d --- ext/bigdecimal/extconf.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index ab856b8d..4ad5172e 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -46,7 +46,9 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_builtin_func("__builtin_clzl", "__builtin_clzl(0)") have_header("stdbool.h") -have_header("x86intrin.h") +if have_func("_lzcnt_u64", "x86intrin.h") # check availability + $defs << "-DHAVE_X86INTRIN_H" +end have_func("labs", "stdlib.h") have_func("llabs", "stdlib.h") From d01499a3a4776e88001c173aaf24150091034d7f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 2 Jan 2021 10:18:58 +0900 Subject: [PATCH 149/546] Add __x86_64__ guard to include x86intrin.h https://github.com/ruby/ruby/commit/ef6ab776d5 --- ext/bigdecimal/bits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index 5dfda497..ea9cb108 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -4,7 +4,7 @@ #include "feature.h" #include "static_assert.h" -#if defined(HAVE_X86INTRIN_H) +#if defined(__x86_64__) && defined(HAVE_X86INTRIN_H) # include /* for _lzcnt_u64 */ #elif defined(_MSC_VER) && _MSC_VER >= 1310 # include /* for the following intrinsics */ From e67faafd569bf85e5a4c3a25690fedf67cd3f678 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 2 Jan 2021 12:06:14 +0900 Subject: [PATCH 150/546] Avoid to use __builtin_clzl in SPARC Solaris https://github.com/ruby/ruby/commit/a6bbba1135 --- ext/bigdecimal/bits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index ea9cb108..f39e83b4 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -87,7 +87,7 @@ nlz_int64(uint64_t x) unsigned long r; return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; -#elif __has_builtin(__builtin_clzl) +#elif __has_builtin(__builtin_clzl) && !(defined(__sun) && defined(__sparc)) if (x == 0) { return 64; } From 4a58eddca4e291bed8eb66273fe9ddee58e10704 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 3 Jan 2021 23:19:20 +0900 Subject: [PATCH 151/546] Benchmark: change benchmark names --- benchmark/from_large_integer.yml | 4 ++-- benchmark/from_small_integer.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/benchmark/from_large_integer.yml b/benchmark/from_large_integer.yml index 5d65938c..58437fe0 100644 --- a/benchmark/from_large_integer.yml +++ b/benchmark/from_large_integer.yml @@ -11,7 +11,7 @@ contexts: prelude: |- figs = (0..9).to_a - big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + int_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: - big_n10000: BigDecimal(big_n10000) + int_n10000: BigDecimal(int_n10000) diff --git a/benchmark/from_small_integer.yml b/benchmark/from_small_integer.yml index 021aa7d9..b6640a57 100644 --- a/benchmark/from_small_integer.yml +++ b/benchmark/from_small_integer.yml @@ -11,9 +11,9 @@ contexts: prelude: |- figs = (0..9).to_a - big_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } - big_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + int_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + int_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: - big_n9: BigDecimal(big_n9) - big_n19: BigDecimal(big_n19) + int_n9: BigDecimal(int_n9) + int_n19: BigDecimal(int_n19) From c2b22cc8b3a086dd72271287dc3107b7071e115b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 4 Jan 2021 12:10:09 +0900 Subject: [PATCH 152/546] Add HAVE_FLOAT_H guard --- ext/bigdecimal/bigdecimal.h | 6 ++++-- ext/bigdecimal/extconf.rb | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 900ebd41..e6a0cd52 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -10,9 +10,11 @@ #define RUBY_BIG_DECIMAL_H 1 #define RUBY_NO_OLD_COMPATIBILITY - #include "ruby/ruby.h" -#include + +#ifdef HAVE_FLOAT_H +# include +#endif #if defined(__bool_true_false_are_defined) # /* Take that. */ diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 4ad5172e..463cf1bb 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -45,6 +45,7 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_builtin_func("__builtin_clz", "__builtin_clz(0)") have_builtin_func("__builtin_clzl", "__builtin_clzl(0)") +have_header("float.h") have_header("stdbool.h") if have_func("_lzcnt_u64", "x86intrin.h") # check availability $defs << "-DHAVE_X86INTRIN_H" From 8cbca8481d644a1b329de54bd502d73b84d80bf2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 4 Jan 2021 12:55:22 +0900 Subject: [PATCH 153/546] Move some definitions to missing.h --- ext/bigdecimal/bigdecimal.c | 52 -------- ext/bigdecimal/bigdecimal.h | 116 +----------------- ext/bigdecimal/bits.h | 4 + ext/bigdecimal/extconf.rb | 3 + ext/bigdecimal/missing.h | 232 ++++++++++++++++++++++++++++++++++++ 5 files changed, 240 insertions(+), 167 deletions(-) create mode 100644 ext/bigdecimal/missing.h diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 5a692428..b4f71142 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -39,10 +39,6 @@ #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) -#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) -#define roomof(x, y) (((x) + (y) - 1) / (y)) -#define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) - VALUE rb_cBigDecimal; VALUE rb_mBigMath; @@ -106,54 +102,6 @@ static ID id_half; # define RB_OBJ_STRING(obj) StringValueCStr(obj) #endif -#ifndef HAVE_RB_RATIONAL_NUM -static inline VALUE -rb_rational_num(VALUE rat) -{ -#ifdef HAVE_TYPE_STRUCT_RRATIONAL - return RRATIONAL(rat)->num; -#else - return rb_funcall(rat, rb_intern("numerator"), 0); -#endif -} -#endif - -#ifndef HAVE_RB_RATIONAL_DEN -static inline VALUE -rb_rational_den(VALUE rat) -{ -#ifdef HAVE_TYPE_STRUCT_RRATIONAL - return RRATIONAL(rat)->den; -#else - return rb_funcall(rat, rb_intern("denominator"), 0); -#endif -} -#endif - -#ifndef HAVE_RB_COMPLEX_REAL -static inline VALUE -rb_complex_real(VALUE cmp) -{ -#ifdef HAVE_TYPE_STRUCT_RCOMPLEX - return RCOMPLEX(cmp)->real; -#else - return rb_funcall(cmp, rb_intern("real"), 0); -#endif -} -#endif - -#ifndef HAVE_RB_COMPLEX_IMAG -static inline VALUE -rb_complex_imag(VALUE cmp) -{ -#ifdef HAVE_TYPE_STRUCT_RCOMPLEX - return RCOMPLEX(cmp)->imag; -#else - return rb_funcall(cmp, rb_intern("imag"), 0); -#endif -} -#endif - #define BIGDECIMAL_POSITIVE_P(bd) ((bd)->sign > 0) #define BIGDECIMAL_NEGATIVE_P(bd) ((bd)->sign < 0) diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index e6a0cd52..12b9ee6a 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -11,37 +11,12 @@ #define RUBY_NO_OLD_COMPATIBILITY #include "ruby/ruby.h" +#include "missing.h" #ifdef HAVE_FLOAT_H # include #endif -#if defined(__bool_true_false_are_defined) -# /* Take that. */ - -#elif defined(HAVE_STDBOOL_H) -# include - -#else -typedef unsigned char _Bool; -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)-1) -# define __bool_true_false_are_defined -#endif - -#ifndef RB_UNUSED_VAR -# ifdef __GNUC__ -# define RB_UNUSED_VAR(x) x __attribute__ ((unused)) -# else -# define RB_UNUSED_VAR(x) x -# endif -#endif - -#ifndef UNREACHABLE -# define UNREACHABLE /* unreachable */ -#endif - #undef BDIGIT #undef SIZEOF_BDIGITS #undef BDIGIT_DBL @@ -90,95 +65,6 @@ extern "C" { #endif #endif -#ifndef HAVE_LABS -static inline long -labs(long const x) -{ - if (x < 0) return -x; - return x; -} -#endif - -#ifndef HAVE_LLABS -static inline LONG_LONG -llabs(LONG_LONG const x) -{ - if (x < 0) return -x; - return x; -} -#endif - -#ifndef HAVE_FINITE -static int -finite(double) -{ - return !isnan(n) && !isinf(n); -} -#endif - -#ifndef isfinite -# ifndef HAVE_ISFINITE -# define HAVE_ISFINITE 1 -# define isfinite(x) finite(x) -# endif -#endif - -#ifndef FIX_CONST_VALUE_PTR -# if defined(__fcc__) || defined(__fcc_version) || \ - defined(__FCC__) || defined(__FCC_VERSION) -/* workaround for old version of Fujitsu C Compiler (fcc) */ -# define FIX_CONST_VALUE_PTR(x) ((const VALUE *)(x)) -# else -# define FIX_CONST_VALUE_PTR(x) (x) -# endif -#endif - -#ifndef HAVE_RB_ARRAY_CONST_PTR -static inline const VALUE * -rb_array_const_ptr(VALUE a) -{ - return FIX_CONST_VALUE_PTR((RBASIC(a)->flags & RARRAY_EMBED_FLAG) ? - RARRAY(a)->as.ary : RARRAY(a)->as.heap.ptr); -} -#endif - -#ifndef RARRAY_CONST_PTR -# define RARRAY_CONST_PTR(a) rb_array_const_ptr(a) -#endif - -#ifndef RARRAY_AREF -# define RARRAY_AREF(a, i) (RARRAY_CONST_PTR(a)[i]) -#endif - -#ifndef HAVE_RB_SYM2STR -static inline VALUE -rb_sym2str(VALUE sym) -{ - return rb_id2str(SYM2ID(sym)); -} -#endif - -#ifndef ST2FIX -# undef RB_ST2FIX -# define RB_ST2FIX(h) LONG2FIX((long)(h)) -# define ST2FIX(h) RB_ST2FIX(h) -#endif - -#ifdef vabs -# undef vabs -#endif -#if SIZEOF_VALUE <= SIZEOF_INT -# define vabs abs -#elif SIZEOF_VALUE <= SIZEOF_LONG -# define vabs labs -#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG -# define vabs llabs -#endif - -#if !defined(HAVE_RB_CATEGORY_WARN) || !defined(HAVE_CONST_RB_WARN_CATEGORY_DEPRECATED) -# define rb_category_warn(category, ...) rb_warn(__VA_ARGS__) -#endif - extern VALUE rb_cBigDecimal; #if 0 || SIZEOF_BDIGITS >= 16 diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index f39e83b4..3835fe3a 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -15,6 +15,10 @@ # pragma intrinsic(__lzcnt64) #endif +#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) +#define roomof(x, y) (((x) + (y) - 1) / (y)) +#define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) + #define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ (a) == 0 ? 0 : \ (a) == -1 ? (b) < -(max) : \ diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 463cf1bb..5055e10f 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -46,7 +46,10 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_builtin_func("__builtin_clzl", "__builtin_clzl(0)") have_header("float.h") +have_header("math.h") have_header("stdbool.h") +have_header("stdlib.h") + if have_func("_lzcnt_u64", "x86intrin.h") # check availability $defs << "-DHAVE_X86INTRIN_H" end diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h new file mode 100644 index 00000000..aa056c32 --- /dev/null +++ b/ext/bigdecimal/missing.h @@ -0,0 +1,232 @@ +#ifndef MISSING_H +#define MISSING_H 1 + +#if defined(__cplusplus) +extern "C" { +#if 0 +} /* satisfy cc-mode */ +#endif +#endif + +#ifdef HAVE_STDLIB_H +# include +#endif + +#ifdef HAVE_MATH_H +# include +#endif + +#ifndef RB_UNUSED_VAR +# if defined(_MSC_VER) && _MSC_VER >= 1911 +# define RB_UNUSED_VAR(x) x [[maybe_unused]] + +# elif defined(__has_cpp_attribute) && __has_cpp_attribute(maybe_unused) +# define RB_UNUSED_VAR(x) x [[maybe_unused]] + +# elif defined(__has_c_attribute) && __has_c_attribute(maybe_unused) +# define RB_UNUSED_VAR(x) x [[maybe_unused]] + +# elif defined(__GNUC__) +# define RB_UNUSED_VAR(x) x __attribute__ ((unused)) + +# else +# define RB_UNUSED_VAR(x) x +# endif +#endif /* RB_UNUSED_VAR */ + +#if defined(_MSC_VER) && _MSC_VER >= 1310 +# define HAVE___ASSUME + +#elif defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1300 +# define HAVE___ASSUME +#endif + +#ifndef UNREACHABLE +# if __has_builtin(__builtin_unreachable) +# define UNREACHABLE __builtin_unreachable() + +# elif HAVE___ASSUME +# define UNREACHABLE __assume(0) + +# else +# define UNREACHABLE /* unreachable */ +# endif +#endif /* UNREACHABLE */ + +/* bool */ + +#if defined(__bool_true_false_are_defined) +# /* Take that. */ + +#elif defined(HAVE_STDBOOL_H) +# include + +#else +typedef unsigned char _Bool; +# define bool _Bool +# define true ((_Bool)+1) +# define false ((_Bool)-1) +# define __bool_true_false_are_defined +#endif + +/* abs */ + +#ifndef HAVE_LABS +static inline long +labs(long const x) +{ + if (x < 0) return -x; + return x; +} +#endif + +#ifndef HAVE_LLABS +static inline LONG_LONG +llabs(LONG_LONG const x) +{ + if (x < 0) return -x; + return x; +} +#endif + +#ifdef vabs +# undef vabs +#endif +#if SIZEOF_VALUE <= SIZEOF_INT +# define vabs abs +#elif SIZEOF_VALUE <= SIZEOF_LONG +# define vabs labs +#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG +# define vabs llabs +#endif + +/* finite */ + +#ifndef HAVE_FINITE +static int +finite(double) +{ + return !isnan(n) && !isinf(n); +} +#endif + +#ifndef isfinite +# ifndef HAVE_ISFINITE +# define HAVE_ISFINITE 1 +# define isfinite(x) finite(x) +# endif +#endif + +/* rational */ + +#ifndef HAVE_RB_RATIONAL_NUM +static inline VALUE +rb_rational_num(VALUE rat) +{ +#ifdef HAVE_TYPE_STRUCT_RRATIONAL + return RRATIONAL(rat)->num; +#else + return rb_funcall(rat, rb_intern("numerator"), 0); +#endif +} +#endif + +#ifndef HAVE_RB_RATIONAL_DEN +static inline VALUE +rb_rational_den(VALUE rat) +{ +#ifdef HAVE_TYPE_STRUCT_RRATIONAL + return RRATIONAL(rat)->den; +#else + return rb_funcall(rat, rb_intern("denominator"), 0); +#endif +} +#endif + +/* complex */ + +#ifndef HAVE_RB_COMPLEX_REAL +static inline VALUE +rb_complex_real(VALUE cmp) +{ +#ifdef HAVE_TYPE_STRUCT_RCOMPLEX + return RCOMPLEX(cmp)->real; +#else + return rb_funcall(cmp, rb_intern("real"), 0); +#endif +} +#endif + +#ifndef HAVE_RB_COMPLEX_IMAG +static inline VALUE +rb_complex_imag(VALUE cmp) +{ +# ifdef HAVE_TYPE_STRUCT_RCOMPLEX + return RCOMPLEX(cmp)->imag; +# else + return rb_funcall(cmp, rb_intern("imag"), 0); +# endif +} +#endif + +/* array */ + +#ifndef FIX_CONST_VALUE_PTR +# if defined(__fcc__) || defined(__fcc_version) || \ + defined(__FCC__) || defined(__FCC_VERSION) +/* workaround for old version of Fujitsu C Compiler (fcc) */ +# define FIX_CONST_VALUE_PTR(x) ((const VALUE *)(x)) +# else +# define FIX_CONST_VALUE_PTR(x) (x) +# endif +#endif + +#ifndef HAVE_RB_ARRAY_CONST_PTR +static inline const VALUE * +rb_array_const_ptr(VALUE a) +{ + return FIX_CONST_VALUE_PTR((RBASIC(a)->flags & RARRAY_EMBED_FLAG) ? + RARRAY(a)->as.ary : RARRAY(a)->as.heap.ptr); +} +#endif + +#ifndef RARRAY_CONST_PTR +# define RARRAY_CONST_PTR(a) rb_array_const_ptr(a) +#endif + +#ifndef RARRAY_AREF +# define RARRAY_AREF(a, i) (RARRAY_CONST_PTR(a)[i]) +#endif + +/* symbol */ + +#ifndef HAVE_RB_SYM2STR +static inline VALUE +rb_sym2str(VALUE sym) +{ + return rb_id2str(SYM2ID(sym)); +} +#endif + +/* st */ + +#ifndef ST2FIX +# undef RB_ST2FIX +# define RB_ST2FIX(h) LONG2FIX((long)(h)) +# define ST2FIX(h) RB_ST2FIX(h) +#endif + +/* warning */ + +#if !defined(HAVE_RB_CATEGORY_WARN) || !defined(HAVE_CONST_RB_WARN_CATEGORY_DEPRECATED) +# define rb_category_warn(category, ...) rb_warn(__VA_ARGS__) +#endif + +#if defined(__cplusplus) +#if 0 +{ /* satisfy cc-mode */ +#endif +} /* extern "C" { */ +#endif + +#endif /* MISSING_H */ From f05aecf673eccba2ef4e8bf89597f80a0b02956e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 4 Jan 2021 13:10:40 +0900 Subject: [PATCH 154/546] Add missing.h in bigdecimal.gemspec --- bigdecimal.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index ed33de8b..5dc35db7 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -21,6 +21,7 @@ Gem::Specification.new do |s| ext/bigdecimal/bigdecimal.h ext/bigdecimal/bits.h ext/bigdecimal/feature.h + ext/bigdecimal/missing.h ext/bigdecimal/static_assert.h lib/bigdecimal.rb lib/bigdecimal/jacobian.rb From 9809878872b01c85d6c99094a544d2cd6f79ca7c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 4 Jan 2021 22:08:18 +0900 Subject: [PATCH 155/546] Benchmark: Add creation from Float --- benchmark/from_float.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 benchmark/from_float.yml diff --git a/benchmark/from_float.yml b/benchmark/from_float.yml new file mode 100644 index 00000000..25b362e8 --- /dev/null +++ b/benchmark/from_float.yml @@ -0,0 +1,23 @@ +loop_count: 100000 + +contexts: +- gems: + bigdecimal: 3.0.0 +- name: "master" + prelude: |- + $LOAD_PATH.unshift(File.expand_path("lib")) + require "bigdecimal" + +prelude: |- + flt_e0 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}".to_f + flt_ep10 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e+10".to_f + flt_ep100 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e+100".to_f + flt_em10 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e-10".to_f + flt_em100 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e-100".to_f + +benchmark: + flt_e0: BigDecimal(flt_e0, Float::DIG+1) + flt_ep10: BigDecimal(flt_ep10, Float::DIG+1) + flt_ep100: BigDecimal(flt_ep100, Float::DIG+1) + flt_em10: BigDecimal(flt_em10, Float::DIG+1) + flt_em100: BigDecimal(flt_em100, Float::DIG+1) From cf839a34c878d2283d2870685bbf2bc0ed7bc982 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 5 Jan 2021 08:12:39 +0900 Subject: [PATCH 156/546] Check the function availabilities separately --- ext/bigdecimal/bits.h | 18 +++++++++--------- ext/bigdecimal/extconf.rb | 13 ++++++++++--- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index 3835fe3a..f221119c 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -5,8 +5,8 @@ #include "static_assert.h" #if defined(__x86_64__) && defined(HAVE_X86INTRIN_H) -# include /* for _lzcnt_u64 */ -#elif defined(_MSC_VER) && _MSC_VER >= 1310 +# include /* for _lzcnt_u64, etc. */ +#elif defined(_MSC_VER) && defined(HAVE_INTRIN_H) # include /* for the following intrinsics */ #endif @@ -48,17 +48,17 @@ static inline unsigned nlz_int128(uint128_t x); static inline unsigned int nlz_int32(uint32_t x) { -#if defined(_MSC_VER) && defined(__AVX2__) +#if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT) /* Note: It seems there is no such thing like __LZCNT__ predefined in MSVC. * AMD CPUs have had this instruction for decades (since K10) but for * Intel, Haswell is the oldest one. We need to use __AVX2__ for maximum * safety. */ return (unsigned int)__lzcnt(x); -#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */ +#elif defined(__x86_64__) && defined(HAVE__LZCNT_U32) return (unsigned int)_lzcnt_u32(x); -#elif defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */ +#elif defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE) unsigned long r; return _BitScanReverse(&r, x) ? (31 - (int)r) : 32; @@ -81,17 +81,17 @@ nlz_int32(uint32_t x) static inline unsigned int nlz_int64(uint64_t x) { -#if defined(_MSC_VER) && defined(__AVX2__) +#if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) return (unsigned int)__lzcnt64(x); -#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */ +#elif defined(__x86_64__) && defined(HAVE__LZCNT_U64) return (unsigned int)_lzcnt_u64(x); -#elif defined(_WIN64) && defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */ +#elif defined(_WIN64) && defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE64) unsigned long r; return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; -#elif __has_builtin(__builtin_clzl) && !(defined(__sun) && defined(__sparc)) +#elif __has_builtin(__builtin_clzl) && __has_builtin(__builtin_clzll) && !(defined(__sun) && defined(__sparc)) if (x == 0) { return 64; } diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 5055e10f..d5140e8a 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -44,15 +44,22 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_builtin_func("__builtin_clz", "__builtin_clz(0)") have_builtin_func("__builtin_clzl", "__builtin_clzl(0)") +have_builtin_func("__builtin_clzll", "__builtin_clzll(0)") have_header("float.h") have_header("math.h") have_header("stdbool.h") have_header("stdlib.h") -if have_func("_lzcnt_u64", "x86intrin.h") # check availability - $defs << "-DHAVE_X86INTRIN_H" -end +have_header("x86intrin.h") +have_func("_lzcnt_u32", "x86intrin.h") +have_func("_lzcnt_u64", "x86intrin.h") + +have_header("intrin.h") +have_func("__lzcnt", "intrin.h") +have_func("__lzcnt64", "intrin.h") +have_func("_BitScanReverse", "intrin.h") +have_func("_BitScanReverse64", "intrin.h") have_func("labs", "stdlib.h") have_func("llabs", "stdlib.h") From 75db4dabb98de54104e74de0c2ae1bf6317a9541 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 5 Jan 2021 15:50:38 +0900 Subject: [PATCH 157/546] Restore __LZCNT__ guard --- ext/bigdecimal/bits.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index f221119c..6e1e4776 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -55,7 +55,7 @@ nlz_int32(uint32_t x) * safety. */ return (unsigned int)__lzcnt(x); -#elif defined(__x86_64__) && defined(HAVE__LZCNT_U32) +#elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U32) return (unsigned int)_lzcnt_u32(x); #elif defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE) @@ -84,7 +84,7 @@ nlz_int64(uint64_t x) #if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) return (unsigned int)__lzcnt64(x); -#elif defined(__x86_64__) && defined(HAVE__LZCNT_U64) +#elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U64) return (unsigned int)_lzcnt_u64(x); #elif defined(_WIN64) && defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE64) From 4792a917d806ca1059c952f489413073ea51bf01 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 10:25:45 +0900 Subject: [PATCH 158/546] Optimize the conversion from small Bignum --- ext/bigdecimal/bigdecimal.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b4f71142..a9129086 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2763,8 +2763,32 @@ rb_int64_convert_to_BigDecimal(int64_t ival, size_t digs, int raise_exception) static VALUE rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) { - Real *vp = GetVpValue(val, 1); - return check_exception(vp->obj); + assert(RB_TYPE_P(val, T_BIGNUM)); + + size_t size = rb_absint_size(val, NULL); + int sign = rb_big_cmp(val, INT2FIX(0)); + if (size <= sizeof(long)) { + if (sign < 0) { + return rb_int64_convert_to_BigDecimal(NUM2LONG(val), digs, raise_exception); + } + else { + return rb_uint64_convert_to_BigDecimal(NUM2ULONG(val), digs, raise_exception); + } + } +#if defined(SIZEOF_LONG_LONG) && SIZEOF_LONG < SIZEOF_LONG_LONG + else if (size <= sizeof(LONG_LONG)) { + if (sign < 0) { + return rb_int64_convert_to_BigDecimal(NUM2LL(val), digs, raise_exception); + } + else { + return rb_uint64_convert_to_BigDecimal(NUM2ULL(val), digs, raise_exception); + } + } +#endif + else { + Real *vp = GetVpValue(val, 1); + return check_exception(vp->obj); + } } static VALUE From 686487d9425d8953624c7ab5b17e8d58657b0002 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 11:54:17 +0900 Subject: [PATCH 159/546] Rename BDIGIT to DECDIG --- benchmark/from_small_integer.yml | 2 + ext/bigdecimal/bigdecimal.c | 198 +++++++++++++++---------------- ext/bigdecimal/bigdecimal.h | 81 ++++++------- 3 files changed, 138 insertions(+), 143 deletions(-) diff --git a/benchmark/from_small_integer.yml b/benchmark/from_small_integer.yml index b6640a57..26ce4107 100644 --- a/benchmark/from_small_integer.yml +++ b/benchmark/from_small_integer.yml @@ -13,7 +13,9 @@ prelude: |- int_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } int_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + int_n38 = 37.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: int_n9: BigDecimal(int_n9) int_n19: BigDecimal(int_n19) + int_n38: BigDecimal(int_n38) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a9129086..abb0681d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -115,7 +115,7 @@ static ID id_half; */ static unsigned short VpGetException(void); static void VpSetException(unsigned short f); -static void VpInternalRound(Real *c, size_t ixDigit, BDIGIT vPrev, BDIGIT v); +static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); static Real *VpCopy(Real *pv, Real const* const x); @@ -137,7 +137,7 @@ static size_t BigDecimal_memsize(const void *ptr) { const Real *pv = ptr; - return (sizeof(*pv) + pv->MaxPrec * sizeof(BDIGIT)); + return (sizeof(*pv) + pv->MaxPrec * sizeof(DECDIG)); } #ifndef HAVE_RB_EXT_RACTOR_SAFE @@ -381,7 +381,7 @@ BigDecimal_precision(VALUE self) ex = 0; } else if (p->Prec > 0) { - BDIGIT x = p->frac[0]; + DECDIG x = p->frac[0]; for (precision = 0; x > 0; x /= 10) { ++precision; } @@ -397,7 +397,7 @@ BigDecimal_precision(VALUE self) precision += n * BASE_FIG; if (ex < (ssize_t)p->Prec) { - BDIGIT x = p->frac[n]; + DECDIG x = p->frac[n]; for (; x > 0 && x % 10 == 0; x /= 10) { --precision; } @@ -423,7 +423,7 @@ BigDecimal_n_significant_digits(VALUE self) int nlz, ntz; - BDIGIT x = p->frac[0]; + DECDIG x = p->frac[0]; for (nlz = BASE_FIG; x > 0; x /= 10) --nlz; x = p->frac[n-1]; @@ -452,8 +452,8 @@ BigDecimal_hash(VALUE self) hash = (st_index_t)p->sign; /* hash!=2: the case for 0(1),NaN(0) or +-Infinity(3) is sign itself */ if(hash == 2 || hash == (st_index_t)-2) { - hash ^= rb_memhash(p->frac, sizeof(BDIGIT)*p->Prec); - hash += p->exponent; + hash ^= rb_memhash(p->frac, sizeof(DECDIG)*p->Prec); + hash += p->exponent; } return ST2FIX(hash); } @@ -760,8 +760,8 @@ VpCreateRbObject(size_t mx, const char *str, bool raise_exception) return VpNewRbClass(mx, str, rb_cBigDecimal, true, raise_exception); } -#define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(BDIGIT)) -#define VpReallocReal(ptr, prec) (Real *)VpMemRealloc((ptr), offsetof(Real, frac) + (prec) * sizeof(BDIGIT)) +#define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(DECDIG)) +#define VpReallocReal(ptr, prec) (Real *)VpMemRealloc((ptr), offsetof(Real, frac) + (prec) * sizeof(DECDIG)) static Real * VpCopy(Real *pv, Real const* const x) @@ -774,7 +774,7 @@ VpCopy(Real *pv, Real const* const x) pv->exponent = x->exponent; pv->sign = x->sign; pv->flag = x->flag; - MEMCPY(pv->frac, x->frac, BDIGIT, pv->MaxPrec); + MEMCPY(pv->frac, x->frac, DECDIG, pv->MaxPrec); return pv; } @@ -836,7 +836,7 @@ BigDecimal_to_i(VALUE self) if (e <= 0) return INT2FIX(0); nf = VpBaseFig(); if (e <= nf) { - return LONG2NUM((long)(VpGetSign(p) * (BDIGIT_DBL_SIGNED)p->frac[0])); + return LONG2NUM((long)(VpGetSign(p) * (DECDIG_DBL_SIGNED)p->frac[0])); } else { VALUE a = BigDecimal_split(self); @@ -1424,7 +1424,7 @@ BigDecimal_div(VALUE self, VALUE r) */ /* Round */ if (VpHasVal(div)) { /* frac[0] must be zero for NaN,INF,Zero */ - VpInternalRound(c, 0, c->frac[c->Prec-1], (BDIGIT)(VpBaseVal() * (BDIGIT_DBL)res->frac[0] / div->frac[0])); + VpInternalRound(c, 0, c->frac[c->Prec-1], (DECDIG)(VpBaseVal() * (DECDIG_DBL)res->frac[0] / div->frac[0])); } return VpCheckGetValue(c); } @@ -2724,7 +2724,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->Prec = 1; vp->exponent = 1; VpSetSign(vp, 1); - vp->frac[0] = (BDIGIT)uval; + vp->frac[0] = (DECDIG)uval; } else { const size_t len10 = ceil(LOG10_2 * bit_length(uval)); @@ -2738,7 +2738,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r size_t i; for (i = 0; i < len; ++i) { - BDIGIT r = uval % BASE; + DECDIG r = uval % BASE; vp->frac[len - i - 1] = r; uval /= BASE; } @@ -3809,9 +3809,9 @@ enum op_sw { static int VpIsDefOP(Real *c, Real *a, Real *b, enum op_sw sw); static int AddExponent(Real *a, SIGNED_VALUE n); -static BDIGIT VpAddAbs(Real *a,Real *b,Real *c); -static BDIGIT VpSubAbs(Real *a,Real *b,Real *c); -static size_t VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, BDIGIT *av, BDIGIT *bv); +static DECDIG VpAddAbs(Real *a,Real *b,Real *c); +static DECDIG VpSubAbs(Real *a,Real *b,Real *c); +static size_t VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, DECDIG *av, DECDIG *bv); static int VpNmlz(Real *a); static void VpFormatSt(char *psz, size_t fFmt); static int VpRdup(Real *m, size_t ind_m); @@ -4215,13 +4215,13 @@ VpNumOfChars(Real *vp,const char *pszFmt) * that BASE is as large as possible satisfying the * relation MaxVal <= BASE*(BASE+1). Where the value * MaxVal is the largest value which can be represented - * by one BDIGIT word in the computer used. + * by one DECDIG word in the computer used. * * [Returns] * DBLE_FIG ... OK */ VP_EXPORT size_t -VpInit(BDIGIT BaseVal) +VpInit(DECDIG BaseVal) { /* Setup +/- Inf NaN -0 */ VpGetDoubleNegZero(); @@ -4236,12 +4236,12 @@ VpInit(BDIGIT BaseVal) #ifdef BIGDECIMAL_DEBUG if (gfDebug) { - printf("VpInit: BaseVal = %"PRIuBDIGIT"\n", BaseVal); - printf("\tBASE = %"PRIuBDIGIT"\n", BASE); - printf("\tHALF_BASE = %"PRIuBDIGIT"\n", HALF_BASE); - printf("\tBASE1 = %"PRIuBDIGIT"\n", BASE1); - printf("\tBASE_FIG = %u\n", BASE_FIG); - printf("\tDBLE_FIG = %d\n", DBLE_FIG); + printf("VpInit: BaseVal = %"PRIuDECDIG"\n", BaseVal); + printf("\tBASE = %"PRIuDECDIG"\n", BASE); + printf("\tHALF_BASE = %"PRIuDECDIG"\n", HALF_BASE); + printf("\tBASE1 = %"PRIuDECDIG"\n", BASE1); + printf("\tBASE_FIG = %u\n", BASE_FIG); + printf("\tDBLE_FIG = %d\n", DBLE_FIG); } #endif /* BIGDECIMAL_DEBUG */ @@ -4601,7 +4601,7 @@ VpAsgn(Real *c, Real *a, int isw) VpSetSign(c, isw * VpGetSign(a)); /* set sign */ n = (a->Prec < c->MaxPrec) ? (a->Prec) : (c->MaxPrec); c->Prec = n; - memcpy(c->frac, a->frac, n * sizeof(BDIGIT)); + memcpy(c->frac, a->frac, n * sizeof(DECDIG)); /* Needs round ? */ if (isw != 10) { /* Not in ActiveRound */ @@ -4632,7 +4632,7 @@ VpAddSub(Real *c, Real *a, Real *b, int operation) short sw, isw; Real *a_ptr, *b_ptr; size_t n, na, nb, i; - BDIGIT mrv; + DECDIG mrv; #ifdef BIGDECIMAL_DEBUG if (gfDebug) { @@ -4760,7 +4760,7 @@ VpAddSub(Real *c, Real *a, Real *b, int operation) * a and b assuming abs(a)>abs(b). * c = abs(a) + abs(b) ; where |a|>=|b| */ -static BDIGIT +static DECDIG VpAddAbs(Real *a, Real *b, Real *c) { size_t word_shift; @@ -4770,7 +4770,7 @@ VpAddAbs(Real *a, Real *b, Real *c) size_t a_pos; size_t b_pos, b_pos_with_word_shift; size_t c_pos; - BDIGIT av, bv, carry, mrv; + DECDIG av, bv, carry, mrv; #ifdef BIGDECIMAL_DEBUG if (gfDebug) { @@ -4855,7 +4855,7 @@ VpAddAbs(Real *a, Real *b, Real *c) /* * c = abs(a) - abs(b) */ -static BDIGIT +static DECDIG VpSubAbs(Real *a, Real *b, Real *c) { size_t word_shift; @@ -4865,7 +4865,7 @@ VpSubAbs(Real *a, Real *b, Real *c) size_t a_pos; size_t b_pos, b_pos_with_word_shift; size_t c_pos; - BDIGIT av, bv, borrow, mrv; + DECDIG av, bv, borrow, mrv; #ifdef BIGDECIMAL_DEBUG if (gfDebug) { @@ -4972,7 +4972,7 @@ VpSubAbs(Real *a, Real *b, Real *c) * c_pos = | */ static size_t -VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, BDIGIT *av, BDIGIT *bv) +VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, DECDIG *av, DECDIG *bv) { size_t left_word, right_word, word_shift; @@ -5087,8 +5087,8 @@ VpMult(Real *c, Real *a, Real *b) size_t MxIndA, MxIndB, MxIndAB, MxIndC; size_t ind_c, i, ii, nc; size_t ind_as, ind_ae, ind_bs; - BDIGIT carry; - BDIGIT_DBL s; + DECDIG carry; + DECDIG_DBL s; Real *w; #ifdef BIGDECIMAL_DEBUG @@ -5142,7 +5142,7 @@ VpMult(Real *c, Real *a, Real *b) VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ carry = 0; nc = ind_c = MxIndAB; - memset(c->frac, 0, (nc + 1) * sizeof(BDIGIT)); /* Initialize c */ + memset(c->frac, 0, (nc + 1) * sizeof(DECDIG)); /* Initialize c */ c->Prec = nc + 1; /* set precision */ for (nc = 0; nc < MxIndAB; ++nc, --ind_c) { if (nc < MxIndB) { /* The left triangle of the Fig. */ @@ -5162,15 +5162,15 @@ VpMult(Real *c, Real *a, Real *b) } for (i = ind_as; i <= ind_ae; ++i) { - s = (BDIGIT_DBL)a->frac[i] * b->frac[ind_bs--]; - carry = (BDIGIT)(s / BASE); - s -= (BDIGIT_DBL)carry * BASE; - c->frac[ind_c] += (BDIGIT)s; - if (c->frac[ind_c] >= BASE) { - s = c->frac[ind_c] / BASE; - carry += (BDIGIT)s; - c->frac[ind_c] -= (BDIGIT)(s * BASE); - } + s = (DECDIG_DBL)a->frac[i] * b->frac[ind_bs--]; + carry = (DECDIG)(s / BASE); + s -= (DECDIG_DBL)carry * BASE; + c->frac[ind_c] += (DECDIG)s; + if (c->frac[ind_c] >= BASE) { + s = c->frac[ind_c] / BASE; + carry += (DECDIG)s; + c->frac[ind_c] -= (DECDIG)(s * BASE); + } if (carry) { ii = ind_c; while (ii-- > 0) { @@ -5216,9 +5216,9 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) size_t word_a, word_b, word_c, word_r; size_t i, n, ind_a, ind_b, ind_c, ind_r; size_t nLoop; - BDIGIT_DBL q, b1, b1p1, b1b2, b1b2p1, r1r2; - BDIGIT borrow, borrow1, borrow2; - BDIGIT_DBL qb; + DECDIG_DBL q, b1, b1p1, b1b2, b1b2p1, r1r2; + DECDIG borrow, borrow1, borrow2; + DECDIG_DBL qb; #ifdef BIGDECIMAL_DEBUG if (gfDebug) { @@ -5290,7 +5290,7 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) ++ind_c; continue; } - r1r2 = (BDIGIT_DBL)r->frac[ind_c] * BASE + r->frac[ind_c + 1]; + r1r2 = (DECDIG_DBL)r->frac[ind_c] * BASE + r->frac[ind_c + 1]; if (r1r2 == b1b2) { /* The first two word digits is the same */ ind_b = 2; @@ -5327,17 +5327,17 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) /* The first two word digits is not the same, */ /* then compare magnitude, and divide actually. */ if (r1r2 >= b1b2p1) { - q = r1r2 / b1b2p1; /* q == (BDIGIT)q */ - c->frac[ind_c] += (BDIGIT)q; - ind_r = b->Prec + ind_c - 1; - goto sub_mult; + q = r1r2 / b1b2p1; /* q == (DECDIG)q */ + c->frac[ind_c] += (DECDIG)q; + ind_r = b->Prec + ind_c - 1; + goto sub_mult; } div_b1p1: - if (ind_c + 1 >= word_c) goto out_side; - q = r1r2 / b1p1; /* q == (BDIGIT)q */ - c->frac[ind_c + 1] += (BDIGIT)q; - ind_r = b->Prec + ind_c; + if (ind_c + 1 >= word_c) goto out_side; + q = r1r2 / b1p1; /* q == (DECDIG)q */ + c->frac[ind_c + 1] += (DECDIG)q; + ind_r = b->Prec + ind_c; sub_mult: borrow1 = borrow2 = 0; @@ -5349,16 +5349,16 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) qb = q * b->frac[ind_b]; if (qb < BASE) borrow1 = 0; else { - borrow1 = (BDIGIT)(qb / BASE); - qb -= (BDIGIT_DBL)borrow1 * BASE; /* get qb < BASE */ + borrow1 = (DECDIG)(qb / BASE); + qb -= (DECDIG_DBL)borrow1 * BASE; /* get qb < BASE */ } if(r->frac[ind_r] < qb) { - r->frac[ind_r] += (BDIGIT)(BASE - qb); - borrow2 = borrow2 + borrow1 + 1; + r->frac[ind_r] += (DECDIG)(BASE - qb); + borrow2 = borrow2 + borrow1 + 1; } else { - r->frac[ind_r] -= (BDIGIT)qb; - borrow2 += borrow1; + r->frac[ind_r] -= (DECDIG)qb; + borrow2 += borrow1; } if (borrow2) { if(r->frac[ind_r - 1] < borrow2) { @@ -5440,9 +5440,9 @@ VpNmlz(Real *a) i = 0; while (a->frac[i] == 0) ++i; /* skip the first few zeros */ if (i) { - a->Prec -= i; - if (!AddExponent(a, -(SIGNED_VALUE)i)) return 0; - memmove(&a->frac[0], &a->frac[i], a->Prec*sizeof(BDIGIT)); + a->Prec -= i; + if (!AddExponent(a, -(SIGNED_VALUE)i)) return 0; + memmove(&a->frac[0], &a->frac[i], a->Prec*sizeof(DECDIG)); } return 1; } @@ -5565,7 +5565,7 @@ static int VPrint(FILE *fp, const char *cntl_chr, Real *a) { size_t i, j, nc, nd, ZeroSup, sep = 10; - BDIGIT m, e, nn; + DECDIG m, e, nn; j = 0; nd = nc = 0; /* nd : number of digits in fraction part(every 10 digits, */ @@ -5709,7 +5709,7 @@ VP_EXPORT void VpSzMantissa(Real *a,char *psz) { size_t i, n, ZeroSup; - BDIGIT_DBL m, e, nn; + DECDIG_DBL m, e, nn; if (VpIsNaN(a)) { sprintf(psz, SZ_NaN); @@ -5792,7 +5792,7 @@ VpToString(Real *a, char *psz, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { size_t i, n, ZeroSup; - BDIGIT shift, m, e, nn; + DECDIG shift, m, e, nn; char *pszSav = psz; ssize_t ex; @@ -5840,7 +5840,7 @@ VpToFString(Real *a, char *psz, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { size_t i, n; - BDIGIT m, e, nn; + DECDIG m, e, nn; char *pszSav = psz; ssize_t ex; @@ -5915,7 +5915,7 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con me = ne; signe = 1; exponent_overflow = 0; - memset(a->frac, 0, ma * sizeof(BDIGIT)); + memset(a->frac, 0, ma * sizeof(DECDIG)); if (ne > 0) { i = 0; if (exp_chr[0] == '-') { @@ -6130,7 +6130,7 @@ VpDtoV(Real *m, double d) { size_t ind_m, mm; SIGNED_VALUE ne; - BDIGIT i; + DECDIG i; double val, val2; if (isnan(d)) { @@ -6165,12 +6165,12 @@ VpDtoV(Real *m, double d) /* Now val = 0.xxxxx*BASE**ne */ mm = m->MaxPrec; - memset(m->frac, 0, mm * sizeof(BDIGIT)); + memset(m->frac, 0, mm * sizeof(DECDIG)); for (ind_m = 0; val > 0.0 && ind_m < mm; ind_m++) { - val *= (double)BASE; - i = (BDIGIT)val; - val -= (double)i; - m->frac[ind_m] = i; + val *= (double)BASE; + i = (DECDIG)val; + val -= (double)i; + m->frac[ind_m] = i; } if (ind_m >= mm) ind_m = mm - 1; VpSetSign(m, (d > 0.0) ? 1 : -1); @@ -6178,7 +6178,7 @@ VpDtoV(Real *m, double d) m->exponent = ne; VpInternalRound(m, 0, (m->Prec > 0) ? m->frac[m->Prec-1] : 0, - (BDIGIT)(val*(double)BASE)); + (DECDIG)(val*(double)BASE)); Exit: #ifdef BIGDECIMAL_DEBUG @@ -6374,8 +6374,8 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) /* exptoadd: number of digits needed to compensate negative nf */ int fracf, fracf_1further; ssize_t n,i,ix,ioffset, exptoadd; - BDIGIT v, shifter; - BDIGIT div; + DECDIG v, shifter; + DECDIG div; nf += y->exponent * (ssize_t)BASE_FIG; exptoadd=0; @@ -6397,8 +6397,8 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) n = (ssize_t)BASE_FIG - ioffset - 1; for (shifter = 1, i = 0; i < n; ++i) shifter *= 10; - /* so the representation used (in y->frac) is an array of BDIGIT, where - each BDIGIT contains a value between 0 and BASE-1, consisting of BASE_FIG + /* so the representation used (in y->frac) is an array of DECDIG, where + each DECDIG contains a value between 0 and BASE-1, consisting of BASE_FIG decimal places. (that numbers of decimal places are typed as ssize_t is somewhat confusing) @@ -6406,10 +6406,10 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) nf is now position (in decimal places) of the digit from the start of the array. - ix is the position (in BDIGITS) of the BDIGIT containing the decimal digit, + ix is the position (in DECDIGs) of the DECDIG containing the decimal digit, from the start of the array. - v is the value of this BDIGIT + v is the value of this DECDIG ioffset is the number of extra decimal places along of this decimal digit within v. @@ -6435,7 +6435,7 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) now fracf_1further is whether any of the remaining digits within v are non-zero */ - /* now check all the remaining BDIGITS for zero-ness a whole BDIGIT at a time. + /* now check all the remaining DECDIGs for zero-ness a whole DECDIG at a time. if we spot any non-zeroness, that means that we found a positive digit under rounding position, and we also found a positive digit under one further than the rounding position, so both searches (to see if any such non-zero digit exists) @@ -6454,7 +6454,7 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) now v = the first digit under the rounding position */ /* drop digits after pointed digit */ - memset(y->frac + ix + 1, 0, (y->Prec - (ix + 1)) * sizeof(BDIGIT)); + memset(y->frac + ix + 1, 0, (y->Prec - (ix + 1)) * sizeof(DECDIG)); switch (f) { case VP_ROUND_DOWN: /* Truncate */ @@ -6482,11 +6482,11 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) } else { if (ioffset == 0) { - /* v is the first decimal digit of its BDIGIT; - need to grab the previous BDIGIT if present - to check for evenness of the previous decimal - digit (which is same as that of the BDIGIT since - base 10 has a factor of 2) */ + /* v is the first decimal digit of its DECDIG; + need to grab the previous DECDIG if present + to check for evenness of the previous decimal + digit (which is same as that of the DECDIG since + base 10 has a factor of 2) */ if (ix && (y->frac[ix-1] % 2)) ++div; } else { @@ -6534,7 +6534,7 @@ VpLeftRound(Real *y, unsigned short f, ssize_t nf) * Round from the left hand side of the digits. */ { - BDIGIT v; + DECDIG v; if (!VpHasVal(y)) return 0; /* Unable to round */ v = y->frac[0]; nf -= VpExponent(y) * (ssize_t)BASE_FIG; @@ -6565,7 +6565,7 @@ VpLimitRound(Real *c, size_t ixDigit) /* If I understand correctly, this is only ever used to round off the final decimal digit of precision */ static void -VpInternalRound(Real *c, size_t ixDigit, BDIGIT vPrev, BDIGIT v) +VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v) { int f = 0; @@ -6615,7 +6615,7 @@ VpInternalRound(Real *c, size_t ixDigit, BDIGIT vPrev, BDIGIT v) static int VpRdup(Real *m, size_t ind_m) { - BDIGIT carry; + DECDIG carry; if (!ind_m) ind_m = m->Prec; @@ -6810,12 +6810,12 @@ VpVarCheck(Real * v) } for (i = 0; i < v->Prec; ++i) { if (v->frac[i] >= BASE) { - printf("ERROR(VpVarCheck): Illegal fraction\n"); - printf(" Frac[%"PRIuSIZE"]=%"PRIuBDIGIT"\n", i, v->frac[i]); - printf(" Prec. =%"PRIuSIZE"\n", v->Prec); - printf(" Exp. =%"PRIdVALUE"\n", v->exponent); - printf(" BASE =%"PRIuBDIGIT"\n", BASE); - return 3; + printf("ERROR(VpVarCheck): Illegal fraction\n"); + printf(" Frac[%"PRIuSIZE"]=%"PRIuDECDIG"\n", i, v->frac[i]); + printf(" Prec. =%"PRIuSIZE"\n", v->Prec); + printf(" Exp. =%"PRIdVALUE"\n", v->exponent); + printf(" BASE =%"PRIuDECDIG"\n", BASE); + return 3; } } return 0; diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 12b9ee6a..6b6ac216 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -17,46 +17,39 @@ # include #endif -#undef BDIGIT -#undef SIZEOF_BDIGITS -#undef BDIGIT_DBL -#undef BDIGIT_DBL_SIGNED -#undef PRI_BDIGIT_PREFIX -#undef PRI_BDIGIT_DBL_PREFIX - #ifdef HAVE_INT64_T -# define BDIGIT uint32_t -# define BDIGIT_DBL uint64_t -# define BDIGIT_DBL_SIGNED int64_t -# define SIZEOF_BDIGITS 4 -# define PRI_BDIGIT_PREFIX "" +# define DECDIG uint32_t +# define DECDIG_DBL uint64_t +# define DECDIG_DBL_SIGNED int64_t +# define SIZEOF_DECDIG 4 +# define PRI_DECDIG_PREFIX "" # ifdef PRI_LL_PREFIX -# define PRI_BDIGIT_DBL_PREFIX PRI_LL_PREFIX +# define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX # else -# define PRI_BDIGIT_DBL_PREFIX "l" +# define PRI_DECDIG_DBL_PREFIX "l" # endif #else -# define BDIGIT uint16_t -# define BDIGIT_DBL uint32_t -# define BDIGIT_DBL_SIGNED int32_t -# define SIZEOF_BDIGITS 2 -# define PRI_BDIGIT_PREFIX "h" -# define PRI_BDIGIT_DBL_PREFIX "" +# define DECDIG uint16_t +# define DECDIG_DBL uint32_t +# define DECDIG_DBL_SIGNED int32_t +# define SIZEOF_DECDIG 2 +# define PRI_DECDIG_PREFIX "h" +# define PRI_DECDIG_DBL_PREFIX "" #endif -#define PRIdBDIGIT PRI_BDIGIT_PREFIX"d" -#define PRIiBDIGIT PRI_BDIGIT_PREFIX"i" -#define PRIoBDIGIT PRI_BDIGIT_PREFIX"o" -#define PRIuBDIGIT PRI_BDIGIT_PREFIX"u" -#define PRIxBDIGIT PRI_BDIGIT_PREFIX"x" -#define PRIXBDIGIT PRI_BDIGIT_PREFIX"X" +#define PRIdDECDIG PRI_DECDIG_PREFIX"d" +#define PRIiDECDIG PRI_DECDIG_PREFIX"i" +#define PRIoDECDIG PRI_DECDIG_PREFIX"o" +#define PRIuDECDIG PRI_DECDIG_PREFIX"u" +#define PRIxDECDIG PRI_DECDIG_PREFIX"x" +#define PRIXDECDIG PRI_DECDIG_PREFIX"X" -#define PRIdBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"d" -#define PRIiBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"i" -#define PRIoBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"o" -#define PRIuBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"u" -#define PRIxBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"x" -#define PRIXBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"X" +#define PRIdDECDIG_DBL PRI_DECDIG_DBL_PREFIX"d" +#define PRIiDECDIG_DBL PRI_DECDIG_DBL_PREFIX"i" +#define PRIoDECDIG_DBL PRI_DECDIG_DBL_PREFIX"o" +#define PRIuDECDIG_DBL PRI_DECDIG_DBL_PREFIX"u" +#define PRIxDECDIG_DBL PRI_DECDIG_DBL_PREFIX"x" +#define PRIXDECDIG_DBL PRI_DECDIG_DBL_PREFIX"X" #if defined(__cplusplus) extern "C" { @@ -67,21 +60,21 @@ extern "C" { extern VALUE rb_cBigDecimal; -#if 0 || SIZEOF_BDIGITS >= 16 +#if 0 || SIZEOF_DECDIG >= 16 # define RMPD_COMPONENT_FIGURES 38 -# define RMPD_BASE ((BDIGIT)100000000000000000000000000000000000000U) -#elif SIZEOF_BDIGITS >= 8 +# define RMPD_BASE ((DECDIG)100000000000000000000000000000000000000U) +#elif SIZEOF_DECDIG >= 8 # define RMPD_COMPONENT_FIGURES 19 -# define RMPD_BASE ((BDIGIT)10000000000000000000U) -#elif SIZEOF_BDIGITS >= 4 +# define RMPD_BASE ((DECDIG)10000000000000000000U) +#elif SIZEOF_DECDIG >= 4 # define RMPD_COMPONENT_FIGURES 9 -# define RMPD_BASE ((BDIGIT)1000000000U) -#elif SIZEOF_BDIGITS >= 2 +# define RMPD_BASE ((DECDIG)1000000000U) +#elif SIZEOF_DECDIG >= 2 # define RMPD_COMPONENT_FIGURES 4 -# define RMPD_BASE ((BDIGIT)10000U) +# define RMPD_BASE ((DECDIG)10000U) #else # define RMPD_COMPONENT_FIGURES 2 -# define RMPD_BASE ((BDIGIT)100U) +# define RMPD_BASE ((DECDIG)100U) #endif @@ -164,7 +157,7 @@ typedef struct { * -3 : Negative infinite number */ short flag; /* Not used in vp_routines,space for user. */ - BDIGIT frac[FLEXIBLE_ARRAY_SIZE]; /* Array of fraction part. */ + DECDIG frac[FLEXIBLE_ARRAY_SIZE]; /* Array of fraction part. */ } Real; /* @@ -177,7 +170,7 @@ VP_EXPORT Real *VpNewRbClass(size_t mx, char const *str, VALUE klass, bool stric VP_EXPORT Real *VpCreateRbObject(size_t mx, const char *str, bool raise_exception); -static inline BDIGIT +static inline DECDIG rmpd_base_value(void) { return RMPD_BASE; } static inline size_t rmpd_component_figures(void) { return RMPD_COMPONENT_FIGURES; } @@ -208,7 +201,7 @@ VP_EXPORT int VpException(unsigned short f,const char *str,int always); VP_EXPORT int VpIsNegDoubleZero(double v); #endif VP_EXPORT size_t VpNumOfChars(Real *vp,const char *pszFmt); -VP_EXPORT size_t VpInit(BDIGIT BaseVal); +VP_EXPORT size_t VpInit(DECDIG BaseVal); VP_EXPORT void *VpMemAlloc(size_t mb); VP_EXPORT void *VpMemRealloc(void *ptr, size_t mb); VP_EXPORT void VpFree(Real *pv); From c8de503f8d9713e06727e016c8c81292c4971373 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 13:39:09 +0900 Subject: [PATCH 160/546] Need to convert the return value of rb_big_cmp https://github.com/ruby/ruby/commit/7da06c04b2 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index abb0681d..63de77cc 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2766,7 +2766,7 @@ rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_ex assert(RB_TYPE_P(val, T_BIGNUM)); size_t size = rb_absint_size(val, NULL); - int sign = rb_big_cmp(val, INT2FIX(0)); + int sign = FIX2INT(rb_big_cmp(val, INT2FIX(0))); if (size <= sizeof(long)) { if (sign < 0) { return rb_int64_convert_to_BigDecimal(NUM2LONG(val), digs, raise_exception); From f732201df1a9c6aefa75681e5abadb3dabcf4718 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 14:41:13 +0900 Subject: [PATCH 161/546] Include TestBigDecimalBase in TestBigDecimalUtil --- test/bigdecimal/{testbase.rb => helper.rb} | 0 test/bigdecimal/test_bigdecimal.rb | 2 +- test/bigdecimal/test_bigdecimal_util.rb | 5 +++-- test/bigdecimal/test_bigmath.rb | 2 +- test/bigdecimal/test_ractor.rb | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) rename test/bigdecimal/{testbase.rb => helper.rb} (100%) diff --git a/test/bigdecimal/testbase.rb b/test/bigdecimal/helper.rb similarity index 100% rename from test/bigdecimal/testbase.rb rename to test/bigdecimal/helper.rb diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e9a86cfb..f94cf9b6 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1,5 +1,5 @@ # frozen_string_literal: false -require_relative "testbase" +require_relative "helper" require 'bigdecimal/math' require 'rbconfig/sizeof' diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index b855fd58..3e3d9db1 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -1,9 +1,10 @@ # frozen_string_literal: false -require_relative "testbase" - +require_relative "helper" require 'bigdecimal/util' class TestBigDecimalUtil < Test::Unit::TestCase + include TestBigDecimalBase + def test_BigDecimal_to_d x = BigDecimal(1) assert_same(x, x.to_d) diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 6f271d09..5bf1fbf3 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -1,5 +1,5 @@ # frozen_string_literal: false -require_relative "testbase" +require_relative "helper" require "bigdecimal/math" class TestBigMath < Test::Unit::TestCase diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb index 3ccd7c80..f78663f1 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require_relative "testbase" +require_relative "helper" class TestBigDecimalRactor < Test::Unit::TestCase include TestBigDecimalBase From 2056604d56d1985bdf145793b85071a22db638be Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 18:17:35 +0900 Subject: [PATCH 162/546] Fix trailing zero handling in rb_uint64_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 5 ++++- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 63de77cc..72c7b348 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2736,12 +2736,15 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->exponent = len; VpSetSign(vp, 1); - size_t i; + size_t i, ntz = 0; for (i = 0; i < len; ++i) { DECDIG r = uval % BASE; vp->frac[len - i - 1] = r; + if (r == 0) ++ntz; uval /= BASE; } + + vp->Prec -= ntz; } return BigDecimal_wrap_struct(obj, vp); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f94cf9b6..12b25606 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1948,6 +1948,10 @@ def test_precision_only_integer assert_equal(1, BigDecimal(-1).precision) assert_equal(2, BigDecimal(10).precision) assert_equal(2, BigDecimal(-10).precision) + assert_equal(9, BigDecimal(100_000_000).precision) + assert_equal(9, BigDecimal(-100_000_000).precision) + assert_equal(12, BigDecimal(100_000_000_000).precision) + assert_equal(12, BigDecimal(-100_000_000_000).precision) assert_equal(21, BigDecimal(100_000_000_000_000_000_000).precision) assert_equal(21, BigDecimal(-100_000_000_000_000_000_000).precision) assert_equal(103, BigDecimal("111e100").precision) From 28d383636633366d891ed32540559843f4fcc7a4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 8 Jan 2021 21:00:45 +0900 Subject: [PATCH 163/546] Add test cases of conversion from Float --- test/bigdecimal/test_bigdecimal_util.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 3e3d9db1..9c6973dc 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -25,6 +25,8 @@ def test_Float_to_d_without_precision assert_equal(9.05, 9.05.to_d.to_f) assert_equal("9.050000000000001", 9.05.to_d.to_s('F')) + assert_equal(Math::PI, Math::PI.to_d.to_f) + bug9214 = '[ruby-core:58858]' assert_equal((-0.0).to_d.sign, -1, bug9214) @@ -50,6 +52,10 @@ def test_Float_to_d_bug13331 assert_equal(64.4.to_d, 1.to_d * 64.4, "[ruby-core:80234] [Bug #13331]") + + assert_equal((2*Math::PI).to_d, + 2.to_d * Math::PI, + "[ruby-core:80234] [Bug #13331]") end def test_Rational_to_d From 33e7c502636aa1b11590922830dde7f1259f5f98 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 15:10:20 +0900 Subject: [PATCH 164/546] Stop using GetVpValueWithPrec in rb_float_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 72c7b348..5defd0f1 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2806,10 +2806,13 @@ rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_e } } +static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); + static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { double d = RFLOAT_VALUE(val); + if (!isfinite(d)) { Real *vp = VpCreateRbObject(1, NULL, true); /* vp->obj is allocated */ VpDtoV(vp, d); @@ -2829,7 +2832,18 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) rb_raise(rb_eArgError, "precision too large."); } - Real *vp = GetVpValueWithPrec(val, digs, 1); + if (d != 0.0) { + val = rb_funcall(val, id_to_r, 0); + return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); + } + + Real *vp; + if (1/d < 0.0) { + vp = VpCreateRbObject(digs, "-0", true); + } + else { + vp = VpCreateRbObject(digs, "0", true); + } return check_exception(vp->obj); } From d3c1b0b9218f3106736897044cefeb8c6c926279 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 15:16:35 +0900 Subject: [PATCH 165/546] Use rb_float_convert_to_BigDecimal in GetVpValueWIthPrec --- ext/bigdecimal/bigdecimal.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 5defd0f1..51946154 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -199,6 +199,7 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); +static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) @@ -208,27 +209,14 @@ GetVpValueWithPrec(VALUE v, long prec, int must) VALUE num, bg; char szD[128]; VALUE orig = Qundef; - double d; again: switch(TYPE(v)) { - case T_FLOAT: - if (prec < 0) goto unable_to_coerce_without_prec; - if (prec > (long)DBLE_FIG) goto SomeOneMayDoIt; - d = RFLOAT_VALUE(v); - if (!isfinite(d)) { - pv = VpCreateRbObject(1, NULL, true); - VpDtoV(pv, d); - return pv; - } - if (d != 0.0) { - v = rb_funcall(v, id_to_r, 0); - goto again; - } - if (1/d < 0.0) { - return VpCreateRbObject(prec, "-0", true); - } - return VpCreateRbObject(prec, "0", true); + case T_FLOAT: { + VALUE obj = rb_float_convert_to_BigDecimal(v, prec, must); + TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); + return pv; + } case T_RATIONAL: if (prec < 0) goto unable_to_coerce_without_prec; From 96c9ebd886da2880a9a44124d75d6044ed9d53b0 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 15:19:52 +0900 Subject: [PATCH 166/546] Add assertions for checking the argument types --- ext/bigdecimal/bigdecimal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 51946154..ef17882d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2799,6 +2799,8 @@ static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { + assert(RB_FLOAT_TYPE_P(val)); + double d = RFLOAT_VALUE(val); if (!isfinite(d)) { @@ -2838,6 +2840,8 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { + assert(RB_TYPE_P(val, T_RATIONAL)); + if (digs == SIZE_MAX) { if (!raise_exception) return Qnil; From b4f470da61a23f7863e2ada0edb097e772482cab Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 15:28:08 +0900 Subject: [PATCH 167/546] Stop using GetVpValueWithPrec in rb_rational_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index ef17882d..f440b425 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2849,8 +2849,10 @@ rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) "can't omit precision for a %"PRIsVALUE".", CLASS_OF(val)); } - Real *vp = GetVpValueWithPrec(val, digs, 1); - return check_exception(vp->obj); + + VALUE num = rb_inum_convert_to_BigDecimal(rb_rational_num(val), 0, raise_exception); + VALUE d = BigDecimal_div2(num, rb_rational_den(val), SIZET2NUM(digs)); + return d; } static VALUE From 44a78df8668e0bacb33653591fd65f88938bac78 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 17:24:40 +0900 Subject: [PATCH 168/546] Use rb_rational_convert_to_BigDecimal in GetVpValueWithPrec --- ext/bigdecimal/bigdecimal.c | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f440b425..460456d9 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -200,17 +200,16 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); +static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { ENTER(1); Real *pv; - VALUE num, bg; + VALUE bg; char szD[128]; - VALUE orig = Qundef; -again: switch(TYPE(v)) { case T_FLOAT: { VALUE obj = rb_float_convert_to_BigDecimal(v, prec, must); @@ -218,20 +217,11 @@ GetVpValueWithPrec(VALUE v, long prec, int must) return pv; } - case T_RATIONAL: - if (prec < 0) goto unable_to_coerce_without_prec; - - if (orig == Qundef ? (orig = v, 1) : orig != v) { - num = rb_rational_num(v); - pv = GetVpValueWithPrec(num, -1, must); - if (pv == NULL) goto SomeOneMayDoIt; - - v = BigDecimal_div2(VpCheckGetValue(pv), rb_rational_den(v), LONG2NUM(prec)); - goto again; - } - - v = orig; - goto SomeOneMayDoIt; + case T_RATIONAL: { + VALUE obj = rb_rational_convert_to_BigDecimal(v, prec, must); + TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); + return pv; + } case T_DATA: if (is_kind_of_BigDecimal(v)) { @@ -268,14 +258,6 @@ GetVpValueWithPrec(VALUE v, long prec, int must) cannot_be_coerced_into_BigDecimal(rb_eTypeError, v); } return NULL; /* NULL means to coerce */ - -unable_to_coerce_without_prec: - if (must) { - rb_raise(rb_eArgError, - "%"PRIsVALUE" can't be coerced into BigDecimal without a precision", - RB_OBJ_CLASSNAME(v)); - } - return NULL; } static Real* @@ -2794,8 +2776,6 @@ rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_e } } -static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); - static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { From 034fd2b25e9ecd99bdbbd4483f352854286ada53 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 17:31:23 +0900 Subject: [PATCH 169/546] Stop using GetVpValue in rb_big_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 460456d9..4d8bb071 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2759,7 +2759,10 @@ rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_ex } #endif else { - Real *vp = GetVpValue(val, 1); + VALUE str = rb_big2str(val, 10); + Real *vp = VpCreateRbObject(RSTRING_LEN(str) + BASE_FIG + 1, + RSTRING_PTR(str), true); + RB_GC_GUARD(str); return check_exception(vp->obj); } } From a4a862c9179361832460f9d962e50a0ffb130e2e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 17:34:54 +0900 Subject: [PATCH 170/546] Use rb_inum_convert_to_BigDecimal in GetVpValueWithPrec --- ext/bigdecimal/bigdecimal.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4d8bb071..292514a5 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -199,16 +199,14 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); +static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { - ENTER(1); Real *pv; - VALUE bg; - char szD[128]; switch(TYPE(v)) { case T_FLOAT: { @@ -234,8 +232,11 @@ GetVpValueWithPrec(VALUE v, long prec, int must) break; case T_FIXNUM: - sprintf(szD, "%ld", FIX2LONG(v)); - return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); + case T_BIGNUM: { + VALUE obj = rb_inum_convert_to_BigDecimal(v, prec, must); + TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); + return pv; + } #ifdef ENABLE_NUMERIC_STRING case T_STRING: @@ -244,11 +245,6 @@ GetVpValueWithPrec(VALUE v, long prec, int must) RSTRING_PTR(v), true); #endif /* ENABLE_NUMERIC_STRING */ - case T_BIGNUM: - bg = rb_big2str(v, 10); - PUSH(bg); - return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, - RSTRING_PTR(bg), true); default: goto SomeOneMayDoIt; } From 9d81e7d4e246f15ebdb3717e9189b615e93b8c83 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 21:11:23 +0900 Subject: [PATCH 171/546] WIP: 0.01.to_d --- test/bigdecimal/test_bigdecimal.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 12b25606..6316a204 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -146,6 +146,7 @@ def test_BigDecimal_with_rational def test_BigDecimal_with_float assert_equal(BigDecimal("0.1235"), BigDecimal(0.1234567, 4)) assert_equal(BigDecimal("-0.1235"), BigDecimal(-0.1234567, 4)) + assert_equal(BigDecimal("0.01"), BigDecimal(0.01, Float::DIG + 1)) assert_raise_with_message(ArgumentError, "can't omit precision for a Float.") { BigDecimal(4.2) } assert_raise(ArgumentError) { BigDecimal(0.1, Float::DIG + 2) } assert_nothing_raised { BigDecimal(0.1, Float::DIG + 1) } From f828d72cfb72e42c41c48dee1aef4469e022caeb Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 22:21:24 +0900 Subject: [PATCH 172/546] Revert "Use rb_inum_convert_to_BigDecimal in GetVpValueWithPrec" This reverts commit a4a862c9179361832460f9d962e50a0ffb130e2e. --- ext/bigdecimal/bigdecimal.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 292514a5..4d8bb071 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -199,14 +199,16 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); -static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { + ENTER(1); Real *pv; + VALUE bg; + char szD[128]; switch(TYPE(v)) { case T_FLOAT: { @@ -232,11 +234,8 @@ GetVpValueWithPrec(VALUE v, long prec, int must) break; case T_FIXNUM: - case T_BIGNUM: { - VALUE obj = rb_inum_convert_to_BigDecimal(v, prec, must); - TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); - return pv; - } + sprintf(szD, "%ld", FIX2LONG(v)); + return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); #ifdef ENABLE_NUMERIC_STRING case T_STRING: @@ -245,6 +244,11 @@ GetVpValueWithPrec(VALUE v, long prec, int must) RSTRING_PTR(v), true); #endif /* ENABLE_NUMERIC_STRING */ + case T_BIGNUM: + bg = rb_big2str(v, 10); + PUSH(bg); + return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, + RSTRING_PTR(bg), true); default: goto SomeOneMayDoIt; } From 44f26b9aa0a17ee71adfc8aec183d2d1251fe09f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 10 Jan 2021 08:29:53 +0900 Subject: [PATCH 173/546] Use smallest local variable scope in GetVpValueWithPrec --- ext/bigdecimal/bigdecimal.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4d8bb071..c2f92bfe 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -205,10 +205,7 @@ static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { - ENTER(1); Real *pv; - VALUE bg; - char szD[128]; switch(TYPE(v)) { case T_FLOAT: { @@ -233,9 +230,11 @@ GetVpValueWithPrec(VALUE v, long prec, int must) } break; - case T_FIXNUM: + case T_FIXNUM: { + char szD[128]; sprintf(szD, "%ld", FIX2LONG(v)); return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); + } #ifdef ENABLE_NUMERIC_STRING case T_STRING: @@ -244,11 +243,14 @@ GetVpValueWithPrec(VALUE v, long prec, int must) RSTRING_PTR(v), true); #endif /* ENABLE_NUMERIC_STRING */ - case T_BIGNUM: - bg = rb_big2str(v, 10); - PUSH(bg); - return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, - RSTRING_PTR(bg), true); + case T_BIGNUM: { + VALUE bg = rb_big2str(v, 10); + pv = VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, + RSTRING_PTR(bg), true); + RB_GC_GUARD(bg); + return pv; + } + default: goto SomeOneMayDoIt; } From f047b2786f81cf9f79769fcbc0832e31150e41d9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 10 Jan 2021 08:31:20 +0900 Subject: [PATCH 174/546] Avoid casting negative value to size_t --- ext/bigdecimal/bigdecimal.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c2f92bfe..908a99b8 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -205,17 +205,18 @@ static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { + const size_t digs = prec < 0 ? SIZE_MAX : (long)prec; Real *pv; switch(TYPE(v)) { case T_FLOAT: { - VALUE obj = rb_float_convert_to_BigDecimal(v, prec, must); + VALUE obj = rb_float_convert_to_BigDecimal(v, digs, must); TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); return pv; } case T_RATIONAL: { - VALUE obj = rb_rational_convert_to_BigDecimal(v, prec, must); + VALUE obj = rb_rational_convert_to_BigDecimal(v, digs, must); TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); return pv; } From 2dad4d17b23d1521539845d07469a4fadb0746c0 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 10 Jan 2021 08:37:35 +0900 Subject: [PATCH 175/546] Fix type name --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 908a99b8..d3206a57 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -205,7 +205,7 @@ static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { - const size_t digs = prec < 0 ? SIZE_MAX : (long)prec; + const size_t digs = prec < 0 ? SIZE_MAX : (size_t)prec; Real *pv; switch(TYPE(v)) { From ac230a996e6cbfddd981c1476dc237ce5e6ab45d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 09:19:14 +0900 Subject: [PATCH 176/546] Add rb_cstr_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d3206a57..a4423af7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2842,18 +2842,24 @@ rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } static VALUE -rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception) { if (digs == SIZE_MAX) digs = 0; - const char *c_str = StringValueCStr(val); Real *vp = VpCreateRbObject(digs, c_str, raise_exception); if (!vp) return Qnil; return VpCheckGetValue(vp); } +static inline VALUE +rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + const char *c_str = StringValueCStr(val); + return rb_cstr_convert_to_BigDecimal(c_str, digs, raise_exception); +} + static VALUE rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { From 381ddf5ff6f5d3a99c24f160de5a714e306a67e9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 09:19:19 +0900 Subject: [PATCH 177/546] Use rb_cstr_convert_to_BigDecimal in GetVpValueWithPrec --- ext/bigdecimal/bigdecimal.c | 53 +++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a4423af7..482bc343 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -201,61 +201,58 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); +static VALUE rb_cstr_convert_to_BigDecimal(const char *cstr, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { const size_t digs = prec < 0 ? SIZE_MAX : (size_t)prec; - Real *pv; switch(TYPE(v)) { - case T_FLOAT: { - VALUE obj = rb_float_convert_to_BigDecimal(v, digs, must); - TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); - return pv; - } + case T_FLOAT: + v = rb_float_convert_to_BigDecimal(v, digs, must); + break; - case T_RATIONAL: { - VALUE obj = rb_rational_convert_to_BigDecimal(v, digs, must); - TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); - return pv; - } + case T_RATIONAL: + v = rb_rational_convert_to_BigDecimal(v, digs, must); + break; case T_DATA: - if (is_kind_of_BigDecimal(v)) { - pv = DATA_PTR(v); - return pv; - } - else { - goto SomeOneMayDoIt; - } - break; + if (!is_kind_of_BigDecimal(v)) { + goto SomeOneMayDoIt; + } + break; case T_FIXNUM: { char szD[128]; - sprintf(szD, "%ld", FIX2LONG(v)); - return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); + sprintf(szD, "%ld", FIX2LONG(v)); + v = rb_cstr_convert_to_BigDecimal(szD, VpBaseFig() * 2 + 1, must); + break; } #ifdef ENABLE_NUMERIC_STRING - case T_STRING: - StringValueCStr(v); - return VpCreateRbObject(RSTRING_LEN(v) + VpBaseFig() + 1, - RSTRING_PTR(v), true); + case T_STRING: { + const char *c_str = StringValueCStr(v); + v = rb_cstr_convert_to_BigDecimal(c_str, RSTRING_LEN(v) + VpBaseFig() + 1, must); + break; + } #endif /* ENABLE_NUMERIC_STRING */ case T_BIGNUM: { VALUE bg = rb_big2str(v, 10); - pv = VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, - RSTRING_PTR(bg), true); + v = rb_cstr_convert_to_BigDecimal(RSTRING_PTR(bg), RSTRING_LEN(bg) + VpBaseFig() + 1, must); RB_GC_GUARD(bg); - return pv; + break; } default: goto SomeOneMayDoIt; } + Real *vp; + TypedData_Get_Struct(v, Real, &BigDecimal_data_type, vp); + return vp; + SomeOneMayDoIt: if (must) { cannot_be_coerced_into_BigDecimal(rb_eTypeError, v); From 14e53ed7f67c51265f13b014c94c51f0789ae55c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 16:54:36 +0900 Subject: [PATCH 178/546] Fix length calculation in rb_uint64_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 482bc343..395c12bb 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2697,8 +2697,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->frac[0] = (DECDIG)uval; } else { - const size_t len10 = ceil(LOG10_2 * bit_length(uval)); - size_t len = roomof(len10, BASE_FIG); + const size_t len = (size_t)ceil(log10(uval) / BASE_FIG); vp = VpAllocReal(len); vp->MaxPrec = len; From d163f170a4b9c475be46791311a0ec859c497194 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 22:56:54 +0900 Subject: [PATCH 179/546] Fix exception message raised in Kernel.BigDecimal --- ext/bigdecimal/bigdecimal.c | 29 +++++++++++++++++++---------- test/bigdecimal/test_bigdecimal.rb | 14 +++++++++++++- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 395c12bb..6dd0850b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -201,7 +201,7 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); -static VALUE rb_cstr_convert_to_BigDecimal(const char *cstr, size_t digs, int raise_exception); +static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) @@ -2863,10 +2863,15 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) case Qnil: case Qtrue: case Qfalse: - if (!raise_exception) - return Qnil; - rb_raise(rb_eTypeError, - "can't convert %"PRIsVALUE" into BigDecimal", val); + if (raise_exception) { + const char *cname = NIL_P(val) ? "nil" : + val == Qtrue ? "true" : + val == Qfalse ? "false" : + NULL; + rb_raise(rb_eTypeError, + "can't convert %s into BigDecimal", cname); + } + return Qnil; default: break; @@ -2905,13 +2910,17 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) else if (RB_TYPE_P(val, T_STRING)) { return rb_str_convert_to_BigDecimal(val, digs, raise_exception); } + /* TODO: chheck to_d */ /* TODO: chheck to_int */ - if (!raise_exception) { - VALUE str = rb_check_convert_type(val, T_STRING, "String", "to_str"); - if (NIL_P(str)) - return Qnil; - val = str; + + VALUE str = rb_check_convert_type(val, T_STRING, "String", "to_str"); + if (!RB_TYPE_P(str, T_STRING)) { + if (raise_exception) { + rb_raise(rb_eTypeError, + "can't convert %"PRIsVALUE" into BigDecimal", rb_obj_class(val)); + } + return Qnil; } return rb_str_convert_to_BigDecimal(val, digs, raise_exception); } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 6316a204..4c908d25 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -229,9 +229,18 @@ def test_BigDecimal_with_exception_keyword # assert_nothing_raised(RangeError) { # assert_equal(nil, BigDecimal(1i, exception: false)) # } - assert_raise(TypeError) { + assert_raise_with_message(TypeError, "can't convert nil into BigDecimal") { BigDecimal(nil, exception: true) } + assert_raise_with_message(TypeError, "can't convert true into BigDecimal") { + BigDecimal(true, exception: true) + } + assert_raise_with_message(TypeError, "can't convert false into BigDecimal") { + BigDecimal(false, exception: true) + } + assert_raise_with_message(TypeError, "can't convert Object into BigDecimal") { + BigDecimal(Object.new, exception: true) + } assert_nothing_raised(TypeError) { assert_equal(nil, BigDecimal(nil, exception: false)) } @@ -241,6 +250,9 @@ def test_BigDecimal_with_exception_keyword assert_nothing_raised(TypeError) { assert_equal(nil, BigDecimal(Object.new, exception: false)) } + assert_nothing_raised(TypeError) { + assert_equal(nil, BigDecimal(Object.new, exception: false)) + } # TODO: support to_d # assert_nothing_raised(TypeError) { # o = Object.new From 95c201f2d36eee4a2cfe832771fa47333a2d26fc Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 22:51:29 +0900 Subject: [PATCH 180/546] Use pre-allocated objects for special values --- ext/bigdecimal/bigdecimal.c | 115 +++++++++++++++++++++++------ test/bigdecimal/test_bigdecimal.rb | 8 +- 2 files changed, 101 insertions(+), 22 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6dd0850b..40c4473f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -127,6 +127,12 @@ static int VPrint(FILE *fp,const char *cntl_chr,Real *a); * **** BigDecimal part **** */ +static VALUE BigDecimal_nan(void); +static VALUE BigDecimal_positive_infinity(void); +static VALUE BigDecimal_negative_infinity(void); +static VALUE BigDecimal_positive_zero(void); +static VALUE BigDecimal_negative_zero(void); + static void BigDecimal_delete(void *pv) { @@ -2785,10 +2791,27 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) double d = RFLOAT_VALUE(val); - if (!isfinite(d)) { - Real *vp = VpCreateRbObject(1, NULL, true); /* vp->obj is allocated */ - VpDtoV(vp, d); - return check_exception(vp->obj); + if (isnan(d)) { + VALUE obj = BigDecimal_nan(); + return check_exception(obj); + } + else if (isinf(d)) { + VALUE obj; + if (d > 0) { + obj = BigDecimal_positive_infinity(); + } + else { + obj = BigDecimal_negative_infinity(); + } + return check_exception(obj); + } + else if (d == 0.0) { + if (1/d < 0.0) { + return BigDecimal_negative_zero(); + } + else { + return BigDecimal_positive_zero(); + } } if (digs == SIZE_MAX) { @@ -2804,19 +2827,8 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) rb_raise(rb_eArgError, "precision too large."); } - if (d != 0.0) { - val = rb_funcall(val, id_to_r, 0); - return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); - } - - Real *vp; - if (1/d < 0.0) { - vp = VpCreateRbObject(digs, "-0", true); - } - else { - vp = VpCreateRbObject(digs, "0", true); - } - return check_exception(vp->obj); + val = rb_funcall(val, id_to_r, 0); + return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); } static VALUE @@ -3426,6 +3438,46 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) return y; } +static VALUE BIGDECIMAL_NAN = Qnil; + +static VALUE +BigDecimal_nan(void) +{ + return BIGDECIMAL_NAN; +} + +static VALUE BIGDECIMAL_POSITIVE_INFINITY = Qnil; + +static VALUE +BigDecimal_positive_infinity(void) +{ + return BIGDECIMAL_POSITIVE_INFINITY; +} + +static VALUE BIGDECIMAL_NEGATIVE_INFINITY = Qnil; + +static VALUE +BigDecimal_negative_infinity(void) +{ + return BIGDECIMAL_NEGATIVE_INFINITY; +} + +static VALUE BIGDECIMAL_POSITIVE_ZERO = Qnil; + +static VALUE +BigDecimal_positive_zero(void) +{ + return BIGDECIMAL_POSITIVE_ZERO; +} + +static VALUE BIGDECIMAL_NEGATIVE_ZERO = Qnil; + +static VALUE +BigDecimal_negative_zero(void) +{ + return BIGDECIMAL_NEGATIVE_ZERO; +} + /* Document-class: BigDecimal * BigDecimal provides arbitrary-precision floating point decimal arithmetic. * @@ -3697,13 +3749,34 @@ Init_bigdecimal(void) /* -3: Indicates that a value is negative and infinite. See BigDecimal.sign. */ rb_define_const(rb_cBigDecimal, "SIGN_NEGATIVE_INFINITE", INT2FIX(VP_SIGN_NEGATIVE_INFINITE)); - arg = rb_str_new2("+Infinity"); + /* Positive zero value. */ + arg = rb_str_new2("+0"); + BIGDECIMAL_POSITIVE_ZERO = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_POSITIVE_ZERO); + + /* Negative zero value. */ + arg = rb_str_new2("-0"); + BIGDECIMAL_NEGATIVE_ZERO = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_NEGATIVE_ZERO); + /* Positive infinity value. */ - rb_define_const(rb_cBigDecimal, "INFINITY", f_BigDecimal(1, &arg, rb_cBigDecimal)); - arg = rb_str_new2("NaN"); + arg = rb_str_new2("+Infinity"); + BIGDECIMAL_POSITIVE_INFINITY = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_POSITIVE_INFINITY); + + /* Negative infinity value. */ + arg = rb_str_new2("-Infinity"); + BIGDECIMAL_NEGATIVE_INFINITY = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_NEGATIVE_INFINITY); + /* 'Not a Number' value. */ - rb_define_const(rb_cBigDecimal, "NAN", f_BigDecimal(1, &arg, rb_cBigDecimal)); + arg = rb_str_new2("NaN"); + BIGDECIMAL_NAN = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_NAN); + /* Special value constants */ + rb_define_const(rb_cBigDecimal, "INFINITY", BIGDECIMAL_POSITIVE_INFINITY); + rb_define_const(rb_cBigDecimal, "NAN", BIGDECIMAL_NAN); /* instance methods */ rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 4c908d25..3d3a750a 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -151,17 +151,23 @@ def test_BigDecimal_with_float assert_raise(ArgumentError) { BigDecimal(0.1, Float::DIG + 2) } assert_nothing_raised { BigDecimal(0.1, Float::DIG + 1) } + assert_same(BigDecimal(0.0), BigDecimal(0.0)) + assert_same(BigDecimal(-0.0), BigDecimal(-0.0)) + bug9214 = '[ruby-core:58858]' - assert_equal(BigDecimal(-0.0, Float::DIG).sign, -1, bug9214) + assert_equal(BigDecimal(-0.0).sign, -1, bug9214) BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) assert_nan(BigDecimal(Float::NAN)) + assert_same(BigDecimal(Float::NAN), BigDecimal(Float::NAN)) end BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) assert_positive_infinite(BigDecimal(Float::INFINITY)) + assert_same(BigDecimal(Float::INFINITY), BigDecimal(Float::INFINITY)) assert_negative_infinite(BigDecimal(-Float::INFINITY)) + assert_same(BigDecimal(-Float::INFINITY), BigDecimal(-Float::INFINITY)) end end From ff8eeeb0648b90b7c46d720138ec5c4e60ece60f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 00:43:10 +0900 Subject: [PATCH 181/546] Should not pass the original object but the converted string --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 40c4473f..d1574c9d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2934,7 +2934,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } return Qnil; } - return rb_str_convert_to_BigDecimal(val, digs, raise_exception); + return rb_str_convert_to_BigDecimal(str, digs, raise_exception); } /* call-seq: From 9bfff57f9051f1461f933fff468eddef9f6dbeba Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 01:32:17 +0900 Subject: [PATCH 182/546] Stop using pry in development --- bigdecimal.gemspec | 2 +- bin/console | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 5dc35db7..86ea1801 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -41,5 +41,5 @@ Gem::Specification.new do |s| s.add_development_dependency "rake", ">= 12.3.3" s.add_development_dependency "rake-compiler", ">= 0.9" s.add_development_dependency "minitest", "< 5.0.0" - s.add_development_dependency "pry" + s.add_development_dependency "irb" end diff --git a/bin/console b/bin/console index 43fde8bf..6d23f921 100755 --- a/bin/console +++ b/bin/console @@ -6,5 +6,5 @@ require "bigdecimal" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. -require "pry" -Pry.start +require "irb" +IRB.start From 41ee358af804b2ced5f0555fce96e53adb6ba5fd Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 01:36:25 +0900 Subject: [PATCH 183/546] CI: Limit events --- .github/workflows/benchmark.yml | 10 ++++++++-- .github/workflows/ci.yml | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 8c9aa8d8..5ce8fde8 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,8 +1,14 @@ name: Benchmarking on: -- push -- pull_request + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + - reopened jobs: host: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 106b8040..985e0ec8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,14 @@ name: CI on: -- push -- pull_request + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + - reopened jobs: host: From 5bdaedd53097e85bbe51387209f1f47aba13387b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 22:58:17 +0900 Subject: [PATCH 184/546] Optimize rb_float_convert_to_BigDecimal by using dtoa --- ext/bigdecimal/bigdecimal.c | 117 +- ext/bigdecimal/bigdecimal.h | 3 +- ext/bigdecimal/extconf.rb | 1 + ext/bigdecimal/missing.c | 17 + ext/bigdecimal/missing.h | 3 + ext/bigdecimal/missing/dtoa.c | 3462 +++++++++++++++++++++++++++++++++ 6 files changed, 3599 insertions(+), 4 deletions(-) create mode 100644 ext/bigdecimal/missing.c create mode 100644 ext/bigdecimal/missing/dtoa.c diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d1574c9d..a2f78063 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -74,7 +74,7 @@ static ID id_half; #define BASE1 (BASE/10) #ifndef DBLE_FIG -#define DBLE_FIG rmpd_double_figures() /* figure of double */ +#define DBLE_FIG RMPD_DOUBLE_FIGURES /* figure of double */ #endif #define LOG10_2 0.3010299956639812 @@ -205,6 +205,7 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); +static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); @@ -2827,8 +2828,118 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) rb_raise(rb_eArgError, "precision too large."); } - val = rb_funcall(val, id_to_r, 0); - return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); + /* Use the same logic in flo_to_s to convert a float to a decimal string */ + char buf[DBLE_FIG + BASE_FIG + 2 + 1]; + int decpt, negative_p; + char *e; + char *p = BigDecimal_dtoa(d, 2, digs, &decpt, &negative_p, &e); + int len10 = (int)(e - p); + if (len10 >= (int)sizeof(buf)) + len10 = (int)sizeof(buf) - 1; + memcpy(buf, p, len10); + xfree(p); + + VALUE inum; + size_t RB_UNUSED_VAR(prec) = 0; + size_t exp = 0; + if (decpt > 0) { + if (decpt < len10) { + /* + * len10 |---------------| + * : |-------| frac_len10 = len10 - decpt + * decpt |-------| |--| ntz10 = BASE_FIG - frac_len10 % BASE_FIG + * : : : + * 00 dd dddd.dddd dd 00 + * prec |-----.----.----.-----| prec = exp + roomof(frac_len, BASE_FIG) + * exp |-----.----| exp = roomof(decpt, BASE_FIG) + */ + const size_t frac_len10 = len10 - decpt; + const size_t ntz10 = BASE_FIG - frac_len10 % BASE_FIG; + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + exp = roomof(decpt, BASE_FIG); + prec = exp + roomof(frac_len10, BASE_FIG); + } + else { + /* + * decpt |-----------------------| + * len10 |----------| : + * : |------------| exp10 + * : : : + * 00 dd dddd dd 00 0000 0000.0 + * : : : : + * : |--| ntz10 = exp10 % BASE_FIG + * prec |-----.----.-----| : + * : |----.----| exp10 / BASE_FIG + * exp |-----.----.-----.----.----| + */ + const size_t exp10 = decpt - len10; + const size_t ntz10 = exp10 % BASE_FIG; + + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + prec = roomof(len10 + ntz10, BASE_FIG); + exp = prec + exp10 / BASE_FIG; + } + } + else if (decpt == 0) { + /* + * len10 |------------| + * : : + * 0.dddd dddd dd 00 + * : : : + * : |--| ntz10 = prec * BASE_FIG - len10 + * prec |----.----.-----| roomof(len10, BASE_FIG) + */ + prec = roomof(len10, BASE_FIG); + const size_t ntz10 = prec * BASE_FIG - len10; + + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + } + else { + /* + * len10 |---------------| + * : : + * decpt |-------| |--| ntz10 = prec * BASE_FIG - nlz10 - len10 + * : : : + * 0.0000 00 dd dddd dddd dd 00 + * : : : + * nlz10 |--| : decpt % BASE_FIG + * prec |-----.----.----.-----| roomof(decpt + len10, BASE_FIG) - exp + * exp |----| decpt / BASE_FIG + */ + decpt = -decpt; + + const size_t nlz10 = decpt % BASE_FIG; + exp = decpt / BASE_FIG; + prec = roomof(decpt + len10, BASE_FIG) - exp; + const size_t ntz10 = prec * BASE_FIG - nlz10 - len10; + + if (nlz10 > 0) { + memmove(buf + nlz10, buf, len10); + memset(buf, '0', nlz10); + } + memset(buf + nlz10 + len10, '0', ntz10); + buf[nlz10 + len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + exp = -exp; + } + + VALUE bd = rb_inum_convert_to_BigDecimal(inum, SIZE_MAX, raise_exception); + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + assert(vp->Prec == prec); + vp->exponent = exp; + + if (negative_p) VpSetSign(vp, -1); + return bd; } static VALUE diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 6b6ac216..5f343db6 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -77,6 +77,7 @@ extern VALUE rb_cBigDecimal; # define RMPD_BASE ((DECDIG)100U) #endif +#define RMPD_DOUBLE_FIGURES (1+DBL_DIG) /* * NaN & Infinity @@ -175,7 +176,7 @@ rmpd_base_value(void) { return RMPD_BASE; } static inline size_t rmpd_component_figures(void) { return RMPD_COMPONENT_FIGURES; } static inline size_t -rmpd_double_figures(void) { return 1+DBL_DIG; } +rmpd_double_figures(void) { return RMPD_DOUBLE_FIGURES; } #define VpBaseFig() rmpd_component_figures() #define VpDblFig() rmpd_double_figures() diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index d5140e8a..c92aacb3 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -66,6 +66,7 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_func("finite", "math.h") have_func("isfinite", "math.h") +have_header("ruby/atomic.h") have_header("ruby/internal/has/builtin.h") have_header("ruby/internal/static_assert.h") diff --git a/ext/bigdecimal/missing.c b/ext/bigdecimal/missing.c new file mode 100644 index 00000000..b0bc6eea --- /dev/null +++ b/ext/bigdecimal/missing.c @@ -0,0 +1,17 @@ +#include + +#ifdef HAVE_RUBY_ATOMIC_H +# include +#endif + +#ifdef RUBY_ATOMIC_PTR_CAS +# define ATOMIC_PTR_CAS(var, old, new) RUBY_ATOMIC_PTR_CAS(var, old, new) +#endif + +#undef strtod +#define strtod BigDecimal_strtod +#undef dtoa +#define dtoa BigDecimal_dtoa +#undef hdtoa +#define hdtoa BigDecimal_hdtoa +#include "missing/dtoa.c" diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index aa056c32..11b58c09 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -117,6 +117,9 @@ finite(double) # endif #endif +/* dtoa */ +char *BigDecimal_dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve); + /* rational */ #ifndef HAVE_RB_RATIONAL_NUM diff --git a/ext/bigdecimal/missing/dtoa.c b/ext/bigdecimal/missing/dtoa.c new file mode 100644 index 00000000..41b0a221 --- /dev/null +++ b/ext/bigdecimal/missing/dtoa.c @@ -0,0 +1,3462 @@ +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +/* Please send bug reports to David M. Gay (dmg at acm dot org, + * with " at " changed at "@" and " dot " changed to "."). */ + +/* On a machine with IEEE extended-precision registers, it is + * necessary to specify double-precision (53-bit) rounding precision + * before invoking strtod or dtoa. If the machine uses (the equivalent + * of) Intel 80x87 arithmetic, the call + * _control87(PC_53, MCW_PC); + * does this with many compilers. Whether this or another call is + * appropriate depends on the compiler; for this to work, it may be + * necessary to #include "float.h" or another system-dependent header + * file. + */ + +/* strtod for IEEE-, VAX-, and IBM-arithmetic machines. + * + * This strtod returns a nearest machine number to the input decimal + * string (or sets errno to ERANGE). With IEEE arithmetic, ties are + * broken by the IEEE round-even rule. Otherwise ties are broken by + * biased rounding (add half and chop). + * + * Inspired loosely by William D. Clinger's paper "How to Read Floating + * Point Numbers Accurately" [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * + * 1. We only require IEEE, IBM, or VAX double-precision + * arithmetic (not IEEE double-extended). + * 2. We get by with floating-point arithmetic in a case that + * Clinger missed -- when we're computing d * 10^n + * for a small integer d and the integer n is not too + * much larger than 22 (the maximum integer k for which + * we can represent 10^k exactly), we may be able to + * compute (d*10^k) * 10^(e-k) with just one roundoff. + * 3. Rather than a bit-at-a-time adjustment of the binary + * result in the hard case, we use floating-point + * arithmetic to determine the adjustment to within + * one bit; only in really hard cases do we need to + * compute a second residual. + * 4. Because of 3., we don't need a large table of powers of 10 + * for ten-to-e (just some small tables, e.g. of 10^k + * for 0 <= k <= 22). + */ + +/* + * #define IEEE_LITTLE_ENDIAN for IEEE-arithmetic machines where the least + * significant byte has the lowest address. + * #define IEEE_BIG_ENDIAN for IEEE-arithmetic machines where the most + * significant byte has the lowest address. + * #define Long int on machines with 32-bit ints and 64-bit longs. + * #define IBM for IBM mainframe-style floating-point arithmetic. + * #define VAX for VAX-style floating-point arithmetic (D_floating). + * #define No_leftright to omit left-right logic in fast floating-point + * computation of dtoa. + * #define Honor_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and strtod and dtoa should round accordingly. + * #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and Honor_FLT_ROUNDS is not #defined. + * #define RND_PRODQUOT to use rnd_prod and rnd_quot (assembly routines + * that use extended-precision instructions to compute rounded + * products and quotients) with IBM. + * #define ROUND_BIASED for IEEE-format with biased rounding. + * #define Inaccurate_Divide for IEEE-format with correctly rounded + * products but inaccurate quotients, e.g., for Intel i860. + * #define NO_LONG_LONG on machines that do not have a "long long" + * integer type (of >= 64 bits). On such machines, you can + * #define Just_16 to store 16 bits per 32-bit Long when doing + * high-precision integer arithmetic. Whether this speeds things + * up or slows things down depends on the machine and the number + * being converted. If long long is available and the name is + * something other than "long long", #define Llong to be the name, + * and if "unsigned Llong" does not work as an unsigned version of + * Llong, #define #ULLong to be the corresponding unsigned type. + * #define KR_headers for old-style C function headers. + * #define Bad_float_h if your system lacks a float.h or if it does not + * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, + * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. + * #define MALLOC your_malloc, where your_malloc(n) acts like malloc(n) + * if memory is available and otherwise does something you deem + * appropriate. If MALLOC is undefined, malloc will be invoked + * directly -- and assumed always to succeed. + * #define Omit_Private_Memory to omit logic (added Jan. 1998) for making + * memory allocations from a private pool of memory when possible. + * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, + * unless #defined to be a different length. This default length + * suffices to get rid of MALLOC calls except for unusual cases, + * such as decimal-to-binary conversion of a very long string of + * digits. The longest string dtoa can return is about 751 bytes + * long. For conversions by strtod of strings of 800 digits and + * all dtoa conversions in single-threaded executions with 8-byte + * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte + * pointers, PRIVATE_MEM >= 7112 appears adequate. + * #define INFNAN_CHECK on IEEE systems to cause strtod to check for + * Infinity and NaN (case insensitively). On some systems (e.g., + * some HP systems), it may be necessary to #define NAN_WORD0 + * appropriately -- to the most significant word of a quiet NaN. + * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) + * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, + * strtod also accepts (case insensitively) strings of the form + * NaN(x), where x is a string of hexadecimal digits and spaces; + * if there is only one string of hexadecimal digits, it is taken + * for the 52 fraction bits of the resulting NaN; if there are two + * or more strings of hex digits, the first is for the high 20 bits, + * the second and subsequent for the low 32 bits, with intervening + * white space ignored; but if this results in none of the 52 + * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 + * and NAN_WORD1 are used instead. + * #define MULTIPLE_THREADS if the system offers preemptively scheduled + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed + * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. + * #define NO_IEEE_Scale to disable new (Feb. 1997) logic in strtod that + * avoids underflows on inputs whose result does not underflow. + * If you #define NO_IEEE_Scale on a machine that uses IEEE-format + * floating-point numbers and flushes underflows to zero rather + * than implementing gradual underflow, then you must also #define + * Sudden_Underflow. + * #define YES_ALIAS to permit aliasing certain double values with + * arrays of ULongs. This leads to slightly better code with + * some compilers and was always used prior to 19990916, but it + * is not strictly legal and can cause trouble with aggressively + * optimizing compilers (e.g., gcc 2.95.1 under -O2). + * #define USE_LOCALE to use the current locale's decimal_point value. + * #define SET_INEXACT if IEEE arithmetic is being used and extra + * computation should be done to set the inexact flag when the + * result is inexact and avoid setting inexact when the result + * is exact. In this case, dtoa.c must be compiled in + * an environment, perhaps provided by #include "dtoa.c" in a + * suitable wrapper, that defines two functions, + * int get_inexact(void); + * void clear_inexact(void); + * such that get_inexact() returns a nonzero value if the + * inexact bit is already set, and clear_inexact() sets the + * inexact bit to 0. When SET_INEXACT is #defined, strtod + * also does extra computations to set the underflow and overflow + * flags when appropriate (i.e., when the result is tiny and + * inexact or when it is a numeric value rounded to +-infinity). + * #define NO_ERRNO if strtod should not assign errno = ERANGE when + * the result overflows to +-Infinity or underflows to 0. + */ + +#ifdef WORDS_BIGENDIAN +#define IEEE_BIG_ENDIAN +#else +#define IEEE_LITTLE_ENDIAN +#endif + +#ifdef __vax__ +#define VAX +#undef IEEE_BIG_ENDIAN +#undef IEEE_LITTLE_ENDIAN +#endif + +#if defined(__arm__) && !defined(__VFP_FP__) +#define IEEE_BIG_ENDIAN +#undef IEEE_LITTLE_ENDIAN +#endif + +#undef Long +#undef ULong + +#include + +#if (INT_MAX >> 30) && !(INT_MAX >> 31) +#define Long int +#define ULong unsigned int +#elif (LONG_MAX >> 30) && !(LONG_MAX >> 31) +#define Long long int +#define ULong unsigned long int +#else +#error No 32bit integer +#endif + +#if HAVE_LONG_LONG +#define Llong LONG_LONG +#else +#define NO_LONG_LONG +#endif + +#ifdef DEBUG +#include +#define Bug(x) {fprintf(stderr, "%s\n", (x)); exit(EXIT_FAILURE);} +#endif + +#ifndef ISDIGIT +#include +#define ISDIGIT(c) isdigit(c) +#endif +#include +#include +#include + +#ifdef USE_LOCALE +#include +#endif + +#ifdef MALLOC +extern void *MALLOC(size_t); +#else +#define MALLOC xmalloc +#endif +#ifdef FREE +extern void FREE(void*); +#else +#define FREE xfree +#endif +#ifndef NO_SANITIZE +#define NO_SANITIZE(x, y) y +#endif + +#ifndef Omit_Private_Memory +#ifndef PRIVATE_MEM +#define PRIVATE_MEM 2304 +#endif +#define PRIVATE_mem ((PRIVATE_MEM+sizeof(double)-1)/sizeof(double)) +static double private_mem[PRIVATE_mem], *pmem_next = private_mem; +#endif + +#undef IEEE_Arith +#undef Avoid_Underflow +#ifdef IEEE_BIG_ENDIAN +#define IEEE_Arith +#endif +#ifdef IEEE_LITTLE_ENDIAN +#define IEEE_Arith +#endif + +#ifdef Bad_float_h + +#ifdef IEEE_Arith +#define DBL_DIG 15 +#define DBL_MAX_10_EXP 308 +#define DBL_MAX_EXP 1024 +#define FLT_RADIX 2 +#endif /*IEEE_Arith*/ + +#ifdef IBM +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 75 +#define DBL_MAX_EXP 63 +#define FLT_RADIX 16 +#define DBL_MAX 7.2370055773322621e+75 +#endif + +#ifdef VAX +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 38 +#define DBL_MAX_EXP 127 +#define FLT_RADIX 2 +#define DBL_MAX 1.7014118346046923e+38 +#endif + +#ifndef LONG_MAX +#define LONG_MAX 2147483647 +#endif + +#else /* ifndef Bad_float_h */ +#include +#endif /* Bad_float_h */ + +#include + +#ifdef __cplusplus +extern "C" { +#if 0 +} /* satisfy cc-mode */ +#endif +#endif + +#ifndef hexdigit +static const char hexdigit[] = "0123456789abcdef0123456789ABCDEF"; +#endif + +#if defined(IEEE_LITTLE_ENDIAN) + defined(IEEE_BIG_ENDIAN) + defined(VAX) + defined(IBM) != 1 +Exactly one of IEEE_LITTLE_ENDIAN, IEEE_BIG_ENDIAN, VAX, or IBM should be defined. +#endif + +typedef union { double d; ULong L[2]; } U; + +#ifdef YES_ALIAS +typedef double double_u; +# define dval(x) (x) +# ifdef IEEE_LITTLE_ENDIAN +# define word0(x) (((ULong *)&(x))[1]) +# define word1(x) (((ULong *)&(x))[0]) +# else +# define word0(x) (((ULong *)&(x))[0]) +# define word1(x) (((ULong *)&(x))[1]) +# endif +#else +typedef U double_u; +# ifdef IEEE_LITTLE_ENDIAN +# define word0(x) ((x).L[1]) +# define word1(x) ((x).L[0]) +# else +# define word0(x) ((x).L[0]) +# define word1(x) ((x).L[1]) +# endif +# define dval(x) ((x).d) +#endif + +/* The following definition of Storeinc is appropriate for MIPS processors. + * An alternative that might be better on some machines is + * #define Storeinc(a,b,c) (*a++ = b << 16 | c & 0xffff) + */ +#if defined(IEEE_LITTLE_ENDIAN) + defined(VAX) + defined(__arm__) +#define Storeinc(a,b,c) (((unsigned short *)(a))[1] = (unsigned short)(b), \ +((unsigned short *)(a))[0] = (unsigned short)(c), (a)++) +#else +#define Storeinc(a,b,c) (((unsigned short *)(a))[0] = (unsigned short)(b), \ +((unsigned short *)(a))[1] = (unsigned short)(c), (a)++) +#endif + +/* #define P DBL_MANT_DIG */ +/* Ten_pmax = floor(P*log(2)/log(5)) */ +/* Bletch = (highest power of 2 < DBL_MAX_10_EXP) / 16 */ +/* Quick_max = floor((P-1)*log(FLT_RADIX)/log(10) - 1) */ +/* Int_max = floor(P*log(FLT_RADIX)/log(10) - 1) */ + +#ifdef IEEE_Arith +#define Exp_shift 20 +#define Exp_shift1 20 +#define Exp_msk1 0x100000 +#define Exp_msk11 0x100000 +#define Exp_mask 0x7ff00000 +#define P 53 +#define Bias 1023 +#define Emin (-1022) +#define Exp_1 0x3ff00000 +#define Exp_11 0x3ff00000 +#define Ebits 11 +#define Frac_mask 0xfffff +#define Frac_mask1 0xfffff +#define Ten_pmax 22 +#define Bletch 0x10 +#define Bndry_mask 0xfffff +#define Bndry_mask1 0xfffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 1 +#define Tiny0 0 +#define Tiny1 1 +#define Quick_max 14 +#define Int_max 14 +#ifndef NO_IEEE_Scale +#define Avoid_Underflow +#ifdef Flush_Denorm /* debugging option */ +#undef Sudden_Underflow +#endif +#endif + +#ifndef Flt_Rounds +#ifdef FLT_ROUNDS +#define Flt_Rounds FLT_ROUNDS +#else +#define Flt_Rounds 1 +#endif +#endif /*Flt_Rounds*/ + +#ifdef Honor_FLT_ROUNDS +#define Rounding rounding +#undef Check_FLT_ROUNDS +#define Check_FLT_ROUNDS +#else +#define Rounding Flt_Rounds +#endif + +#else /* ifndef IEEE_Arith */ +#undef Check_FLT_ROUNDS +#undef Honor_FLT_ROUNDS +#undef SET_INEXACT +#undef Sudden_Underflow +#define Sudden_Underflow +#ifdef IBM +#undef Flt_Rounds +#define Flt_Rounds 0 +#define Exp_shift 24 +#define Exp_shift1 24 +#define Exp_msk1 0x1000000 +#define Exp_msk11 0x1000000 +#define Exp_mask 0x7f000000 +#define P 14 +#define Bias 65 +#define Exp_1 0x41000000 +#define Exp_11 0x41000000 +#define Ebits 8 /* exponent has 7 bits, but 8 is the right value in b2d */ +#define Frac_mask 0xffffff +#define Frac_mask1 0xffffff +#define Bletch 4 +#define Ten_pmax 22 +#define Bndry_mask 0xefffff +#define Bndry_mask1 0xffffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 4 +#define Tiny0 0x100000 +#define Tiny1 0 +#define Quick_max 14 +#define Int_max 15 +#else /* VAX */ +#undef Flt_Rounds +#define Flt_Rounds 1 +#define Exp_shift 23 +#define Exp_shift1 7 +#define Exp_msk1 0x80 +#define Exp_msk11 0x800000 +#define Exp_mask 0x7f80 +#define P 56 +#define Bias 129 +#define Exp_1 0x40800000 +#define Exp_11 0x4080 +#define Ebits 8 +#define Frac_mask 0x7fffff +#define Frac_mask1 0xffff007f +#define Ten_pmax 24 +#define Bletch 2 +#define Bndry_mask 0xffff007f +#define Bndry_mask1 0xffff007f +#define LSB 0x10000 +#define Sign_bit 0x8000 +#define Log2P 1 +#define Tiny0 0x80 +#define Tiny1 0 +#define Quick_max 15 +#define Int_max 15 +#endif /* IBM, VAX */ +#endif /* IEEE_Arith */ + +#ifndef IEEE_Arith +#define ROUND_BIASED +#endif + +#ifdef RND_PRODQUOT +#define rounded_product(a,b) ((a) = rnd_prod((a), (b))) +#define rounded_quotient(a,b) ((a) = rnd_quot((a), (b))) +extern double rnd_prod(double, double), rnd_quot(double, double); +#else +#define rounded_product(a,b) ((a) *= (b)) +#define rounded_quotient(a,b) ((a) /= (b)) +#endif + +#define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1)) +#define Big1 0xffffffff + +#ifndef Pack_32 +#define Pack_32 +#endif + +#define FFFFFFFF 0xffffffffUL + +#ifdef NO_LONG_LONG +#undef ULLong +#ifdef Just_16 +#undef Pack_32 +/* When Pack_32 is not defined, we store 16 bits per 32-bit Long. + * This makes some inner loops simpler and sometimes saves work + * during multiplications, but it often seems to make things slightly + * slower. Hence the default is now to store 32 bits per Long. + */ +#endif +#else /* long long available */ +#ifndef Llong +#define Llong long long +#endif +#ifndef ULLong +#define ULLong unsigned Llong +#endif +#endif /* NO_LONG_LONG */ + +#define MULTIPLE_THREADS 1 + +#ifndef MULTIPLE_THREADS +#define ACQUIRE_DTOA_LOCK(n) /*nothing*/ +#define FREE_DTOA_LOCK(n) /*nothing*/ +#else +#define ACQUIRE_DTOA_LOCK(n) /*unused right now*/ +#define FREE_DTOA_LOCK(n) /*unused right now*/ +#endif + +#ifndef ATOMIC_PTR_CAS +#define ATOMIC_PTR_CAS(var, old, new) ((var) = (new), (old)) +#endif +#ifndef LIKELY +#define LIKELY(x) (x) +#endif +#ifndef UNLIKELY +#define UNLIKELY(x) (x) +#endif +#ifndef ASSUME +#define ASSUME(x) (void)(x) +#endif + +#define Kmax 15 + +struct Bigint { + struct Bigint *next; + int k, maxwds, sign, wds; + ULong x[1]; +}; + +typedef struct Bigint Bigint; + +static Bigint *freelist[Kmax+1]; + +static Bigint * +Balloc(int k) +{ + int x; + Bigint *rv; +#ifndef Omit_Private_Memory + size_t len; +#endif + + rv = 0; + ACQUIRE_DTOA_LOCK(0); + if (k <= Kmax) { + rv = freelist[k]; + while (rv) { + Bigint *rvn = rv; + rv = ATOMIC_PTR_CAS(freelist[k], rv, rv->next); + if (LIKELY(rvn == rv)) { + ASSUME(rv); + break; + } + } + } + if (!rv) { + x = 1 << k; +#ifdef Omit_Private_Memory + rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); +#else + len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) + /sizeof(double); + if (k <= Kmax) { + double *pnext = pmem_next; + while (pnext - private_mem + len <= PRIVATE_mem) { + double *p = pnext; + pnext = ATOMIC_PTR_CAS(pmem_next, pnext, pnext + len); + if (LIKELY(p == pnext)) { + rv = (Bigint*)pnext; + ASSUME(rv); + break; + } + } + } + if (!rv) + rv = (Bigint*)MALLOC(len*sizeof(double)); +#endif + rv->k = k; + rv->maxwds = x; + } + FREE_DTOA_LOCK(0); + rv->sign = rv->wds = 0; + return rv; +} + +static void +Bfree(Bigint *v) +{ + Bigint *vn; + if (v) { + if (v->k > Kmax) { + FREE(v); + return; + } + ACQUIRE_DTOA_LOCK(0); + do { + vn = v->next = freelist[v->k]; + } while (UNLIKELY(ATOMIC_PTR_CAS(freelist[v->k], vn, v) != vn)); + FREE_DTOA_LOCK(0); + } +} + +#define Bcopy(x,y) memcpy((char *)&(x)->sign, (char *)&(y)->sign, \ +(y)->wds*sizeof(Long) + 2*sizeof(int)) + +static Bigint * +multadd(Bigint *b, int m, int a) /* multiply by m and add a */ +{ + int i, wds; + ULong *x; +#ifdef ULLong + ULLong carry, y; +#else + ULong carry, y; +#ifdef Pack_32 + ULong xi, z; +#endif +#endif + Bigint *b1; + + wds = b->wds; + x = b->x; + i = 0; + carry = a; + do { +#ifdef ULLong + y = *x * (ULLong)m + carry; + carry = y >> 32; + *x++ = (ULong)(y & FFFFFFFF); +#else +#ifdef Pack_32 + xi = *x; + y = (xi & 0xffff) * m + carry; + z = (xi >> 16) * m + (y >> 16); + carry = z >> 16; + *x++ = (z << 16) + (y & 0xffff); +#else + y = *x * m + carry; + carry = y >> 16; + *x++ = y & 0xffff; +#endif +#endif + } while (++i < wds); + if (carry) { + if (wds >= b->maxwds) { + b1 = Balloc(b->k+1); + Bcopy(b1, b); + Bfree(b); + b = b1; + } + b->x[wds++] = (ULong)carry; + b->wds = wds; + } + return b; +} + +static Bigint * +s2b(const char *s, int nd0, int nd, ULong y9) +{ + Bigint *b; + int i, k; + Long x, y; + + x = (nd + 8) / 9; + for (k = 0, y = 1; x > y; y <<= 1, k++) ; +#ifdef Pack_32 + b = Balloc(k); + b->x[0] = y9; + b->wds = 1; +#else + b = Balloc(k+1); + b->x[0] = y9 & 0xffff; + b->wds = (b->x[1] = y9 >> 16) ? 2 : 1; +#endif + + i = 9; + if (9 < nd0) { + s += 9; + do { + b = multadd(b, 10, *s++ - '0'); + } while (++i < nd0); + s++; + } + else + s += 10; + for (; i < nd; i++) + b = multadd(b, 10, *s++ - '0'); + return b; +} + +static int +hi0bits(register ULong x) +{ + register int k = 0; + + if (!(x & 0xffff0000)) { + k = 16; + x <<= 16; + } + if (!(x & 0xff000000)) { + k += 8; + x <<= 8; + } + if (!(x & 0xf0000000)) { + k += 4; + x <<= 4; + } + if (!(x & 0xc0000000)) { + k += 2; + x <<= 2; + } + if (!(x & 0x80000000)) { + k++; + if (!(x & 0x40000000)) + return 32; + } + return k; +} + +static int +lo0bits(ULong *y) +{ + register int k; + register ULong x = *y; + + if (x & 7) { + if (x & 1) + return 0; + if (x & 2) { + *y = x >> 1; + return 1; + } + *y = x >> 2; + return 2; + } + k = 0; + if (!(x & 0xffff)) { + k = 16; + x >>= 16; + } + if (!(x & 0xff)) { + k += 8; + x >>= 8; + } + if (!(x & 0xf)) { + k += 4; + x >>= 4; + } + if (!(x & 0x3)) { + k += 2; + x >>= 2; + } + if (!(x & 1)) { + k++; + x >>= 1; + if (!x) + return 32; + } + *y = x; + return k; +} + +static Bigint * +i2b(int i) +{ + Bigint *b; + + b = Balloc(1); + b->x[0] = i; + b->wds = 1; + return b; +} + +static Bigint * +mult(Bigint *a, Bigint *b) +{ + Bigint *c; + int k, wa, wb, wc; + ULong *x, *xa, *xae, *xb, *xbe, *xc, *xc0; + ULong y; +#ifdef ULLong + ULLong carry, z; +#else + ULong carry, z; +#ifdef Pack_32 + ULong z2; +#endif +#endif + + if (a->wds < b->wds) { + c = a; + a = b; + b = c; + } + k = a->k; + wa = a->wds; + wb = b->wds; + wc = wa + wb; + if (wc > a->maxwds) + k++; + c = Balloc(k); + for (x = c->x, xa = x + wc; x < xa; x++) + *x = 0; + xa = a->x; + xae = xa + wa; + xb = b->x; + xbe = xb + wb; + xc0 = c->x; +#ifdef ULLong + for (; xb < xbe; xc0++) { + if ((y = *xb++) != 0) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * (ULLong)y + *xc + carry; + carry = z >> 32; + *xc++ = (ULong)(z & FFFFFFFF); + } while (x < xae); + *xc = (ULong)carry; + } + } +#else +#ifdef Pack_32 + for (; xb < xbe; xb++, xc0++) { + if ((y = *xb & 0xffff) != 0) { + x = xa; + xc = xc0; + carry = 0; + do { + z = (*x & 0xffff) * y + (*xc & 0xffff) + carry; + carry = z >> 16; + z2 = (*x++ >> 16) * y + (*xc >> 16) + carry; + carry = z2 >> 16; + Storeinc(xc, z2, z); + } while (x < xae); + *xc = (ULong)carry; + } + if ((y = *xb >> 16) != 0) { + x = xa; + xc = xc0; + carry = 0; + z2 = *xc; + do { + z = (*x & 0xffff) * y + (*xc >> 16) + carry; + carry = z >> 16; + Storeinc(xc, z, z2); + z2 = (*x++ >> 16) * y + (*xc & 0xffff) + carry; + carry = z2 >> 16; + } while (x < xae); + *xc = z2; + } + } +#else + for (; xb < xbe; xc0++) { + if (y = *xb++) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * y + *xc + carry; + carry = z >> 16; + *xc++ = z & 0xffff; + } while (x < xae); + *xc = (ULong)carry; + } + } +#endif +#endif + for (xc0 = c->x, xc = xc0 + wc; wc > 0 && !*--xc; --wc) ; + c->wds = wc; + return c; +} + +static Bigint *p5s; + +static Bigint * +pow5mult(Bigint *b, int k) +{ + Bigint *b1, *p5, *p51; + Bigint *p5tmp; + int i; + static const int p05[3] = { 5, 25, 125 }; + + if ((i = k & 3) != 0) + b = multadd(b, p05[i-1], 0); + + if (!(k >>= 2)) + return b; + if (!(p5 = p5s)) { + /* first time */ + ACQUIRE_DTOA_LOCK(1); + if (!(p5 = p5s)) { + p5 = i2b(625); + p5->next = 0; + p5tmp = ATOMIC_PTR_CAS(p5s, NULL, p5); + if (UNLIKELY(p5tmp)) { + Bfree(p5); + p5 = p5tmp; + } + } + FREE_DTOA_LOCK(1); + } + for (;;) { + if (k & 1) { + b1 = mult(b, p5); + Bfree(b); + b = b1; + } + if (!(k >>= 1)) + break; + if (!(p51 = p5->next)) { + ACQUIRE_DTOA_LOCK(1); + if (!(p51 = p5->next)) { + p51 = mult(p5,p5); + p51->next = 0; + p5tmp = ATOMIC_PTR_CAS(p5->next, NULL, p51); + if (UNLIKELY(p5tmp)) { + Bfree(p51); + p51 = p5tmp; + } + } + FREE_DTOA_LOCK(1); + } + p5 = p51; + } + return b; +} + +static Bigint * +lshift(Bigint *b, int k) +{ + int i, k1, n, n1; + Bigint *b1; + ULong *x, *x1, *xe, z; + +#ifdef Pack_32 + n = k >> 5; +#else + n = k >> 4; +#endif + k1 = b->k; + n1 = n + b->wds + 1; + for (i = b->maxwds; n1 > i; i <<= 1) + k1++; + b1 = Balloc(k1); + x1 = b1->x; + for (i = 0; i < n; i++) + *x1++ = 0; + x = b->x; + xe = x + b->wds; +#ifdef Pack_32 + if (k &= 0x1f) { + k1 = 32 - k; + z = 0; + do { + *x1++ = *x << k | z; + z = *x++ >> k1; + } while (x < xe); + if ((*x1 = z) != 0) + ++n1; + } +#else + if (k &= 0xf) { + k1 = 16 - k; + z = 0; + do { + *x1++ = *x << k & 0xffff | z; + z = *x++ >> k1; + } while (x < xe); + if (*x1 = z) + ++n1; + } +#endif + else + do { + *x1++ = *x++; + } while (x < xe); + b1->wds = n1 - 1; + Bfree(b); + return b1; +} + +static int +cmp(Bigint *a, Bigint *b) +{ + ULong *xa, *xa0, *xb, *xb0; + int i, j; + + i = a->wds; + j = b->wds; +#ifdef DEBUG + if (i > 1 && !a->x[i-1]) + Bug("cmp called with a->x[a->wds-1] == 0"); + if (j > 1 && !b->x[j-1]) + Bug("cmp called with b->x[b->wds-1] == 0"); +#endif + if (i -= j) + return i; + xa0 = a->x; + xa = xa0 + j; + xb0 = b->x; + xb = xb0 + j; + for (;;) { + if (*--xa != *--xb) + return *xa < *xb ? -1 : 1; + if (xa <= xa0) + break; + } + return 0; +} + +NO_SANITIZE("unsigned-integer-overflow", static Bigint * diff(Bigint *a, Bigint *b)); +static Bigint * +diff(Bigint *a, Bigint *b) +{ + Bigint *c; + int i, wa, wb; + ULong *xa, *xae, *xb, *xbe, *xc; +#ifdef ULLong + ULLong borrow, y; +#else + ULong borrow, y; +#ifdef Pack_32 + ULong z; +#endif +#endif + + i = cmp(a,b); + if (!i) { + c = Balloc(0); + c->wds = 1; + c->x[0] = 0; + return c; + } + if (i < 0) { + c = a; + a = b; + b = c; + i = 1; + } + else + i = 0; + c = Balloc(a->k); + c->sign = i; + wa = a->wds; + xa = a->x; + xae = xa + wa; + wb = b->wds; + xb = b->x; + xbe = xb + wb; + xc = c->x; + borrow = 0; +#ifdef ULLong + do { + y = (ULLong)*xa++ - *xb++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = (ULong)(y & FFFFFFFF); + } while (xb < xbe); + while (xa < xae) { + y = *xa++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = (ULong)(y & FFFFFFFF); + } +#else +#ifdef Pack_32 + do { + y = (*xa & 0xffff) - (*xb & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - (*xb++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } while (xb < xbe); + while (xa < xae) { + y = (*xa & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } +#else + do { + y = *xa++ - *xb++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } while (xb < xbe); + while (xa < xae) { + y = *xa++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } +#endif +#endif + while (!*--xc) + wa--; + c->wds = wa; + return c; +} + +static double +ulp(double x_) +{ + register Long L; + double_u x, a; + dval(x) = x_; + + L = (word0(x) & Exp_mask) - (P-1)*Exp_msk1; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + if (L > 0) { +#endif +#endif +#ifdef IBM + L |= Exp_msk1 >> 4; +#endif + word0(a) = L; + word1(a) = 0; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + } + else { + L = -L >> Exp_shift; + if (L < Exp_shift) { + word0(a) = 0x80000 >> L; + word1(a) = 0; + } + else { + word0(a) = 0; + L -= Exp_shift; + word1(a) = L >= 31 ? 1 : 1 << 31 - L; + } + } +#endif +#endif + return dval(a); +} + +static double +b2d(Bigint *a, int *e) +{ + ULong *xa, *xa0, w, y, z; + int k; + double_u d; +#ifdef VAX + ULong d0, d1; +#else +#define d0 word0(d) +#define d1 word1(d) +#endif + + xa0 = a->x; + xa = xa0 + a->wds; + y = *--xa; +#ifdef DEBUG + if (!y) Bug("zero y in b2d"); +#endif + k = hi0bits(y); + *e = 32 - k; +#ifdef Pack_32 + if (k < Ebits) { + d0 = Exp_1 | y >> (Ebits - k); + w = xa > xa0 ? *--xa : 0; + d1 = y << ((32-Ebits) + k) | w >> (Ebits - k); + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + if (k -= Ebits) { + d0 = Exp_1 | y << k | z >> (32 - k); + y = xa > xa0 ? *--xa : 0; + d1 = z << k | y >> (32 - k); + } + else { + d0 = Exp_1 | y; + d1 = z; + } +#else + if (k < Ebits + 16) { + z = xa > xa0 ? *--xa : 0; + d0 = Exp_1 | y << k - Ebits | z >> Ebits + 16 - k; + w = xa > xa0 ? *--xa : 0; + y = xa > xa0 ? *--xa : 0; + d1 = z << k + 16 - Ebits | w << k - Ebits | y >> 16 + Ebits - k; + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + w = xa > xa0 ? *--xa : 0; + k -= Ebits + 16; + d0 = Exp_1 | y << k + 16 | z << k | w >> 16 - k; + y = xa > xa0 ? *--xa : 0; + d1 = w << k + 16 | y << k; +#endif +ret_d: +#ifdef VAX + word0(d) = d0 >> 16 | d0 << 16; + word1(d) = d1 >> 16 | d1 << 16; +#else +#undef d0 +#undef d1 +#endif + return dval(d); +} + +static Bigint * +d2b(double d_, int *e, int *bits) +{ + double_u d; + Bigint *b; + int de, k; + ULong *x, y, z; +#ifndef Sudden_Underflow + int i; +#endif +#ifdef VAX + ULong d0, d1; +#endif + dval(d) = d_; +#ifdef VAX + d0 = word0(d) >> 16 | word0(d) << 16; + d1 = word1(d) >> 16 | word1(d) << 16; +#else +#define d0 word0(d) +#define d1 word1(d) +#endif + +#ifdef Pack_32 + b = Balloc(1); +#else + b = Balloc(2); +#endif + x = b->x; + + z = d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ +#ifdef Sudden_Underflow + de = (int)(d0 >> Exp_shift); +#ifndef IBM + z |= Exp_msk11; +#endif +#else + if ((de = (int)(d0 >> Exp_shift)) != 0) + z |= Exp_msk1; +#endif +#ifdef Pack_32 + if ((y = d1) != 0) { + if ((k = lo0bits(&y)) != 0) { + x[0] = y | z << (32 - k); + z >>= k; + } + else + x[0] = y; +#ifndef Sudden_Underflow + i = +#endif + b->wds = (x[1] = z) ? 2 : 1; + } + else { +#ifdef DEBUG + if (!z) + Bug("Zero passed to d2b"); +#endif + k = lo0bits(&z); + x[0] = z; +#ifndef Sudden_Underflow + i = +#endif + b->wds = 1; + k += 32; + } +#else + if (y = d1) { + if (k = lo0bits(&y)) + if (k >= 16) { + x[0] = y | z << 32 - k & 0xffff; + x[1] = z >> k - 16 & 0xffff; + x[2] = z >> k; + i = 2; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16 | z << 16 - k & 0xffff; + x[2] = z >> k & 0xffff; + x[3] = z >> k+16; + i = 3; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16; + x[2] = z & 0xffff; + x[3] = z >> 16; + i = 3; + } + } + else { +#ifdef DEBUG + if (!z) + Bug("Zero passed to d2b"); +#endif + k = lo0bits(&z); + if (k >= 16) { + x[0] = z; + i = 0; + } + else { + x[0] = z & 0xffff; + x[1] = z >> 16; + i = 1; + } + k += 32; + } + while (!x[i]) + --i; + b->wds = i + 1; +#endif +#ifndef Sudden_Underflow + if (de) { +#endif +#ifdef IBM + *e = (de - Bias - (P-1) << 2) + k; + *bits = 4*P + 8 - k - hi0bits(word0(d) & Frac_mask); +#else + *e = de - Bias - (P-1) + k; + *bits = P - k; +#endif +#ifndef Sudden_Underflow + } + else { + *e = de - Bias - (P-1) + 1 + k; +#ifdef Pack_32 + *bits = 32*i - hi0bits(x[i-1]); +#else + *bits = (i+2)*16 - hi0bits(x[i]); +#endif + } +#endif + return b; +} +#undef d0 +#undef d1 + +static double +ratio(Bigint *a, Bigint *b) +{ + double_u da, db; + int k, ka, kb; + + dval(da) = b2d(a, &ka); + dval(db) = b2d(b, &kb); +#ifdef Pack_32 + k = ka - kb + 32*(a->wds - b->wds); +#else + k = ka - kb + 16*(a->wds - b->wds); +#endif +#ifdef IBM + if (k > 0) { + word0(da) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(da) *= 1 << k; + } + else { + k = -k; + word0(db) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(db) *= 1 << k; + } +#else + if (k > 0) + word0(da) += k*Exp_msk1; + else { + k = -k; + word0(db) += k*Exp_msk1; + } +#endif + return dval(da) / dval(db); +} + +static const double +tens[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 +#ifdef VAX + , 1e23, 1e24 +#endif +}; + +static const double +#ifdef IEEE_Arith +bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; +static const double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, +#ifdef Avoid_Underflow + 9007199254740992.*9007199254740992.e-256 + /* = 2^106 * 1e-53 */ +#else + 1e-256 +#endif +}; +/* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ +/* flag unnecessarily. It leads to a song and dance at the end of strtod. */ +#define Scale_Bit 0x10 +#define n_bigtens 5 +#else +#ifdef IBM +bigtens[] = { 1e16, 1e32, 1e64 }; +static const double tinytens[] = { 1e-16, 1e-32, 1e-64 }; +#define n_bigtens 3 +#else +bigtens[] = { 1e16, 1e32 }; +static const double tinytens[] = { 1e-16, 1e-32 }; +#define n_bigtens 2 +#endif +#endif + +#ifndef IEEE_Arith +#undef INFNAN_CHECK +#endif + +#ifdef INFNAN_CHECK + +#ifndef NAN_WORD0 +#define NAN_WORD0 0x7ff80000 +#endif + +#ifndef NAN_WORD1 +#define NAN_WORD1 0 +#endif + +static int +match(const char **sp, char *t) +{ + int c, d; + const char *s = *sp; + + while (d = *t++) { + if ((c = *++s) >= 'A' && c <= 'Z') + c += 'a' - 'A'; + if (c != d) + return 0; + } + *sp = s + 1; + return 1; +} + +#ifndef No_Hex_NaN +static void +hexnan(double *rvp, const char **sp) +{ + ULong c, x[2]; + const char *s; + int havedig, udx0, xshift; + + x[0] = x[1] = 0; + havedig = xshift = 0; + udx0 = 1; + s = *sp; + while (c = *(const unsigned char*)++s) { + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'a' && c <= 'f') + c += 10 - 'a'; + else if (c >= 'A' && c <= 'F') + c += 10 - 'A'; + else if (c <= ' ') { + if (udx0 && havedig) { + udx0 = 0; + xshift = 1; + } + continue; + } + else if (/*(*/ c == ')' && havedig) { + *sp = s + 1; + break; + } + else + return; /* invalid form: don't change *sp */ + havedig = 1; + if (xshift) { + xshift = 0; + x[0] = x[1]; + x[1] = 0; + } + if (udx0) + x[0] = (x[0] << 4) | (x[1] >> 28); + x[1] = (x[1] << 4) | c; + } + if ((x[0] &= 0xfffff) || x[1]) { + word0(*rvp) = Exp_mask | x[0]; + word1(*rvp) = x[1]; + } +} +#endif /*No_Hex_NaN*/ +#endif /* INFNAN_CHECK */ + +NO_SANITIZE("unsigned-integer-overflow", double strtod(const char *s00, char **se)); +double +strtod(const char *s00, char **se) +{ +#ifdef Avoid_Underflow + int scale; +#endif + int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, dsign, + e, e1, esign, i, j, k, nd, nd0, nf, nz, nz0, sign; + const char *s, *s0, *s1; + double aadj, adj; + double_u aadj1, rv, rv0; + Long L; + ULong y, z; + Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; +#ifdef SET_INEXACT + int inexact, oldinexact; +#endif +#ifdef Honor_FLT_ROUNDS + int rounding; +#endif +#ifdef USE_LOCALE + const char *s2; +#endif + + errno = 0; + sign = nz0 = nz = 0; + dval(rv) = 0.; + for (s = s00;;s++) + switch (*s) { + case '-': + sign = 1; + /* no break */ + case '+': + if (*++s) + goto break2; + /* no break */ + case 0: + goto ret0; + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case ' ': + continue; + default: + goto break2; + } +break2: + if (*s == '0') { + if (s[1] == 'x' || s[1] == 'X') { + s0 = ++s; + adj = 0; + aadj = 1.0; + nd0 = -4; + + if (!*++s || !(s1 = strchr(hexdigit, *s))) goto ret0; + if (*s == '0') { + while (*++s == '0'); + s1 = strchr(hexdigit, *s); + } + if (s1 != NULL) { + do { + adj += aadj * ((s1 - hexdigit) & 15); + nd0 += 4; + aadj /= 16; + } while (*++s && (s1 = strchr(hexdigit, *s))); + } + + if (*s == '.') { + dsign = 1; + if (!*++s || !(s1 = strchr(hexdigit, *s))) goto ret0; + if (nd0 < 0) { + while (*s == '0') { + s++; + nd0 -= 4; + } + } + for (; *s && (s1 = strchr(hexdigit, *s)); ++s) { + adj += aadj * ((s1 - hexdigit) & 15); + if ((aadj /= 16) == 0.0) { + while (strchr(hexdigit, *++s)); + break; + } + } + } + else { + dsign = 0; + } + + if (*s == 'P' || *s == 'p') { + dsign = 0x2C - *++s; /* +: 2B, -: 2D */ + if (abs(dsign) == 1) s++; + else dsign = 1; + + nd = 0; + c = *s; + if (c < '0' || '9' < c) goto ret0; + do { + nd *= 10; + nd += c; + nd -= '0'; + c = *++s; + /* Float("0x0."+("0"*267)+"1fp2095") */ + if (nd + dsign * nd0 > 2095) { + while ('0' <= c && c <= '9') c = *++s; + break; + } + } while ('0' <= c && c <= '9'); + nd0 += nd * dsign; + } + else { + if (dsign) goto ret0; + } + dval(rv) = ldexp(adj, nd0); + goto ret; + } + nz0 = 1; + while (*++s == '0') ; + if (!*s) + goto ret; + } + s0 = s; + y = z = 0; + for (nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) + if (nd < 9) + y = 10*y + c - '0'; + else if (nd < DBL_DIG + 2) + z = 10*z + c - '0'; + nd0 = nd; +#ifdef USE_LOCALE + s1 = localeconv()->decimal_point; + if (c == *s1) { + c = '.'; + if (*++s1) { + s2 = s; + for (;;) { + if (*++s2 != *s1) { + c = 0; + break; + } + if (!*++s1) { + s = s2; + break; + } + } + } + } +#endif + if (c == '.') { + if (!ISDIGIT(s[1])) + goto dig_done; + c = *++s; + if (!nd) { + for (; c == '0'; c = *++s) + nz++; + if (c > '0' && c <= '9') { + s0 = s; + nf += nz; + nz = 0; + goto have_dig; + } + goto dig_done; + } + for (; c >= '0' && c <= '9'; c = *++s) { +have_dig: + nz++; + if (nd > DBL_DIG * 4) { + continue; + } + if (c -= '0') { + nf += nz; + for (i = 1; i < nz; i++) + if (nd++ < 9) + y *= 10; + else if (nd <= DBL_DIG + 2) + z *= 10; + if (nd++ < 9) + y = 10*y + c; + else if (nd <= DBL_DIG + 2) + z = 10*z + c; + nz = 0; + } + } + } +dig_done: + e = 0; + if (c == 'e' || c == 'E') { + if (!nd && !nz && !nz0) { + goto ret0; + } + s00 = s; + esign = 0; + switch (c = *++s) { + case '-': + esign = 1; + case '+': + c = *++s; + } + if (c >= '0' && c <= '9') { + while (c == '0') + c = *++s; + if (c > '0' && c <= '9') { + L = c - '0'; + s1 = s; + while ((c = *++s) >= '0' && c <= '9') + L = 10*L + c - '0'; + if (s - s1 > 8 || L > 19999) + /* Avoid confusion from exponents + * so large that e might overflow. + */ + e = 19999; /* safe for 16 bit ints */ + else + e = (int)L; + if (esign) + e = -e; + } + else + e = 0; + } + else + s = s00; + } + if (!nd) { + if (!nz && !nz0) { +#ifdef INFNAN_CHECK + /* Check for Nan and Infinity */ + switch (c) { + case 'i': + case 'I': + if (match(&s,"nf")) { + --s; + if (!match(&s,"inity")) + ++s; + word0(rv) = 0x7ff00000; + word1(rv) = 0; + goto ret; + } + break; + case 'n': + case 'N': + if (match(&s, "an")) { + word0(rv) = NAN_WORD0; + word1(rv) = NAN_WORD1; +#ifndef No_Hex_NaN + if (*s == '(') /*)*/ + hexnan(&rv, &s); +#endif + goto ret; + } + } +#endif /* INFNAN_CHECK */ +ret0: + s = s00; + sign = 0; + } + goto ret; + } + e1 = e -= nf; + + /* Now we have nd0 digits, starting at s0, followed by a + * decimal point, followed by nd-nd0 digits. The number we're + * after is the integer represented by those digits times + * 10**e */ + + if (!nd0) + nd0 = nd; + k = nd < DBL_DIG + 2 ? nd : DBL_DIG + 2; + dval(rv) = y; + if (k > 9) { +#ifdef SET_INEXACT + if (k > DBL_DIG) + oldinexact = get_inexact(); +#endif + dval(rv) = tens[k - 9] * dval(rv) + z; + } + bd0 = bb = bd = bs = delta = 0; + if (nd <= DBL_DIG +#ifndef RND_PRODQUOT +#ifndef Honor_FLT_ROUNDS + && Flt_Rounds == 1 +#endif +#endif + ) { + if (!e) + goto ret; + if (e > 0) { + if (e <= Ten_pmax) { +#ifdef VAX + goto vax_ovfl_check; +#else +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + dval(rv) = -dval(rv); + sign = 0; + } +#endif + /* rv = */ rounded_product(dval(rv), tens[e]); + goto ret; +#endif + } + i = DBL_DIG - nd; + if (e <= Ten_pmax + i) { + /* A fancier test would sometimes let us do + * this for larger i values. + */ +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + dval(rv) = -dval(rv); + sign = 0; + } +#endif + e -= i; + dval(rv) *= tens[i]; +#ifdef VAX + /* VAX exponent range is so narrow we must + * worry about overflow here... + */ +vax_ovfl_check: + word0(rv) -= P*Exp_msk1; + /* rv = */ rounded_product(dval(rv), tens[e]); + if ((word0(rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) + goto ovfl; + word0(rv) += P*Exp_msk1; +#else + /* rv = */ rounded_product(dval(rv), tens[e]); +#endif + goto ret; + } + } +#ifndef Inaccurate_Divide + else if (e >= -Ten_pmax) { +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + dval(rv) = -dval(rv); + sign = 0; + } +#endif + /* rv = */ rounded_quotient(dval(rv), tens[-e]); + goto ret; + } +#endif + } + e1 += nd - k; + +#ifdef IEEE_Arith +#ifdef SET_INEXACT + inexact = 1; + if (k <= DBL_DIG) + oldinexact = get_inexact(); +#endif +#ifdef Avoid_Underflow + scale = 0; +#endif +#ifdef Honor_FLT_ROUNDS + if ((rounding = Flt_Rounds) >= 2) { + if (sign) + rounding = rounding == 2 ? 0 : 2; + else + if (rounding != 2) + rounding = 0; + } +#endif +#endif /*IEEE_Arith*/ + + /* Get starting approximation = rv * 10**e1 */ + + if (e1 > 0) { + if ((i = e1 & 15) != 0) + dval(rv) *= tens[i]; + if (e1 &= ~15) { + if (e1 > DBL_MAX_10_EXP) { +ovfl: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + /* Can't trust HUGE_VAL */ +#ifdef IEEE_Arith +#ifdef Honor_FLT_ROUNDS + switch (rounding) { + case 0: /* toward 0 */ + case 3: /* toward -infinity */ + word0(rv) = Big0; + word1(rv) = Big1; + break; + default: + word0(rv) = Exp_mask; + word1(rv) = 0; + } +#else /*Honor_FLT_ROUNDS*/ + word0(rv) = Exp_mask; + word1(rv) = 0; +#endif /*Honor_FLT_ROUNDS*/ +#ifdef SET_INEXACT + /* set overflow bit */ + dval(rv0) = 1e300; + dval(rv0) *= dval(rv0); +#endif +#else /*IEEE_Arith*/ + word0(rv) = Big0; + word1(rv) = Big1; +#endif /*IEEE_Arith*/ + if (bd0) + goto retfree; + goto ret; + } + e1 >>= 4; + for (j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(rv) *= bigtens[j]; + /* The last multiplication could overflow. */ + word0(rv) -= P*Exp_msk1; + dval(rv) *= bigtens[j]; + if ((z = word0(rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-P)) + goto ovfl; + if (z > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) { + /* set to largest number */ + /* (Can't trust DBL_MAX) */ + word0(rv) = Big0; + word1(rv) = Big1; + } + else + word0(rv) += P*Exp_msk1; + } + } + else if (e1 < 0) { + e1 = -e1; + if ((i = e1 & 15) != 0) + dval(rv) /= tens[i]; + if (e1 >>= 4) { + if (e1 >= 1 << n_bigtens) + goto undfl; +#ifdef Avoid_Underflow + if (e1 & Scale_Bit) + scale = 2*P; + for (j = 0; e1 > 0; j++, e1 >>= 1) + if (e1 & 1) + dval(rv) *= tinytens[j]; + if (scale && (j = 2*P + 1 - ((word0(rv) & Exp_mask) + >> Exp_shift)) > 0) { + /* scaled rv is denormal; zap j low bits */ + if (j >= 32) { + word1(rv) = 0; + if (j >= 53) + word0(rv) = (P+2)*Exp_msk1; + else + word0(rv) &= 0xffffffff << (j-32); + } + else + word1(rv) &= 0xffffffff << j; + } +#else + for (j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(rv) *= tinytens[j]; + /* The last multiplication could underflow. */ + dval(rv0) = dval(rv); + dval(rv) *= tinytens[j]; + if (!dval(rv)) { + dval(rv) = 2.*dval(rv0); + dval(rv) *= tinytens[j]; +#endif + if (!dval(rv)) { +undfl: + dval(rv) = 0.; +#ifndef NO_ERRNO + errno = ERANGE; +#endif + if (bd0) + goto retfree; + goto ret; + } +#ifndef Avoid_Underflow + word0(rv) = Tiny0; + word1(rv) = Tiny1; + /* The refinement below will clean + * this approximation up. + */ + } +#endif + } + } + + /* Now the hard part -- adjusting rv to the correct value.*/ + + /* Put digits into bd: true value = bd * 10^e */ + + bd0 = s2b(s0, nd0, nd, y); + + for (;;) { + bd = Balloc(bd0->k); + Bcopy(bd, bd0); + bb = d2b(dval(rv), &bbe, &bbbits); /* rv = bb * 2^bbe */ + bs = i2b(1); + + if (e >= 0) { + bb2 = bb5 = 0; + bd2 = bd5 = e; + } + else { + bb2 = bb5 = -e; + bd2 = bd5 = 0; + } + if (bbe >= 0) + bb2 += bbe; + else + bd2 -= bbe; + bs2 = bb2; +#ifdef Honor_FLT_ROUNDS + if (rounding != 1) + bs2++; +#endif +#ifdef Avoid_Underflow + j = bbe - scale; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#else /*Avoid_Underflow*/ +#ifdef Sudden_Underflow +#ifdef IBM + j = 1 + 4*P - 3 - bbbits + ((bbe + bbbits - 1) & 3); +#else + j = P + 1 - bbbits; +#endif +#else /*Sudden_Underflow*/ + j = bbe; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + bb2 += j; + bd2 += j; +#ifdef Avoid_Underflow + bd2 += scale; +#endif + i = bb2 < bd2 ? bb2 : bd2; + if (i > bs2) + i = bs2; + if (i > 0) { + bb2 -= i; + bd2 -= i; + bs2 -= i; + } + if (bb5 > 0) { + bs = pow5mult(bs, bb5); + bb1 = mult(bs, bb); + Bfree(bb); + bb = bb1; + } + if (bb2 > 0) + bb = lshift(bb, bb2); + if (bd5 > 0) + bd = pow5mult(bd, bd5); + if (bd2 > 0) + bd = lshift(bd, bd2); + if (bs2 > 0) + bs = lshift(bs, bs2); + delta = diff(bb, bd); + dsign = delta->sign; + delta->sign = 0; + i = cmp(delta, bs); +#ifdef Honor_FLT_ROUNDS + if (rounding != 1) { + if (i < 0) { + /* Error is less than an ulp */ + if (!delta->x[0] && delta->wds <= 1) { + /* exact */ +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + if (rounding) { + if (dsign) { + adj = 1.; + goto apply_adj; + } + } + else if (!dsign) { + adj = -1.; + if (!word1(rv) + && !(word0(rv) & Frac_mask)) { + y = word0(rv) & Exp_mask; +#ifdef Avoid_Underflow + if (!scale || y > 2*P*Exp_msk1) +#else + if (y) +#endif + { + delta = lshift(delta,Log2P); + if (cmp(delta, bs) <= 0) + adj = -0.5; + } + } +apply_adj: +#ifdef Avoid_Underflow + if (scale && (y = word0(rv) & Exp_mask) + <= 2*P*Exp_msk1) + word0(adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= + P*Exp_msk1) { + word0(rv) += P*Exp_msk1; + dval(rv) += adj*ulp(dval(rv)); + word0(rv) -= P*Exp_msk1; + } + else +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + dval(rv) += adj*ulp(dval(rv)); + } + break; + } + adj = ratio(delta, bs); + if (adj < 1.) + adj = 1.; + if (adj <= 0x7ffffffe) { + /* adj = rounding ? ceil(adj) : floor(adj); */ + y = adj; + if (y != adj) { + if (!((rounding>>1) ^ dsign)) + y++; + adj = y; + } + } +#ifdef Avoid_Underflow + if (scale && (y = word0(rv) & Exp_mask) <= 2*P*Exp_msk1) + word0(adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { + word0(rv) += P*Exp_msk1; + adj *= ulp(dval(rv)); + if (dsign) + dval(rv) += adj; + else + dval(rv) -= adj; + word0(rv) -= P*Exp_msk1; + goto cont; + } +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + adj *= ulp(dval(rv)); + if (dsign) + dval(rv) += adj; + else + dval(rv) -= adj; + goto cont; + } +#endif /*Honor_FLT_ROUNDS*/ + + if (i < 0) { + /* Error is less than half an ulp -- check for + * special case of mantissa a power of two. + */ + if (dsign || word1(rv) || word0(rv) & Bndry_mask +#ifdef IEEE_Arith +#ifdef Avoid_Underflow + || (word0(rv) & Exp_mask) <= (2*P+1)*Exp_msk1 +#else + || (word0(rv) & Exp_mask) <= Exp_msk1 +#endif +#endif + ) { +#ifdef SET_INEXACT + if (!delta->x[0] && delta->wds <= 1) + inexact = 0; +#endif + break; + } + if (!delta->x[0] && delta->wds <= 1) { + /* exact result */ +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + delta = lshift(delta,Log2P); + if (cmp(delta, bs) > 0) + goto drop_down; + break; + } + if (i == 0) { + /* exactly half-way between */ + if (dsign) { + if ((word0(rv) & Bndry_mask1) == Bndry_mask1 + && word1(rv) == ( +#ifdef Avoid_Underflow + (scale && (y = word0(rv) & Exp_mask) <= 2*P*Exp_msk1) + ? (0xffffffff & (0xffffffff << (2*P+1-(y>>Exp_shift)))) : +#endif + 0xffffffff)) { + /*boundary case -- increment exponent*/ + word0(rv) = (word0(rv) & Exp_mask) + + Exp_msk1 +#ifdef IBM + | Exp_msk1 >> 4 +#endif + ; + word1(rv) = 0; +#ifdef Avoid_Underflow + dsign = 0; +#endif + break; + } + } + else if (!(word0(rv) & Bndry_mask) && !word1(rv)) { +drop_down: + /* boundary case -- decrement exponent */ +#ifdef Sudden_Underflow /*{{*/ + L = word0(rv) & Exp_mask; +#ifdef IBM + if (L < Exp_msk1) +#else +#ifdef Avoid_Underflow + if (L <= (scale ? (2*P+1)*Exp_msk1 : Exp_msk1)) +#else + if (L <= Exp_msk1) +#endif /*Avoid_Underflow*/ +#endif /*IBM*/ + goto undfl; + L -= Exp_msk1; +#else /*Sudden_Underflow}{*/ +#ifdef Avoid_Underflow + if (scale) { + L = word0(rv) & Exp_mask; + if (L <= (2*P+1)*Exp_msk1) { + if (L > (P+2)*Exp_msk1) + /* round even ==> */ + /* accept rv */ + break; + /* rv = smallest denormal */ + goto undfl; + } + } +#endif /*Avoid_Underflow*/ + L = (word0(rv) & Exp_mask) - Exp_msk1; +#endif /*Sudden_Underflow}}*/ + word0(rv) = L | Bndry_mask1; + word1(rv) = 0xffffffff; +#ifdef IBM + goto cont; +#else + break; +#endif + } +#ifndef ROUND_BIASED + if (!(word1(rv) & LSB)) + break; +#endif + if (dsign) + dval(rv) += ulp(dval(rv)); +#ifndef ROUND_BIASED + else { + dval(rv) -= ulp(dval(rv)); +#ifndef Sudden_Underflow + if (!dval(rv)) + goto undfl; +#endif + } +#ifdef Avoid_Underflow + dsign = 1 - dsign; +#endif +#endif + break; + } + if ((aadj = ratio(delta, bs)) <= 2.) { + if (dsign) + aadj = dval(aadj1) = 1.; + else if (word1(rv) || word0(rv) & Bndry_mask) { +#ifndef Sudden_Underflow + if (word1(rv) == Tiny1 && !word0(rv)) + goto undfl; +#endif + aadj = 1.; + dval(aadj1) = -1.; + } + else { + /* special case -- power of FLT_RADIX to be */ + /* rounded down... */ + + if (aadj < 2./FLT_RADIX) + aadj = 1./FLT_RADIX; + else + aadj *= 0.5; + dval(aadj1) = -aadj; + } + } + else { + aadj *= 0.5; + dval(aadj1) = dsign ? aadj : -aadj; +#ifdef Check_FLT_ROUNDS + switch (Rounding) { + case 2: /* towards +infinity */ + dval(aadj1) -= 0.5; + break; + case 0: /* towards 0 */ + case 3: /* towards -infinity */ + dval(aadj1) += 0.5; + } +#else + if (Flt_Rounds == 0) + dval(aadj1) += 0.5; +#endif /*Check_FLT_ROUNDS*/ + } + y = word0(rv) & Exp_mask; + + /* Check for overflow */ + + if (y == Exp_msk1*(DBL_MAX_EXP+Bias-1)) { + dval(rv0) = dval(rv); + word0(rv) -= P*Exp_msk1; + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; + if ((word0(rv) & Exp_mask) >= + Exp_msk1*(DBL_MAX_EXP+Bias-P)) { + if (word0(rv0) == Big0 && word1(rv0) == Big1) + goto ovfl; + word0(rv) = Big0; + word1(rv) = Big1; + goto cont; + } + else + word0(rv) += P*Exp_msk1; + } + else { +#ifdef Avoid_Underflow + if (scale && y <= 2*P*Exp_msk1) { + if (aadj <= 0x7fffffff) { + if ((z = (int)aadj) <= 0) + z = 1; + aadj = z; + dval(aadj1) = dsign ? aadj : -aadj; + } + word0(aadj1) += (2*P+1)*Exp_msk1 - y; + } + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; +#else +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { + dval(rv0) = dval(rv); + word0(rv) += P*Exp_msk1; + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; +#ifdef IBM + if ((word0(rv) & Exp_mask) < P*Exp_msk1) +#else + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) +#endif + { + if (word0(rv0) == Tiny0 && word1(rv0) == Tiny1) + goto undfl; + word0(rv) = Tiny0; + word1(rv) = Tiny1; + goto cont; + } + else + word0(rv) -= P*Exp_msk1; + } + else { + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; + } +#else /*Sudden_Underflow*/ + /* Compute adj so that the IEEE rounding rules will + * correctly round rv + adj in some half-way cases. + * If rv * ulp(rv) is denormalized (i.e., + * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid + * trouble from bits lost to denormalization; + * example: 1.2e-307 . + */ + if (y <= (P-1)*Exp_msk1 && aadj > 1.) { + dval(aadj1) = (double)(int)(aadj + 0.5); + if (!dsign) + dval(aadj1) = -dval(aadj1); + } + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + } + z = word0(rv) & Exp_mask; +#ifndef SET_INEXACT +#ifdef Avoid_Underflow + if (!scale) +#endif + if (y == z) { + /* Can we stop now? */ + L = (Long)aadj; + aadj -= L; + /* The tolerances below are conservative. */ + if (dsign || word1(rv) || word0(rv) & Bndry_mask) { + if (aadj < .4999999 || aadj > .5000001) + break; + } + else if (aadj < .4999999/FLT_RADIX) + break; + } +#endif +cont: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(delta); + } +#ifdef SET_INEXACT + if (inexact) { + if (!oldinexact) { + word0(rv0) = Exp_1 + (70 << Exp_shift); + word1(rv0) = 0; + dval(rv0) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif +#ifdef Avoid_Underflow + if (scale) { + word0(rv0) = Exp_1 - 2*P*Exp_msk1; + word1(rv0) = 0; + dval(rv) *= dval(rv0); +#ifndef NO_ERRNO + /* try to avoid the bug of testing an 8087 register value */ + if (word0(rv) == 0 && word1(rv) == 0) + errno = ERANGE; +#endif + } +#endif /* Avoid_Underflow */ +#ifdef SET_INEXACT + if (inexact && !(word0(rv) & Exp_mask)) { + /* set underflow bit */ + dval(rv0) = 1e-300; + dval(rv0) *= dval(rv0); + } +#endif +retfree: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(bd0); + Bfree(delta); +ret: + if (se) + *se = (char *)s; + return sign ? -dval(rv) : dval(rv); +} + +NO_SANITIZE("unsigned-integer-overflow", static int quorem(Bigint *b, Bigint *S)); +static int +quorem(Bigint *b, Bigint *S) +{ + int n; + ULong *bx, *bxe, q, *sx, *sxe; +#ifdef ULLong + ULLong borrow, carry, y, ys; +#else + ULong borrow, carry, y, ys; +#ifdef Pack_32 + ULong si, z, zs; +#endif +#endif + + n = S->wds; +#ifdef DEBUG + /*debug*/ if (b->wds > n) + /*debug*/ Bug("oversize b in quorem"); +#endif + if (b->wds < n) + return 0; + sx = S->x; + sxe = sx + --n; + bx = b->x; + bxe = bx + n; + q = *bxe / (*sxe + 1); /* ensure q <= true quotient */ +#ifdef DEBUG + /*debug*/ if (q > 9) + /*debug*/ Bug("oversized quotient in quorem"); +#endif + if (q) { + borrow = 0; + carry = 0; + do { +#ifdef ULLong + ys = *sx++ * (ULLong)q + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = (ULong)(y & FFFFFFFF); +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) * q + carry; + zs = (si >> 16) * q + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ * q + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } while (sx <= sxe); + if (!*bxe) { + bx = b->x; + while (--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + if (cmp(b, S) >= 0) { + q++; + borrow = 0; + carry = 0; + bx = b->x; + sx = S->x; + do { +#ifdef ULLong + ys = *sx++ + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = (ULong)(y & FFFFFFFF); +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) + carry; + zs = (si >> 16) + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } while (sx <= sxe); + bx = b->x; + bxe = bx + n; + if (!*bxe) { + while (--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + return q; +} + +#ifndef MULTIPLE_THREADS +static char *dtoa_result; +#endif + +#ifndef MULTIPLE_THREADS +static char * +rv_alloc(int i) +{ + return dtoa_result = MALLOC(i); +} +#else +#define rv_alloc(i) MALLOC(i) +#endif + +static char * +nrv_alloc(const char *s, char **rve, size_t n) +{ + char *rv, *t; + + t = rv = rv_alloc(n); + while ((*t = *s++) != 0) t++; + if (rve) + *rve = t; + return rv; +} + +#define rv_strdup(s, rve) nrv_alloc((s), (rve), strlen(s)+1) + +#ifndef MULTIPLE_THREADS +/* freedtoa(s) must be used to free values s returned by dtoa + * when MULTIPLE_THREADS is #defined. It should be used in all cases, + * but for consistency with earlier versions of dtoa, it is optional + * when MULTIPLE_THREADS is not defined. + */ + +static void +freedtoa(char *s) +{ + FREE(s); +} +#endif + +static const char INFSTR[] = "Infinity"; +static const char NANSTR[] = "NaN"; +static const char ZEROSTR[] = "0"; + +/* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + * + * Inspired by "How to Print Floating-Point Numbers Accurately" by + * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 112-126]. + * + * Modifications: + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. + */ + +char * +dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve) +{ + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4,5 ==> similar to 2 and 3, respectively, but (in + round-nearest mode) with the tests of mode 0 to + possibly return a shorter string that rounds to d. + With IEEE arithmetic and compilation with + -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same + as modes 2 and 3 when FLT_ROUNDS != 1. + 6-9 ==> Debugging modes similar to mode - 4: don't try + fast floating-point estimate (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int bbits, b2, b5, be, dig, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, + spec_case, try_quick, half = 0; + Long L; +#ifndef Sudden_Underflow + int denorm; + ULong x; +#endif + Bigint *b, *b1, *delta, *mlo = 0, *mhi = 0, *S; + double ds; + double_u d, d2, eps; + char *s, *s0; +#ifdef Honor_FLT_ROUNDS + int rounding; +#endif +#ifdef SET_INEXACT + int inexact, oldinexact; +#endif + + dval(d) = d_; + +#ifndef MULTIPLE_THREADS + if (dtoa_result) { + freedtoa(dtoa_result); + dtoa_result = 0; + } +#endif + + if (word0(d) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + word0(d) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign = 0; + +#if defined(IEEE_Arith) + defined(VAX) +#ifdef IEEE_Arith + if ((word0(d) & Exp_mask) == Exp_mask) +#else + if (word0(d) == 0x8000) +#endif + { + /* Infinity or NaN */ + *decpt = 9999; +#ifdef IEEE_Arith + if (!word1(d) && !(word0(d) & 0xfffff)) + return rv_strdup(INFSTR, rve); +#endif + return rv_strdup(NANSTR, rve); + } +#endif +#ifdef IBM + dval(d) += 0; /* normalize */ +#endif + if (!dval(d)) { + *decpt = 1; + return rv_strdup(ZEROSTR, rve); + } + +#ifdef SET_INEXACT + try_quick = oldinexact = get_inexact(); + inexact = 1; +#endif +#ifdef Honor_FLT_ROUNDS + if ((rounding = Flt_Rounds) >= 2) { + if (*sign) + rounding = rounding == 2 ? 0 : 2; + else + if (rounding != 2) + rounding = 0; + } +#endif + + b = d2b(dval(d), &be, &bbits); +#ifdef Sudden_Underflow + i = (int)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1)); +#else + if ((i = (int)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1))) != 0) { +#endif + dval(d2) = dval(d); + word0(d2) &= Frac_mask1; + word0(d2) |= Exp_11; +#ifdef IBM + if (j = 11 - hi0bits(word0(d2) & Frac_mask)) + dval(d2) /= 1 << j; +#endif + + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + + i -= Bias; +#ifdef IBM + i <<= 2; + i += j; +#endif +#ifndef Sudden_Underflow + denorm = 0; + } + else { + /* d is denormalized */ + + i = bbits + be + (Bias + (P-1) - 1); + x = i > 32 ? word0(d) << (64 - i) | word1(d) >> (i - 32) + : word1(d) << (32 - i); + dval(d2) = x; + word0(d2) -= 31*Exp_msk1; /* adjust exponent */ + i -= (Bias + (P-1) - 1) + 1; + denorm = 1; + } +#endif + ds = (dval(d2)-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int)ds; + if (ds < 0. && ds != k) + k--; /* want k = floor(ds) */ + k_check = 1; + if (k >= 0 && k <= Ten_pmax) { + if (dval(d) < tens[k]) + k--; + k_check = 0; + } + j = bbits - i - 1; + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + if (mode < 0 || mode > 9) + mode = 0; + +#ifndef SET_INEXACT +#ifdef Check_FLT_ROUNDS + try_quick = Rounding == 1; +#else + try_quick = 1; +#endif +#endif /*SET_INEXACT*/ + + if (mode > 5) { + mode -= 4; + try_quick = 0; + } + leftright = 1; + ilim = ilim1 = -1; + switch (mode) { + case 0: + case 1: + i = 18; + ndigits = 0; + break; + case 2: + leftright = 0; + /* no break */ + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = 0; + /* no break */ + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + s = s0 = rv_alloc(i+1); + +#ifdef Honor_FLT_ROUNDS + if (mode > 1 && rounding != 1) + leftright = 0; +#endif + + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + dval(d2) = dval(d); + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if (j & Bletch) { + /* prevent overflows */ + j &= Bletch - 1; + dval(d) /= bigtens[n_bigtens-1]; + ieps++; + } + for (; j; j >>= 1, i++) + if (j & 1) { + ieps++; + ds *= bigtens[i]; + } + dval(d) /= ds; + } + else if ((j1 = -k) != 0) { + dval(d) *= tens[j1 & 0xf]; + for (j = j1 >> 4; j; j >>= 1, i++) + if (j & 1) { + ieps++; + dval(d) *= bigtens[i]; + } + } + if (k_check && dval(d) < 1. && ilim > 0) { + if (ilim1 <= 0) + goto fast_failed; + ilim = ilim1; + k--; + dval(d) *= 10.; + ieps++; + } + dval(eps) = ieps*dval(d) + 7.; + word0(eps) -= (P-1)*Exp_msk1; + if (ilim == 0) { + S = mhi = 0; + dval(d) -= 5.; + if (dval(d) > dval(eps)) + goto one_digit; + if (dval(d) < -dval(eps)) + goto no_digits; + goto fast_failed; + } +#ifndef No_leftright + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + dval(eps) = 0.5/tens[ilim-1] - dval(eps); + for (i = 0;;) { + L = (int)dval(d); + dval(d) -= L; + *s++ = '0' + (int)L; + if (dval(d) < dval(eps)) + goto ret1; + if (1. - dval(d) < dval(eps)) + goto bump_up; + if (++i >= ilim) + break; + dval(eps) *= 10.; + dval(d) *= 10.; + } + } + else { +#endif + /* Generate ilim digits, then fix them up. */ + dval(eps) *= tens[ilim-1]; + for (i = 1;; i++, dval(d) *= 10.) { + L = (Long)(dval(d)); + if (!(dval(d) -= L)) + ilim = i; + *s++ = '0' + (int)L; + if (i == ilim) { + if (dval(d) > 0.5 + dval(eps)) + goto bump_up; + else if (dval(d) < 0.5 - dval(eps)) { + while (*--s == '0') ; + s++; + goto ret1; + } + half = 1; + if ((*(s-1) - '0') & 1) { + goto bump_up; + } + break; + } + } +#ifndef No_leftright + } +#endif +fast_failed: + s = s0; + dval(d) = dval(d2); + k = k0; + ilim = ilim0; + } + + /* Do we have a "small" integer? */ + + if (be >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = 0; + if (ilim < 0 || dval(d) <= 5*ds) + goto no_digits; + goto one_digit; + } + for (i = 1;; i++, dval(d) *= 10.) { + L = (Long)(dval(d) / ds); + dval(d) -= L*ds; +#ifdef Check_FLT_ROUNDS + /* If FLT_ROUNDS == 2, L will usually be high by 1 */ + if (dval(d) < 0) { + L--; + dval(d) += ds; + } +#endif + *s++ = '0' + (int)L; + if (!dval(d)) { +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + if (i == ilim) { +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch (rounding) { + case 0: goto ret1; + case 2: goto bump_up; + } +#endif + dval(d) += dval(d); + if (dval(d) > ds || (dval(d) == ds && (L & 1))) { +bump_up: + while (*--s == '9') + if (s == s0) { + k++; + *s = '0'; + break; + } + ++*s++; + } + break; + } + } + goto ret1; + } + + m2 = b2; + m5 = b5; + if (leftright) { + i = +#ifndef Sudden_Underflow + denorm ? be + (Bias + (P-1) - 1 + 1) : +#endif +#ifdef IBM + 1 + 4*P - 3 - bbits + ((bbits + be - 1) & 3); +#else + 1 + P - bbits; +#endif + b2 += i; + s2 += i; + mhi = i2b(1); + } + if (m2 > 0 && s2 > 0) { + i = m2 < s2 ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + b1 = mult(mhi, b); + Bfree(b); + b = b1; + } + if ((j = b5 - m5) != 0) + b = pow5mult(b, j); + } + else + b = pow5mult(b, b5); + } + S = i2b(1); + if (s5 > 0) + S = pow5mult(S, s5); + + /* Check for special case that d is a normalized power of 2. */ + + spec_case = 0; + if ((mode < 2 || leftright) +#ifdef Honor_FLT_ROUNDS + && rounding == 1 +#endif + ) { + if (!word1(d) && !(word0(d) & Bndry_mask) +#ifndef Sudden_Underflow + && word0(d) & (Exp_mask & ~Exp_msk1) +#endif + ) { + /* The special case */ + b2 += Log2P; + s2 += Log2P; + spec_case = 1; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ +#ifdef Pack_32 + if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0x1f) != 0) + i = 32 - i; +#else + if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0xf) != 0) + i = 16 - i; +#endif + if (i > 4) { + i -= 4; + b2 += i; + m2 += i; + s2 += i; + } + else if (i < 4) { + i += 28; + b2 += i; + m2 += i; + s2 += i; + } + if (b2 > 0) + b = lshift(b, b2); + if (s2 > 0) + S = lshift(S, s2); + if (k_check) { + if (cmp(b,S) < 0) { + k--; + b = multadd(b, 10, 0); /* we botched the k estimate */ + if (leftright) + mhi = multadd(mhi, 10, 0); + ilim = ilim1; + } + } + if (ilim <= 0 && (mode == 3 || mode == 5)) { + if (ilim < 0 || cmp(b,S = multadd(S,5,0)) <= 0) { + /* no digits, fcvt style */ +no_digits: + k = -1 - ndigits; + goto ret; + } +one_digit: + *s++ = '1'; + k++; + goto ret; + } + if (leftright) { + if (m2 > 0) + mhi = lshift(mhi, m2); + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = Balloc(mhi->k); + Bcopy(mhi, mlo); + mhi = lshift(mhi, Log2P); + } + + for (i = 1;;i++) { + dig = quorem(b,S) + '0'; + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = cmp(b, mlo); + delta = diff(S, mhi); + j1 = delta->sign ? 1 : cmp(b, delta); + Bfree(delta); +#ifndef ROUND_BIASED + if (j1 == 0 && mode != 1 && !(word1(d) & 1) +#ifdef Honor_FLT_ROUNDS + && rounding >= 1 +#endif + ) { + if (dig == '9') + goto round_9_up; + if (j > 0) + dig++; +#ifdef SET_INEXACT + else if (!b->x[0] && b->wds <= 1) + inexact = 0; +#endif + *s++ = dig; + goto ret; + } +#endif + if (j < 0 || (j == 0 && mode != 1 +#ifndef ROUND_BIASED + && !(word1(d) & 1) +#endif + )) { + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto accept_dig; + } +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch (rounding) { + case 0: goto accept_dig; + case 2: goto keep_dig; + } +#endif /*Honor_FLT_ROUNDS*/ + if (j1 > 0) { + b = lshift(b, 1); + j1 = cmp(b, S); + if ((j1 > 0 || (j1 == 0 && (dig & 1))) && dig++ == '9') + goto round_9_up; + } +accept_dig: + *s++ = dig; + goto ret; + } + if (j1 > 0) { +#ifdef Honor_FLT_ROUNDS + if (!rounding) + goto accept_dig; +#endif + if (dig == '9') { /* possible if i == 1 */ +round_9_up: + *s++ = '9'; + goto roundoff; + } + *s++ = dig + 1; + goto ret; + } +#ifdef Honor_FLT_ROUNDS +keep_dig: +#endif + *s++ = dig; + if (i == ilim) + break; + b = multadd(b, 10, 0); + if (mlo == mhi) + mlo = mhi = multadd(mhi, 10, 0); + else { + mlo = multadd(mlo, 10, 0); + mhi = multadd(mhi, 10, 0); + } + } + } + else + for (i = 1;; i++) { + *s++ = dig = quorem(b,S) + '0'; + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto ret; + } + if (i >= ilim) + break; + b = multadd(b, 10, 0); + } + + /* Round off last digit */ + +#ifdef Honor_FLT_ROUNDS + switch (rounding) { + case 0: goto trimzeros; + case 2: goto roundoff; + } +#endif + b = lshift(b, 1); + j = cmp(b, S); + if (j > 0 || (j == 0 && (dig & 1))) { + roundoff: + while (*--s == '9') + if (s == s0) { + k++; + *s++ = '1'; + goto ret; + } + if (!half || (*s - '0') & 1) + ++*s; + } + else { + while (*--s == '0') ; + } + s++; +ret: + Bfree(S); + if (mhi) { + if (mlo && mlo != mhi) + Bfree(mlo); + Bfree(mhi); + } +ret1: +#ifdef SET_INEXACT + if (inexact) { + if (!oldinexact) { + word0(d) = Exp_1 + (70 << Exp_shift); + word1(d) = 0; + dval(d) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif + Bfree(b); + *s = 0; + *decpt = k + 1; + if (rve) + *rve = s; + return s0; +} + +/*- + * Copyright (c) 2004-2008 David Schultz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define DBL_MANH_SIZE 20 +#define DBL_MANL_SIZE 32 +#define DBL_ADJ (DBL_MAX_EXP - 2) +#define SIGFIGS ((DBL_MANT_DIG + 3) / 4 + 1) +#define dexp_get(u) ((int)(word0(u) >> Exp_shift) & ~Exp_msk1) +#define dexp_set(u,v) (word0(u) = (((int)(word0(u)) & ~Exp_mask) | ((v) << Exp_shift))) +#define dmanh_get(u) ((uint32_t)(word0(u) & Frac_mask)) +#define dmanl_get(u) ((uint32_t)word1(u)) + + +/* + * This procedure converts a double-precision number in IEEE format + * into a string of hexadecimal digits and an exponent of 2. Its + * behavior is bug-for-bug compatible with dtoa() in mode 2, with the + * following exceptions: + * + * - An ndigits < 0 causes it to use as many digits as necessary to + * represent the number exactly. + * - The additional xdigs argument should point to either the string + * "0123456789ABCDEF" or the string "0123456789abcdef", depending on + * which case is desired. + * - This routine does not repeat dtoa's mistake of setting decpt + * to 9999 in the case of an infinity or NaN. INT_MAX is used + * for this purpose instead. + * + * Note that the C99 standard does not specify what the leading digit + * should be for non-zero numbers. For instance, 0x1.3p3 is the same + * as 0x2.6p2 is the same as 0x4.cp3. This implementation always makes + * the leading digit a 1. This ensures that the exponent printed is the + * actual base-2 exponent, i.e., ilogb(d). + * + * Inputs: d, xdigs, ndigits + * Outputs: decpt, sign, rve + */ +char * +hdtoa(double d, const char *xdigs, int ndigits, int *decpt, int *sign, char **rve) +{ + U u; + char *s, *s0; + int bufsize; + uint32_t manh, manl; + + u.d = d; + if (word0(u) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + word0(u) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign = 0; + + if (isinf(d)) { /* FP_INFINITE */ + *decpt = INT_MAX; + return rv_strdup(INFSTR, rve); + } + else if (isnan(d)) { /* FP_NAN */ + *decpt = INT_MAX; + return rv_strdup(NANSTR, rve); + } + else if (d == 0.0) { /* FP_ZERO */ + *decpt = 1; + return rv_strdup(ZEROSTR, rve); + } + else if (dexp_get(u)) { /* FP_NORMAL */ + *decpt = dexp_get(u) - DBL_ADJ; + } + else { /* FP_SUBNORMAL */ + u.d *= 5.363123171977039e+154 /* 0x1p514 */; + *decpt = dexp_get(u) - (514 + DBL_ADJ); + } + + if (ndigits == 0) /* dtoa() compatibility */ + ndigits = 1; + + /* + * If ndigits < 0, we are expected to auto-size, so we allocate + * enough space for all the digits. + */ + bufsize = (ndigits > 0) ? ndigits : SIGFIGS; + s0 = rv_alloc(bufsize+1); + + /* Round to the desired number of digits. */ + if (SIGFIGS > ndigits && ndigits > 0) { + float redux = 1.0f; + int offset = 4 * ndigits + DBL_MAX_EXP - 4 - DBL_MANT_DIG; + dexp_set(u, offset); + u.d += redux; + u.d -= redux; + *decpt += dexp_get(u) - offset; + } + + manh = dmanh_get(u); + manl = dmanl_get(u); + *s0 = '1'; + for (s = s0 + 1; s < s0 + bufsize; s++) { + *s = xdigs[(manh >> (DBL_MANH_SIZE - 4)) & 0xf]; + manh = (manh << 4) | (manl >> (DBL_MANL_SIZE - 4)); + manl <<= 4; + } + + /* If ndigits < 0, we are expected to auto-size the precision. */ + if (ndigits < 0) { + for (ndigits = SIGFIGS; s0[ndigits - 1] == '0'; ndigits--) + ; + } + + s = s0 + ndigits; + *s = '\0'; + if (rve != NULL) + *rve = s; + return (s0); +} + +#ifdef __cplusplus +#if 0 +{ /* satisfy cc-mode */ +#endif +} +#endif From d071a0abbbf6c98453c3e17789e9d51250f6a39c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 01:30:37 +0900 Subject: [PATCH 185/546] Add new files in gemspec --- bigdecimal.gemspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 86ea1801..ef003e0e 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -21,7 +21,9 @@ Gem::Specification.new do |s| ext/bigdecimal/bigdecimal.h ext/bigdecimal/bits.h ext/bigdecimal/feature.h + ext/bigdecimal/missing.c ext/bigdecimal/missing.h + ext/bigdecimal/missing/dtoa.c ext/bigdecimal/static_assert.h lib/bigdecimal.rb lib/bigdecimal/jacobian.rb From 2dbe170e35aa041795d42b66c09b1425226e5996 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 10:28:23 +0900 Subject: [PATCH 186/546] Allow digits=0 in BigDecimal(flt) and Float#to_d Using dtoa of mode=0, we can determine the number of digits in decimal that is necessary to represent the given Float number without errors. This change permits digits=0 in BigDecimal(flt) and Float#to_d, and these methods use dtoa of mode=0 when the given digits is 0. Internal implicit conversion from Float also uses digits=0. [Fix GH-70] --- ext/bigdecimal/bigdecimal.c | 53 +++++++++++++++---------- lib/bigdecimal/util.rb | 2 +- test/bigdecimal/test_bigdecimal.rb | 2 + test/bigdecimal/test_bigdecimal_util.rb | 8 ++-- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a2f78063..e352f381 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -947,7 +947,7 @@ BigDecimal_coerce(VALUE self, VALUE other) Real *b; if (RB_TYPE_P(other, T_FLOAT)) { - GUARD_OBJ(b, GetVpValueWithPrec(other, DBLE_FIG, 1)); + GUARD_OBJ(b, GetVpValueWithPrec(other, 0, 1)); obj = rb_assoc_new(VpCheckGetValue(b), self); } else { @@ -1005,7 +1005,7 @@ BigDecimal_add(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1063,7 +1063,7 @@ BigDecimal_sub(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self,1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1113,7 +1113,7 @@ BigDecimalCmp(VALUE self, VALUE r,char op) break; case T_FLOAT: - GUARD_OBJ(b, GetVpValueWithPrec(r, DBLE_FIG, 0)); + GUARD_OBJ(b, GetVpValueWithPrec(r, 0, 0)); break; case T_RATIONAL: @@ -1326,7 +1326,7 @@ BigDecimal_mult(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1354,7 +1354,7 @@ BigDecimal_divide(Real **c, Real **res, Real **div, VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1420,7 +1420,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1521,7 +1521,7 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -2416,7 +2416,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (NIL_P(prec)) { n += DBLE_FIG; } - exp = GetVpValueWithPrec(vexp, DBLE_FIG, 1); + exp = GetVpValueWithPrec(vexp, 0, 1); break; case T_RATIONAL: @@ -2832,7 +2832,8 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) char buf[DBLE_FIG + BASE_FIG + 2 + 1]; int decpt, negative_p; char *e; - char *p = BigDecimal_dtoa(d, 2, digs, &decpt, &negative_p, &e); + const int mode = digs == 0 ? 0 : 2; + char *p = BigDecimal_dtoa(d, mode, digs, &decpt, &negative_p, &e); int len10 = (int)(e - p); if (len10 >= (int)sizeof(buf)) len10 = (int)sizeof(buf) - 1; @@ -3009,6 +3010,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) VALUE copy = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); vp = VpCopy(NULL, vp); + /* TODO: rounding */ BigDecimal_wrap_struct(copy, vp); return VpCheckGetValue(vp); } @@ -3049,19 +3051,28 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } /* call-seq: - * BigDecimal(initial, digits=0, exception: true) + * BigDecimal(arg, exception: true) + * BigDecimal(arg, digits, exception: true) * - * Create a new BigDecimal object. + * Returns arg converted to a BigDecimal. Numeric types are converted + * directly. Other types except for String are first converted to String + * by to_str. Strings can be converted when it has appropriate + * forms of decimal numbers. Exceptions can be suppressed by passing + * exception: false. * - * initial:: The initial value, as an Integer, a Float, a Rational, - * a BigDecimal, or a String. + * When arg is a Float and digits is 0, the number + * of digits is determined by the algorithm of dtoa function + * written by David M. Gay. That algorithm is based on "How to Print Floating- + * Point Numbers Accurately" by Guy L. Steele, Jr. and Jon L. White [Proc. ACM + * SIGPLAN '90, pp. 112-126]. * - * If it is a String, spaces are ignored and unrecognized characters - * terminate the value. + * arg:: The value converted to a BigDecimal. * - * digits:: The number of significant digits, as an Integer. If omitted or 0, - * the number of significant digits is determined from the initial - * value. + * If it is a String, spaces are ignored and unrecognized characters + * terminate the value. + * + * digits:: The number of significant digits, as an Integer. If omitted, + * the number of significant digits is determined from arg. * * The actual number of significant digits used in computation is * usually larger than the specified number. @@ -3306,7 +3317,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) infinite = isinf(flo); nan = isnan(flo); if (!infinite && !nan) { - vx = GetVpValueWithPrec(x, DBLE_FIG, 0); + vx = GetVpValueWithPrec(x, 0, 0); } break; @@ -3459,7 +3470,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) infinite = isinf(flo); nan = isnan(flo); if (!zero && !negative && !infinite && !nan) { - vx = GetVpValueWithPrec(x, DBLE_FIG, 1); + vx = GetVpValueWithPrec(x, 0, 1); } break; diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index 00a3e967..cb645d2a 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -43,7 +43,7 @@ class Float < Numeric # # See also BigDecimal::new. # - def to_d(precision=Float::DIG+1) + def to_d(precision=0) BigDecimal(self, precision) end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 3d3a750a..b015f9a9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -915,6 +915,7 @@ def test_mult def test_mult_with_float assert_kind_of(BigDecimal, BigDecimal("3") * 1.5) + assert_equal(BigDecimal("64.4"), BigDecimal(1) * 64.4) end def test_mult_with_rational @@ -953,6 +954,7 @@ def test_div def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) + assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) end def test_div_with_rational diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 9c6973dc..ffd4c5f6 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -19,11 +19,11 @@ def test_Integer_to_d def test_Float_to_d_without_precision delta = 1.0/10**(Float::DIG+1) - assert_in_delta(BigDecimal(0.5, Float::DIG+1), 0.5.to_d, delta) - assert_in_delta(BigDecimal(355.0/113.0, Float::DIG+1), (355.0/113.0).to_d, delta) + assert_in_delta(BigDecimal(0.5, 0), 0.5.to_d, delta) + assert_in_delta(BigDecimal(355.0/113.0, 0), (355.0/113.0).to_d, delta) assert_equal(9.05, 9.05.to_d.to_f) - assert_equal("9.050000000000001", 9.05.to_d.to_s('F')) + assert_equal("9.05", 9.05.to_d.to_s('F')) assert_equal(Math::PI, Math::PI.to_d.to_f) @@ -34,6 +34,8 @@ def test_Float_to_d_without_precision assert_raise(TypeError) { 0.3.to_d(false) } assert(1.1.to_d.frozen?) + + assert_equal(BigDecimal("999_999.9999"), 999_999.9999.to_d) end def test_Float_to_d_with_precision From af6502c2606b9462fe21300243c604f4fd063356 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 11:42:46 +0900 Subject: [PATCH 187/546] Changes: Add 3.0.1 entry --- CHANGES.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 015e7d35..5132d6e3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,21 @@ # CHANGES +## 3.0.1 + +* Improve the conversion speed of BigDecimal() and to_d methods. + + **Kenta Murata** + +* Permit 0 digits in BigDecimal(float) and Float#to_d. + It means auto-detection of the smallest number of digits to represent + the given Float number without error. + + **Kenta Murata** + +* Fix precision issue of Float [GH-70] + + Reported by @casperisfine + ## 3.0.0 * Deprecate `BigDecimal#precs`. From f6765b80711c771f08b0fea3c6440d5cd89517ff Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 14 Jan 2021 09:23:03 +0900 Subject: [PATCH 188/546] Suppress warning at NO_SANITIZE on gcc --- ext/bigdecimal/missing.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ext/bigdecimal/missing.c b/ext/bigdecimal/missing.c index b0bc6eea..703232d9 100644 --- a/ext/bigdecimal/missing.c +++ b/ext/bigdecimal/missing.c @@ -8,6 +8,16 @@ # define ATOMIC_PTR_CAS(var, old, new) RUBY_ATOMIC_PTR_CAS(var, old, new) #endif +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +/* GCC warns about unknown sanitizer, which is annoying. */ +# undef NO_SANITIZE +# define NO_SANITIZE(x, y) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ + __attribute__((__no_sanitize__(x))) y; \ + _Pragma("GCC diagnostic pop") +#endif + #undef strtod #define strtod BigDecimal_strtod #undef dtoa From f0d94e68439afd45ffb8d5faeb8ef6b7dfbc4df4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 14 Jan 2021 09:23:26 +0900 Subject: [PATCH 189/546] Explicit cast uint64_t to double --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e352f381..b00fb3e7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2704,7 +2704,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->frac[0] = (DECDIG)uval; } else { - const size_t len = (size_t)ceil(log10(uval) / BASE_FIG); + const size_t len = (size_t)ceil(log10((double)uval) / BASE_FIG); vp = VpAllocReal(len); vp->MaxPrec = len; From b1f1ed26c93b774d706cc7523eb864cfd5645d5a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 14 Jan 2021 09:26:15 +0900 Subject: [PATCH 190/546] Explicitly cast size_t to int --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b00fb3e7..e4dc5658 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2833,7 +2833,7 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) int decpt, negative_p; char *e; const int mode = digs == 0 ? 0 : 2; - char *p = BigDecimal_dtoa(d, mode, digs, &decpt, &negative_p, &e); + char *p = BigDecimal_dtoa(d, mode, (int)digs, &decpt, &negative_p, &e); int len10 = (int)(e - p); if (len10 >= (int)sizeof(buf)) len10 = (int)sizeof(buf) - 1; From 11025811b71e93bd81daed5a320d33cf4e152fe7 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 14 Jan 2021 09:47:33 +0900 Subject: [PATCH 191/546] Change the workflow step order --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 985e0ec8..71833339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,8 @@ jobs: - run: rake compile - - run: rake build - - run: rake test + - run: rake build + - run: gem install pkg/*.gem From 1e03da707692990fdc7f620fbeded22d49023537 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:00:24 +0900 Subject: [PATCH 192/546] Reorder the arguments of BigDecimal_divide --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e4dc5658..6e808f13 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1345,7 +1345,7 @@ BigDecimal_mult(VALUE self, VALUE r) } static VALUE -BigDecimal_divide(Real **c, Real **res, Real **div, VALUE self, VALUE r) +BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) /* For c = self.div(r): with round operation */ { ENTER(5); @@ -1392,7 +1392,7 @@ BigDecimal_div(VALUE self, VALUE r) { ENTER(5); Real *c=NULL, *res=NULL, *div = NULL; - r = BigDecimal_divide(&c, &res, &div, self, r); + r = BigDecimal_divide(self, r, &c, &res, &div); if (!NIL_P(r)) return r; /* coerced by other */ SAVE(c); SAVE(res); SAVE(div); /* a/b = c + r/b */ From 3b55ad1c429ce88152bca6de6e2b88c4b2fb21f4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:19:39 +0900 Subject: [PATCH 193/546] Use new conversion functions in BigDecimal_divide --- ext/bigdecimal/bigdecimal.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6e808f13..d6097a8f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -209,6 +209,7 @@ static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exc static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); +static VALUE rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) @@ -1352,18 +1353,25 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) Real *a, *b; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); + TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); + SAVE(a); + + VALUE rr = Qnil; if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, 0, 1); + rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); - } + rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); + } else { - b = GetVpValue(r, 0); + rr = rb_convert_to_BigDecimal(r, 0, false); + } + + if (!is_kind_of_BigDecimal(rr)) { + return DoSomeOne(self, r, '/'); } - if (!b) return DoSomeOne(self, r, '/'); + TypedData_Get_Struct(rr, Real, &BigDecimal_data_type, b); SAVE(b); *div = b; From 74cd0393df52b0b7a5afc99acbbd1325aa402607 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:21:12 +0900 Subject: [PATCH 194/546] Use larger precision in divide for irrational or recurring results Just in case for irrational or recurring results, the precision of the quotient is set to at least more than 2*Float::DIG plus alpha. [Bug #13754] [Fix GH-94] --- ext/bigdecimal/bigdecimal.c | 13 ++++++------- test/bigdecimal/test_bigdecimal.rb | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d6097a8f..f314d26c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1375,13 +1375,12 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) SAVE(b); *div = b; - mx = a->Prec + vabs(a->exponent); - if (mx < b->Prec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); - mx++; /* NOTE: An additional digit is needed for the compatibility to - the version 1.2.1 and the former. */ - mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ((*c), VpCreateRbObject(mx, "#0", true)); - GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); + mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; + mx *= BASE_FIG; + if (2*DBLE_FIG > mx) + mx = 2*DBLE_FIG; + GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); + GUARD_OBJ((*res), VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); VpDivd(*c, *res, a, b); return Qnil; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index b015f9a9..7ebc66b2 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -936,9 +936,11 @@ def test_div assert_equal(2, BigDecimal("2") / 1) assert_equal(-2, BigDecimal("2") / -1) - assert_equal(BigDecimal('1486.868686869'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') + assert_not_equal(BigDecimal('1487.0'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') - assert_equal(4.124045235, BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0')), '[#9305]') + assert_in_delta(4.124045235, + (BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0'))).round(9, half: :up), + 10**Float::MIN_10_EXP, '[#9305]') BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) assert_positive_zero(BigDecimal("1.0") / BigDecimal("Infinity")) @@ -952,6 +954,15 @@ def test_div assert_raise_with_message(FloatDomainError, "Computation results to '-Infinity'") { BigDecimal("-1") / 0 } end + def test_dev_precision + bug13754 = '[ruby-core:82107] [Bug #13754]' + a = BigDecimal('101') + b = BigDecimal('0.9163472602589686') + c = a/b + assert(c.precision > b.precision, + "(101/0.9163472602589686).precision >= (0.9163472602589686).precision #{bug13754}") + end + def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) From 2e1dfd23cb6e0a23cd0f15a9e9cc118e3d7fa501 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:41:18 +0900 Subject: [PATCH 195/546] Fix assersion for [Bug #9316] --- test/bigdecimal/test_bigdecimal.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 7ebc66b2..f9e912cf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -936,7 +936,9 @@ def test_div assert_equal(2, BigDecimal("2") / 1) assert_equal(-2, BigDecimal("2") / -1) - assert_not_equal(BigDecimal('1487.0'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') + assert_equal(BigDecimal('1486.868686869'), + (BigDecimal('1472.0') / BigDecimal('0.99')).round(9), + '[ruby-core:59365] [#9316]') assert_in_delta(4.124045235, (BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0'))).round(9, half: :up), From a109d0984fff46bc8535d7e03b225a968be62ec9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:57:30 +0900 Subject: [PATCH 196/546] [Doc] Fix the comment of BigDecimal_div2 [ci skip] --- ext/bigdecimal/bigdecimal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f314d26c..47b64043 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1617,7 +1617,8 @@ BigDecimal_divmod(VALUE self, VALUE r) } /* - * See BigDecimal#quo + * Do the same manner as Float#div when n is nil. + * Do the same manner as BigDecimal#quo when n is 0. */ static inline VALUE BigDecimal_div2(VALUE self, VALUE b, VALUE n) From 68c20200d5f4242ac8c4d2d69685c197140a5390 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:03:56 +0900 Subject: [PATCH 197/546] Use new conversion functions in BigDecimal_DoDivmod --- ext/bigdecimal/bigdecimal.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 47b64043..0e8400e4 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1425,18 +1425,25 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) Real *a, *b; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); + TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); + SAVE(a); + + VALUE rr = Qnil; if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, 0, 1); + rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } else { - b = GetVpValue(r, 0); + rr = rb_convert_to_BigDecimal(r, 0, false); } - if (!b) return Qfalse; + if (!is_kind_of_BigDecimal(rr)) { + return Qfalse; + } + + TypedData_Get_Struct(rr, Real, &BigDecimal_data_type, b); SAVE(b); if (VpIsNaN(a) || VpIsNaN(b)) goto NaN; From d2746121cfc57a151179da2266768f728b554a65 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:04:48 +0900 Subject: [PATCH 198/546] Use pre-allocated special values in BigDecimal_DoDivmod --- ext/bigdecimal/bigdecimal.c | 47 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0e8400e4..a7f9f351 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1449,28 +1449,32 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) if (VpIsNaN(a) || VpIsNaN(b)) goto NaN; if (VpIsInf(a) && VpIsInf(b)) goto NaN; if (VpIsZero(b)) { - rb_raise(rb_eZeroDivError, "divided by 0"); + rb_raise(rb_eZeroDivError, "divided by 0"); } if (VpIsInf(a)) { - GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); - VpSetInf(d, (SIGNED_VALUE)(VpGetSign(a) == VpGetSign(b) ? 1 : -1)); - GUARD_OBJ(c, VpCreateRbObject(1, "NaN", true)); - *div = d; - *mod = c; - return Qtrue; + if (VpGetSign(a) == VpGetSign(b)) { + VALUE inf = BigDecimal_positive_infinity(); + TypedData_Get_Struct(inf, Real, &BigDecimal_data_type, *div); + } + else { + VALUE inf = BigDecimal_negative_infinity(); + TypedData_Get_Struct(inf, Real, &BigDecimal_data_type, *div); + } + VALUE nan = BigDecimal_nan(); + TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *mod); + return Qtrue; } if (VpIsInf(b)) { - GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); - *div = d; - *mod = a; - return Qtrue; + VALUE zero = BigDecimal_positive_zero(); + TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *div); + *mod = a; + return Qtrue; } if (VpIsZero(a)) { - GUARD_OBJ(c, VpCreateRbObject(1, "0", true)); - GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); - *div = d; - *mod = c; - return Qtrue; + VALUE zero = BigDecimal_positive_zero(); + TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *div); + TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *mod); + return Qtrue; } mx = a->Prec + vabs(a->exponent); @@ -1496,11 +1500,12 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) } return Qtrue; -NaN: - GUARD_OBJ(c, VpCreateRbObject(1, "NaN", true)); - GUARD_OBJ(d, VpCreateRbObject(1, "NaN", true)); - *div = d; - *mod = c; + NaN: + { + VALUE nan = BigDecimal_nan(); + TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *div); + TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *mod); + } return Qtrue; } From b2cf1c134a47683d85fcbef9d1632faccc220983 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:10:54 +0900 Subject: [PATCH 199/546] Let BigDecimal_DoDivmod use the same precision calculation as BigDecimal_divide --- ext/bigdecimal/bigdecimal.c | 33 ++++++++++++++++++------------ test/bigdecimal/test_bigdecimal.rb | 7 +++++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a7f9f351..baa26200 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1477,26 +1477,33 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) return Qtrue; } - mx = a->Prec + vabs(a->exponent); - if (mxPrec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); - mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); + mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; + mx *= BASE_FIG; + if (2*DBLE_FIG > mx) + mx = 2*DBLE_FIG; + + GUARD_OBJ(c, VpCreateRbObject(mx + 2*BASE_FIG, "0", true)); + GUARD_OBJ(res, VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); VpDivd(c, res, a, b); - mx = c->Prec * (VpBaseFig() + 1); + + mx = c->Prec * BASE_FIG; GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); + VpMult(res, d, b); VpAddSub(c, a, res, -1); + if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - VpAddSub(res, d, VpOne(), -1); + /* remainder adjustment for negative case */ + VpAddSub(res, d, VpOne(), -1); GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); - VpAddSub(d, c, b, 1); - *div = res; - *mod = d; - } else { - *div = d; - *mod = c; + VpAddSub(d, c, b, 1); + *div = res; + *mod = d; + } + else { + *div = d; + *mod = c; } return Qtrue; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f9e912cf..5df70bc6 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1017,6 +1017,13 @@ def test_divmod assert_raise(ZeroDivisionError){BigDecimal("0").divmod(0)} end + def test_divmod_precision + a = BigDecimal('2e55') + b = BigDecimal('1.23456789e10') + q, r = a.divmod(b) + assert_equal((a/b), q) + end + def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 716062b5014a8da9a14c52efb2a959e1a3674dc9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:25:37 +0900 Subject: [PATCH 200/546] Fix the precision of the adjusted quotient --- ext/bigdecimal/bigdecimal.c | 6 ++++-- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index baa26200..b095ec55 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1494,9 +1494,11 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) VpAddSub(c, a, res, -1); if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - /* remainder adjustment for negative case */ + /* result adjustment for negative case */ + res = VpReallocReal(res, d->MaxPrec); + res->MaxPrec = d->MaxPrec; VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); + GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b) * 2*BASE_FIG, "0", true)); VpAddSub(d, c, b, 1); *div = res; *mod = d; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 5df70bc6..a343c8a4 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1022,6 +1022,10 @@ def test_divmod_precision b = BigDecimal('1.23456789e10') q, r = a.divmod(b) assert_equal((a/b), q) + + b = BigDecimal('-1.23456789e10') + q, r = a.divmod(b) + assert_equal((a/b), q) end def test_add_bigdecimal From 1cb92487f79f1976d2526ebf6bbfb83872c45837 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:44:45 +0900 Subject: [PATCH 201/546] Fix for the coerce cases in divide and DoDivmod --- ext/bigdecimal/bigdecimal.c | 26 ++++++++++++++++---------- test/bigdecimal/test_bigdecimal.rb | 13 +++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b095ec55..4a30355f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1356,16 +1356,19 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = Qnil; - if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ + } + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } - else { - rr = rb_convert_to_BigDecimal(r, 0, false); - } if (!is_kind_of_BigDecimal(rr)) { return DoSomeOne(self, r, '/'); @@ -1428,16 +1431,19 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = Qnil; - if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ + } + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } - else { - rr = rb_convert_to_BigDecimal(r, 0, false); - } if (!is_kind_of_BigDecimal(rr)) { return Qfalse; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a343c8a4..14e35bee 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -974,6 +974,15 @@ def test_div_with_rational assert_kind_of(BigDecimal, BigDecimal("3") / 1.quo(3)) end + def test_div_with_complex + q = BigDecimal("3") / 1i + assert_kind_of(Complex, q) + end + + def test_div_error + assert_raise(TypeError) { BigDecimal(20) / '2' } + end + def test_mod x = BigDecimal((2**100).to_s) assert_equal(1, x % 3) @@ -1028,6 +1037,10 @@ def test_divmod_precision assert_equal((a/b), q) end + def test_divmod_error + assert_raise(TypeError) { BigDecimal(20).divmod('2') } + end + def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 3d1838ca55778b4724ee6ad841404e9b46d440f9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 20 Jan 2021 17:09:15 +0900 Subject: [PATCH 202/546] Revert "Fix for the coerce cases in divide and DoDivmod" This reverts commit 1cb92487f79f1976d2526ebf6bbfb83872c45837. --- ext/bigdecimal/bigdecimal.c | 26 ++++++++++---------------- test/bigdecimal/test_bigdecimal.rb | 13 ------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4a30355f..b095ec55 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1356,19 +1356,16 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = r; - if (is_kind_of_BigDecimal(rr)) { - /* do nothing */ - } - else if (RB_INTEGER_TYPE_P(r)) { - rr = rb_inum_convert_to_BigDecimal(r, 0, true); - } - else if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = Qnil; + if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } + else { + rr = rb_convert_to_BigDecimal(r, 0, false); + } if (!is_kind_of_BigDecimal(rr)) { return DoSomeOne(self, r, '/'); @@ -1431,19 +1428,16 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = r; - if (is_kind_of_BigDecimal(rr)) { - /* do nothing */ - } - else if (RB_INTEGER_TYPE_P(r)) { - rr = rb_inum_convert_to_BigDecimal(r, 0, true); - } - else if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = Qnil; + if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } + else { + rr = rb_convert_to_BigDecimal(r, 0, false); + } if (!is_kind_of_BigDecimal(rr)) { return Qfalse; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 14e35bee..a343c8a4 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -974,15 +974,6 @@ def test_div_with_rational assert_kind_of(BigDecimal, BigDecimal("3") / 1.quo(3)) end - def test_div_with_complex - q = BigDecimal("3") / 1i - assert_kind_of(Complex, q) - end - - def test_div_error - assert_raise(TypeError) { BigDecimal(20) / '2' } - end - def test_mod x = BigDecimal((2**100).to_s) assert_equal(1, x % 3) @@ -1037,10 +1028,6 @@ def test_divmod_precision assert_equal((a/b), q) end - def test_divmod_error - assert_raise(TypeError) { BigDecimal(20).divmod('2') } - end - def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 20a2a877bf0e30d7b31692cbec2e3ac7b325f08d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 20 Jan 2021 17:09:41 +0900 Subject: [PATCH 203/546] Revert "Fix the precision of the adjusted quotient" This reverts commit 716062b5014a8da9a14c52efb2a959e1a3674dc9. --- ext/bigdecimal/bigdecimal.c | 6 ++---- test/bigdecimal/test_bigdecimal.rb | 4 ---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b095ec55..baa26200 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1494,11 +1494,9 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) VpAddSub(c, a, res, -1); if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - /* result adjustment for negative case */ - res = VpReallocReal(res, d->MaxPrec); - res->MaxPrec = d->MaxPrec; + /* remainder adjustment for negative case */ VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b) * 2*BASE_FIG, "0", true)); + GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); VpAddSub(d, c, b, 1); *div = res; *mod = d; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a343c8a4..5df70bc6 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1022,10 +1022,6 @@ def test_divmod_precision b = BigDecimal('1.23456789e10') q, r = a.divmod(b) assert_equal((a/b), q) - - b = BigDecimal('-1.23456789e10') - q, r = a.divmod(b) - assert_equal((a/b), q) end def test_add_bigdecimal From 5a6242820a2bd6c1043f405de5d13b0f847e0bea Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 20 Jan 2021 17:10:11 +0900 Subject: [PATCH 204/546] Revert "Let BigDecimal_DoDivmod use the same precision calculation as BigDecimal_divide" This reverts commit b2cf1c134a47683d85fcbef9d1632faccc220983. --- ext/bigdecimal/bigdecimal.c | 33 ++++++++++++------------------ test/bigdecimal/test_bigdecimal.rb | 7 ------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index baa26200..a7f9f351 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1477,33 +1477,26 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) return Qtrue; } - mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; - mx *= BASE_FIG; - if (2*DBLE_FIG > mx) - mx = 2*DBLE_FIG; - - GUARD_OBJ(c, VpCreateRbObject(mx + 2*BASE_FIG, "0", true)); - GUARD_OBJ(res, VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); + mx = a->Prec + vabs(a->exponent); + if (mxPrec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); + mx = (mx + 1) * VpBaseFig(); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); VpDivd(c, res, a, b); - - mx = c->Prec * BASE_FIG; + mx = c->Prec * (VpBaseFig() + 1); GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); - VpMult(res, d, b); VpAddSub(c, a, res, -1); - if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - /* remainder adjustment for negative case */ - VpAddSub(res, d, VpOne(), -1); + VpAddSub(res, d, VpOne(), -1); GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); - VpAddSub(d, c, b, 1); - *div = res; - *mod = d; - } - else { - *div = d; - *mod = c; + VpAddSub(d, c, b, 1); + *div = res; + *mod = d; + } else { + *div = d; + *mod = c; } return Qtrue; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 5df70bc6..f9e912cf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1017,13 +1017,6 @@ def test_divmod assert_raise(ZeroDivisionError){BigDecimal("0").divmod(0)} end - def test_divmod_precision - a = BigDecimal('2e55') - b = BigDecimal('1.23456789e10') - q, r = a.divmod(b) - assert_equal((a/b), q) - end - def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 12cfd9a2ced2128bc8b13c072c5df6bed8771fe3 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 20 Jan 2021 17:12:57 +0900 Subject: [PATCH 205/546] Revert "Use larger precision in divide for irrational or recurring results" This reverts commit 74cd0393df52b0b7a5afc99acbbd1325aa402607. [Reopen GH-94] --- ext/bigdecimal/bigdecimal.c | 13 +++++++------ test/bigdecimal/test_bigdecimal.rb | 17 ++--------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a7f9f351..910e1f9f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1375,12 +1375,13 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) SAVE(b); *div = b; - mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; - mx *= BASE_FIG; - if (2*DBLE_FIG > mx) - mx = 2*DBLE_FIG; - GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); - GUARD_OBJ((*res), VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); + mx = a->Prec + vabs(a->exponent); + if (mx < b->Prec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); + mx++; /* NOTE: An additional digit is needed for the compatibility to + the version 1.2.1 and the former. */ + mx = (mx + 1) * VpBaseFig(); + GUARD_OBJ((*c), VpCreateRbObject(mx, "#0", true)); + GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); VpDivd(*c, *res, a, b); return Qnil; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f9e912cf..b015f9a9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -936,13 +936,9 @@ def test_div assert_equal(2, BigDecimal("2") / 1) assert_equal(-2, BigDecimal("2") / -1) - assert_equal(BigDecimal('1486.868686869'), - (BigDecimal('1472.0') / BigDecimal('0.99')).round(9), - '[ruby-core:59365] [#9316]') + assert_equal(BigDecimal('1486.868686869'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') - assert_in_delta(4.124045235, - (BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0'))).round(9, half: :up), - 10**Float::MIN_10_EXP, '[#9305]') + assert_equal(4.124045235, BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0')), '[#9305]') BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) assert_positive_zero(BigDecimal("1.0") / BigDecimal("Infinity")) @@ -956,15 +952,6 @@ def test_div assert_raise_with_message(FloatDomainError, "Computation results to '-Infinity'") { BigDecimal("-1") / 0 } end - def test_dev_precision - bug13754 = '[ruby-core:82107] [Bug #13754]' - a = BigDecimal('101') - b = BigDecimal('0.9163472602589686') - c = a/b - assert(c.precision > b.precision, - "(101/0.9163472602589686).precision >= (0.9163472602589686).precision #{bug13754}") - end - def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) From 84f135866b600a78d6015ef1c7ada87da58ee48a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 21 Jan 2021 14:02:30 +0900 Subject: [PATCH 206/546] Revert "Revert "Fix for the coerce cases in divide and DoDivmod"" This reverts commit 3d1838ca55778b4724ee6ad841404e9b46d440f9. --- ext/bigdecimal/bigdecimal.c | 26 ++++++++++++++++---------- test/bigdecimal/test_bigdecimal.rb | 13 +++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 910e1f9f..c553e1d7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1356,16 +1356,19 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = Qnil; - if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ + } + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } - else { - rr = rb_convert_to_BigDecimal(r, 0, false); - } if (!is_kind_of_BigDecimal(rr)) { return DoSomeOne(self, r, '/'); @@ -1429,16 +1432,19 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = Qnil; - if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ + } + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } - else { - rr = rb_convert_to_BigDecimal(r, 0, false); - } if (!is_kind_of_BigDecimal(rr)) { return Qfalse; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index b015f9a9..49e6b2a7 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -961,6 +961,15 @@ def test_div_with_rational assert_kind_of(BigDecimal, BigDecimal("3") / 1.quo(3)) end + def test_div_with_complex + q = BigDecimal("3") / 1i + assert_kind_of(Complex, q) + end + + def test_div_error + assert_raise(TypeError) { BigDecimal(20) / '2' } + end + def test_mod x = BigDecimal((2**100).to_s) assert_equal(1, x % 3) @@ -1004,6 +1013,10 @@ def test_divmod assert_raise(ZeroDivisionError){BigDecimal("0").divmod(0)} end + def test_divmod_error + assert_raise(TypeError) { BigDecimal(20).divmod('2') } + end + def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 4d5b97125b8edc71feae45653d8dd4663dcc38c6 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 29 Jan 2021 18:06:13 +0900 Subject: [PATCH 207/546] Fix the maximum length of float number This change is for preventing the false-positive alert by CoverityScan. See CID-1471770 for the detail. --- ext/bigdecimal/bigdecimal.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c553e1d7..e0832b82 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2856,14 +2856,16 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } /* Use the same logic in flo_to_s to convert a float to a decimal string */ - char buf[DBLE_FIG + BASE_FIG + 2 + 1]; + char buf[DBLE_FIG + BASE_FIG + 2 + 1]; /* sizeof(buf) == 28 in the typical case */ int decpt, negative_p; char *e; const int mode = digs == 0 ? 0 : 2; char *p = BigDecimal_dtoa(d, mode, (int)digs, &decpt, &negative_p, &e); int len10 = (int)(e - p); - if (len10 >= (int)sizeof(buf)) - len10 = (int)sizeof(buf) - 1; + if (len10 > DBLE_FIG) { + /* TODO: Presumably, rounding should be done here. */ + len10 = DBLE_FIG; + } memcpy(buf, p, len10); xfree(p); From 7479923fdba7dbf9c759f29959e1c165146a7365 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 30 Jan 2021 12:05:15 +0900 Subject: [PATCH 208/546] Stop using rmpd and RMPD prefixes and DBLE_FIG --- ext/bigdecimal/bigdecimal.c | 90 ++++++++++++++++++------------------- ext/bigdecimal/bigdecimal.h | 55 +++++++++-------------- 2 files changed, 64 insertions(+), 81 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e0832b82..b9ba0ea6 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -67,16 +67,12 @@ static ID id_half; #define SAVE(p) PUSH((p)->obj) #define GUARD_OBJ(p,y) ((p)=(y), SAVE(p)) -#define BASE_FIG RMPD_COMPONENT_FIGURES -#define BASE RMPD_BASE +#define BASE_FIG BIGDECIMAL_COMPONENT_FIGURES +#define BASE BIGDECIMAL_BASE #define HALF_BASE (BASE/2) #define BASE1 (BASE/10) -#ifndef DBLE_FIG -#define DBLE_FIG RMPD_DOUBLE_FIGURES /* figure of double */ -#endif - #define LOG10_2 0.3010299956639812 #ifndef RRATIONAL_ZERO_P @@ -2371,7 +2367,7 @@ is_even(VALUE x) } static VALUE -rmpd_power_by_big_decimal(Real const* x, Real const* exp, ssize_t const n) +bigdecimal_power_by_bigdecimal(Real const* x, Real const* exp, ssize_t const n) { VALUE log_x, multiplied, y; volatile VALUE obj = exp->obj; @@ -2441,7 +2437,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) goto retry; } if (NIL_P(prec)) { - n += DBLE_FIG; + n += BIGDECIMAL_DOUBLE_FIGURES; } exp = GetVpValueWithPrec(vexp, 0, 1); break; @@ -2573,7 +2569,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) } if (exp != NULL) { - return rmpd_power_by_big_decimal(x, exp, n); + return bigdecimal_power_by_bigdecimal(x, exp, n); } else if (RB_TYPE_P(vexp, T_BIGNUM)) { VALUE abs_value = BigDecimal_abs(self); @@ -2630,7 +2626,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { GUARD_OBJ(y, VpCreateRbObject(1, "0", true)); } - VpPower(y, x, int_exp); + VpPowerByInt(y, x, int_exp); if (!NIL_P(prec) && VpIsDef(y)) { VpMidRound(y, VpGetRoundMode(), n); } @@ -2849,22 +2845,22 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) "can't omit precision for a %"PRIsVALUE".", CLASS_OF(val)); } - else if (digs > DBLE_FIG) { + else if (digs > BIGDECIMAL_DOUBLE_FIGURES) { if (!raise_exception) return Qnil; rb_raise(rb_eArgError, "precision too large."); } /* Use the same logic in flo_to_s to convert a float to a decimal string */ - char buf[DBLE_FIG + BASE_FIG + 2 + 1]; /* sizeof(buf) == 28 in the typical case */ + char buf[BIGDECIMAL_DOUBLE_FIGURES + BASE_FIG + 2 + 1]; /* sizeof(buf) == 28 in the typical case */ int decpt, negative_p; char *e; const int mode = digs == 0 ? 0 : 2; char *p = BigDecimal_dtoa(d, mode, (int)digs, &decpt, &negative_p, &e); int len10 = (int)(e - p); - if (len10 > DBLE_FIG) { + if (len10 > BIGDECIMAL_DOUBLE_FIGURES) { /* TODO: Presumably, rounding should be done here. */ - len10 = DBLE_FIG; + len10 = BIGDECIMAL_DOUBLE_FIGURES; } memcpy(buf, p, len10); xfree(p); @@ -3381,7 +3377,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } x = vx->obj; - n = prec + rmpd_double_figures(); + n = prec + BIGDECIMAL_DOUBLE_FIGURES; negative = BIGDECIMAL_NEGATIVE_P(vx); if (negative) { VALUE x_zero = INT2NUM(1); @@ -3406,8 +3402,8 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) if (m <= 0) { break; } - else if ((size_t)m < rmpd_double_figures()) { - m = rmpd_double_figures(); + else if ((size_t)m < BIGDECIMAL_DOUBLE_FIGURES) { + m = BIGDECIMAL_DOUBLE_FIGURES; } d = BigDecimal_mult(d, x); /* d <- d * x */ @@ -3543,7 +3539,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1", true)); RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2", true)); - n = prec + rmpd_double_figures(); + n = prec + BIGDECIMAL_DOUBLE_FIGURES; RB_GC_GUARD(vn) = SSIZET2NUM(n); expo = VpExponent10(vx); if (expo < 0 || expo >= 3) { @@ -3567,8 +3563,8 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) if (m <= 0) { break; } - else if ((size_t)m < rmpd_double_figures()) { - m = rmpd_double_figures(); + else if ((size_t)m < BIGDECIMAL_DOUBLE_FIGURES) { + m = BIGDECIMAL_DOUBLE_FIGURES; } x = BigDecimal_mult2(x2, x, vn); @@ -4095,7 +4091,7 @@ VpFree(Real *pv) * EXCEPTION Handling. */ -#define rmpd_set_thread_local_exception_mode(mode) \ +#define bigdecimal_set_thread_local_exception_mode(mode) \ rb_thread_local_aset( \ rb_thread_current(), \ id_BigDecimal_exception_mode, \ @@ -4111,8 +4107,8 @@ VpGetException (void) ); if (NIL_P(vmode)) { - rmpd_set_thread_local_exception_mode(RMPD_EXCEPTION_MODE_DEFAULT); - return RMPD_EXCEPTION_MODE_DEFAULT; + bigdecimal_set_thread_local_exception_mode(BIGDECIMAL_EXCEPTION_MODE_DEFAULT); + return BIGDECIMAL_EXCEPTION_MODE_DEFAULT; } return NUM2USHORT(vmode); @@ -4121,20 +4117,20 @@ VpGetException (void) static void VpSetException(unsigned short f) { - rmpd_set_thread_local_exception_mode(f); + bigdecimal_set_thread_local_exception_mode(f); } /* * Precision limit. */ -#define rmpd_set_thread_local_precision_limit(limit) \ +#define bigdecimal_set_thread_local_precision_limit(limit) \ rb_thread_local_aset( \ rb_thread_current(), \ id_BigDecimal_precision_limit, \ SIZET2NUM(limit) \ ) -#define RMPD_PRECISION_LIMIT_DEFAULT ((size_t)0) +#define BIGDECIMAL_PRECISION_LIMIT_DEFAULT ((size_t)0) /* These 2 functions added at v1.1.7 */ VP_EXPORT size_t @@ -4146,8 +4142,8 @@ VpGetPrecLimit(void) ); if (NIL_P(vlimit)) { - rmpd_set_thread_local_precision_limit(RMPD_PRECISION_LIMIT_DEFAULT); - return RMPD_PRECISION_LIMIT_DEFAULT; + bigdecimal_set_thread_local_precision_limit(BIGDECIMAL_PRECISION_LIMIT_DEFAULT); + return BIGDECIMAL_PRECISION_LIMIT_DEFAULT; } return NUM2SIZET(vlimit); @@ -4157,7 +4153,7 @@ VP_EXPORT size_t VpSetPrecLimit(size_t n) { size_t const s = VpGetPrecLimit(); - rmpd_set_thread_local_precision_limit(n); + bigdecimal_set_thread_local_precision_limit(n); return s; } @@ -4165,7 +4161,7 @@ VpSetPrecLimit(size_t n) * Rounding mode. */ -#define rmpd_set_thread_local_rounding_mode(mode) \ +#define bigdecimal_set_thread_local_rounding_mode(mode) \ rb_thread_local_aset( \ rb_thread_current(), \ id_BigDecimal_rounding_mode, \ @@ -4181,8 +4177,8 @@ VpGetRoundMode(void) ); if (NIL_P(vmode)) { - rmpd_set_thread_local_rounding_mode(RMPD_ROUNDING_MODE_DEFAULT); - return RMPD_ROUNDING_MODE_DEFAULT; + bigdecimal_set_thread_local_rounding_mode(BIGDECIMAL_ROUNDING_MODE_DEFAULT); + return BIGDECIMAL_ROUNDING_MODE_DEFAULT; } return NUM2USHORT(vmode); @@ -4210,7 +4206,7 @@ VP_EXPORT unsigned short VpSetRoundMode(unsigned short n) { if (VpIsRoundMode(n)) { - rmpd_set_thread_local_rounding_mode(n); + bigdecimal_set_thread_local_rounding_mode(n); return n; } @@ -4450,7 +4446,7 @@ VpNumOfChars(Real *vp,const char *pszFmt) * by one DECDIG word in the computer used. * * [Returns] - * DBLE_FIG ... OK + * BIGDECIMAL_DOUBLE_FIGURES ... OK */ VP_EXPORT size_t VpInit(DECDIG BaseVal) @@ -4468,16 +4464,16 @@ VpInit(DECDIG BaseVal) #ifdef BIGDECIMAL_DEBUG if (gfDebug) { - printf("VpInit: BaseVal = %"PRIuDECDIG"\n", BaseVal); + printf("VpInit: BaseVal = %"PRIuDECDIG"\n", BaseVal); printf("\tBASE = %"PRIuDECDIG"\n", BASE); printf("\tHALF_BASE = %"PRIuDECDIG"\n", HALF_BASE); printf("\tBASE1 = %"PRIuDECDIG"\n", BASE1); printf("\tBASE_FIG = %u\n", BASE_FIG); - printf("\tDBLE_FIG = %d\n", DBLE_FIG); + printf("\tBIGDECIMAL_DOUBLE_FIGURES = %d\n", BIGDECIMAL_DOUBLE_FIGURES); } #endif /* BIGDECIMAL_DEBUG */ - return rmpd_double_figures(); + return BIGDECIMAL_DOUBLE_FIGURES; } VP_EXPORT Real * @@ -4525,7 +4521,7 @@ AddExponent(Real *a, SIGNED_VALUE n) } Real * -rmpd_parse_special_string(const char *str) +bigdecimal_parse_special_string(const char *str) { static const struct { const char *str; @@ -4626,7 +4622,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) } /* Check on Inf & NaN */ - if ((vp = rmpd_parse_special_string(szVal)) != NULL) { + if ((vp = bigdecimal_parse_special_string(szVal)) != NULL) { return vp; } @@ -5832,7 +5828,7 @@ VPrint(FILE *fp, const char *cntl_chr, Real *a) case '0': case 'z': ZeroSup = 0; ++j; - sep = cntl_chr[j] == 'z' ? RMPD_COMPONENT_FIGURES : 10; + sep = cntl_chr[j] == 'z' ? BIGDECIMAL_COMPONENT_FIGURES : 10; break; } for (i = 0; i < a->Prec; ++i) { @@ -6284,7 +6280,7 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con * [Output] * *d ... fraction part of m(d = 0.xxxxxxx). where # of 'x's is fig. * *e ... exponent of m. - * DBLE_FIG ... Number of digits in a double variable. + * BIGDECIMAL_DOUBLE_FIGURES ... Number of digits in a double variable. * * m -> d*10**e, 0Prec); *d = 0.0; @@ -6348,7 +6344,7 @@ VpVtoD(double *d, SIGNED_VALUE *e, Real *m) if (gfDebug) { VPrint(stdout, " VpVtoD: m=%\n", m); printf(" d=%e * 10 **%ld\n", *d, *e); - printf(" DBLE_FIG = %d\n", DBLE_FIG); + printf(" BIGDECIMAL_DOUBLE_FIGURES = %d\n", BIGDECIMAL_DOUBLE_FIGURES); } #endif /*BIGDECIMAL_DEBUG */ return f; @@ -6549,7 +6545,7 @@ VpSqrt(Real *y, Real *x) } VpDtoV(y, sqrt(val)); /* y <- sqrt(val) */ y->exponent += n; - n = (SIGNED_VALUE)((DBLE_FIG + BASE_FIG - 1) / BASE_FIG); + n = (SIGNED_VALUE)roomof(BIGDECIMAL_DOUBLE_FIGURES, BASE_FIG); y->MaxPrec = Min((size_t)n , y_prec); f->MaxPrec = y->MaxPrec + 1; n = (SIGNED_VALUE)(y_prec * BASE_FIG); @@ -6919,7 +6915,7 @@ VpFrac(Real *y, Real *x) * y = x ** n */ VP_EXPORT int -VpPower(Real *y, Real *x, SIGNED_VALUE n) +VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n) { size_t s, ss; ssize_t sign; @@ -7006,8 +7002,8 @@ VpPower(Real *y, Real *x, SIGNED_VALUE n) Exit: #ifdef BIGDECIMAL_DEBUG if (gfDebug) { - VPrint(stdout, "VpPower y=%\n", y); - VPrint(stdout, "VpPower x=%\n", x); + VPrint(stdout, "VpPowerByInt y=%\n", y); + VPrint(stdout, "VpPowerByInt x=%\n", x); printf(" n=%"PRIdVALUE"\n", n); } #endif /* BIGDECIMAL_DEBUG */ diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 5f343db6..acc00b81 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -24,9 +24,9 @@ # define SIZEOF_DECDIG 4 # define PRI_DECDIG_PREFIX "" # ifdef PRI_LL_PREFIX -# define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX +# define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX # else -# define PRI_DECDIG_DBL_PREFIX "l" +# define PRI_DECDIG_DBL_PREFIX "l" # endif #else # define DECDIG uint16_t @@ -51,6 +51,18 @@ #define PRIxDECDIG_DBL PRI_DECDIG_DBL_PREFIX"x" #define PRIXDECDIG_DBL PRI_DECDIG_DBL_PREFIX"X" +#if SIZEOF_DECDIG == 4 +# define BIGDECIMAL_BASE ((DECDIG)1000000000U) +# define BIGDECIMAL_COMPONENT_FIGURES 9 +#elif SIZEOF_DECDIG == 2 +# define BIGDECIMAL_BASE ((DECDIG)10000U) +# define BIGDECIMAL_COMPONENT_FIGURES 4 +#else +# error Unknown size of DECDIG +#endif + +#define BIGDECIMAL_DOUBLE_FIGURES (1+DBL_DIG) + #if defined(__cplusplus) extern "C" { #if 0 @@ -60,25 +72,6 @@ extern "C" { extern VALUE rb_cBigDecimal; -#if 0 || SIZEOF_DECDIG >= 16 -# define RMPD_COMPONENT_FIGURES 38 -# define RMPD_BASE ((DECDIG)100000000000000000000000000000000000000U) -#elif SIZEOF_DECDIG >= 8 -# define RMPD_COMPONENT_FIGURES 19 -# define RMPD_BASE ((DECDIG)10000000000000000000U) -#elif SIZEOF_DECDIG >= 4 -# define RMPD_COMPONENT_FIGURES 9 -# define RMPD_BASE ((DECDIG)1000000000U) -#elif SIZEOF_DECDIG >= 2 -# define RMPD_COMPONENT_FIGURES 4 -# define RMPD_BASE ((DECDIG)10000U) -#else -# define RMPD_COMPONENT_FIGURES 2 -# define RMPD_BASE ((DECDIG)100U) -#endif - -#define RMPD_DOUBLE_FIGURES (1+DBL_DIG) - /* * NaN & Infinity */ @@ -104,7 +97,7 @@ extern VALUE rb_cBigDecimal; /* Following 2 exceptions can't controlled by user */ #define VP_EXCEPTION_OP ((unsigned short)0x0020) -#define RMPD_EXCEPTION_MODE_DEFAULT 0U +#define BIGDECIMAL_EXCEPTION_MODE_DEFAULT 0U /* Computation mode */ #define VP_ROUND_MODE ((unsigned short)0x0100) @@ -116,7 +109,7 @@ extern VALUE rb_cBigDecimal; #define VP_ROUND_FLOOR 6 #define VP_ROUND_HALF_EVEN 7 -#define RMPD_ROUNDING_MODE_DEFAULT VP_ROUND_HALF_UP +#define BIGDECIMAL_ROUNDING_MODE_DEFAULT VP_ROUND_HALF_UP #define VP_SIGN_NaN 0 /* NaN */ #define VP_SIGN_POSITIVE_ZERO 1 /* Positive zero */ @@ -171,16 +164,9 @@ VP_EXPORT Real *VpNewRbClass(size_t mx, char const *str, VALUE klass, bool stric VP_EXPORT Real *VpCreateRbObject(size_t mx, const char *str, bool raise_exception); -static inline DECDIG -rmpd_base_value(void) { return RMPD_BASE; } -static inline size_t -rmpd_component_figures(void) { return RMPD_COMPONENT_FIGURES; } -static inline size_t -rmpd_double_figures(void) { return RMPD_DOUBLE_FIGURES; } - -#define VpBaseFig() rmpd_component_figures() -#define VpDblFig() rmpd_double_figures() -#define VpBaseVal() rmpd_base_value() +#define VpBaseFig() BIGDECIMAL_COMPONENT_FIGURES +#define VpDblFig() BIGDECIMAL_DOUBLE_FIGURES +#define VpBaseVal() BIGDECIMAL_BASE /* Zero,Inf,NaN (isinf(),isnan() used to check) */ VP_EXPORT double VpGetDoubleNaN(void); @@ -228,7 +214,8 @@ VP_EXPORT int VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t il); VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf); VP_EXPORT int VpLeftRound(Real *y, unsigned short f, ssize_t nf); VP_EXPORT void VpFrac(Real *y, Real *x); -VP_EXPORT int VpPower(Real *y, Real *x, SIGNED_VALUE n); +VP_EXPORT int VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n); +#define VpPower VpPowerByInt /* VP constants */ VP_EXPORT Real *VpOne(void); From 9067b353accb4dc407488460ccd1228e519d9185 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 30 Jan 2021 13:00:03 +0900 Subject: [PATCH 209/546] Fix uint64 conversion Stop using logarithm to compute the number of components. Instead, use the theoretical maximum number of components for buffer, and count up the actual number of components during conversion. --- ext/bigdecimal/bigdecimal.c | 23 +++++++++++------------ ext/bigdecimal/bigdecimal.h | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b9ba0ea6..704f0451 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2727,23 +2727,22 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->frac[0] = (DECDIG)uval; } else { - const size_t len = (size_t)ceil(log10((double)uval) / BASE_FIG); - - vp = VpAllocReal(len); - vp->MaxPrec = len; - vp->Prec = len; - vp->exponent = len; - VpSetSign(vp, 1); - - size_t i, ntz = 0; - for (i = 0; i < len; ++i) { + DECDIG buf[BIGDECIMAL_INT64_MAX_LENGTH] = {0,}; + size_t exp = 0, ntz = 0; + for (; uval > 0; ++exp) { DECDIG r = uval % BASE; - vp->frac[len - i - 1] = r; if (r == 0) ++ntz; + buf[BIGDECIMAL_INT64_MAX_LENGTH - exp - 1] = r; uval /= BASE; } - vp->Prec -= ntz; + const size_t len = exp - ntz; + vp = VpAllocReal(len); + vp->MaxPrec = len; + vp->Prec = len; + vp->exponent = exp; + VpSetSign(vp, 1); + MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - exp, DECDIG, len); } return BigDecimal_wrap_struct(obj, vp); diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index acc00b81..bd1c4674 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -54,9 +54,25 @@ #if SIZEOF_DECDIG == 4 # define BIGDECIMAL_BASE ((DECDIG)1000000000U) # define BIGDECIMAL_COMPONENT_FIGURES 9 +/* + * The number of components required for a 64-bit integer. + * + * INT64_MAX: 9_223372036_854775807 + * UINT64_MAX: 18_446744073_709551615 + */ +# define BIGDECIMAL_INT64_MAX_LENGTH 3 + #elif SIZEOF_DECDIG == 2 # define BIGDECIMAL_BASE ((DECDIG)10000U) # define BIGDECIMAL_COMPONENT_FIGURES 4 +/* + * The number of components required for a 64-bit integer. + * + * INT64_MAX: 922_3372_0368_5477_5807 + * UINT64_MAX: 1844_6744_0737_0955_1615 + */ +# define BIGDECIMAL_INT64_MAX_LENGTH 5 + #else # error Unknown size of DECDIG #endif From ac62a6cc77f7758e9e51fcce0d4baa2eeebaec52 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 3 May 2021 12:25:16 +0900 Subject: [PATCH 210/546] Version 3.0.1 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index ef003e0e..2e017eee 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '3.0.1.dev' +bigdecimal_version = '3.0.1' Gem::Specification.new do |s| s.name = "bigdecimal" From bff0911a8cea11251ff21128b2dbbafe8faab73a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 2 Mar 2021 20:56:44 +0900 Subject: [PATCH 211/546] Removed test-suite of ruby repository --- test/lib/envutil.rb | 365 ----------- test/lib/find_executable.rb | 22 - test/lib/test/unit.rb | 1003 ------------------------------ test/lib/test/unit/assertions.rb | 940 ---------------------------- test/lib/test/unit/parallel.rb | 191 ------ test/lib/test/unit/testcase.rb | 36 -- 6 files changed, 2557 deletions(-) delete mode 100644 test/lib/envutil.rb delete mode 100644 test/lib/find_executable.rb delete mode 100644 test/lib/test/unit.rb delete mode 100644 test/lib/test/unit/assertions.rb delete mode 100644 test/lib/test/unit/parallel.rb delete mode 100644 test/lib/test/unit/testcase.rb diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb deleted file mode 100644 index 937e1128..00000000 --- a/test/lib/envutil.rb +++ /dev/null @@ -1,365 +0,0 @@ -# -*- coding: us-ascii -*- -# frozen_string_literal: true -require "open3" -require "timeout" -require_relative "find_executable" -begin - require 'rbconfig' -rescue LoadError -end -begin - require "rbconfig/sizeof" -rescue LoadError -end - -module EnvUtil - def rubybin - if ruby = ENV["RUBY"] - return ruby - end - ruby = "ruby" - exeext = RbConfig::CONFIG["EXEEXT"] - rubyexe = (ruby + exeext if exeext and !exeext.empty?) - 3.times do - if File.exist? ruby and File.executable? ruby and !File.directory? ruby - return File.expand_path(ruby) - end - if rubyexe and File.exist? rubyexe and File.executable? rubyexe - return File.expand_path(rubyexe) - end - ruby = File.join("..", ruby) - end - if defined?(RbConfig.ruby) - RbConfig.ruby - else - "ruby" - end - end - module_function :rubybin - - LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" - - DEFAULT_SIGNALS = Signal.list - DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM - - RUBYLIB = ENV["RUBYLIB"] - - class << self - attr_accessor :timeout_scale - attr_reader :original_internal_encoding, :original_external_encoding, - :original_verbose, :original_warning - - def capture_global_values - @original_internal_encoding = Encoding.default_internal - @original_external_encoding = Encoding.default_external - @original_verbose = $VERBOSE - @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil - end - end - - def apply_timeout_scale(t) - if scale = EnvUtil.timeout_scale - t * scale - else - t - end - end - module_function :apply_timeout_scale - - def timeout(sec, klass = nil, message = nil, &blk) - return yield(sec) if sec == nil or sec.zero? - sec = apply_timeout_scale(sec) - Timeout.timeout(sec, klass, message, &blk) - end - module_function :timeout - - def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) - reprieve = apply_timeout_scale(reprieve) if reprieve - - signals = Array(signal).select do |sig| - DEFAULT_SIGNALS[sig.to_s] or - DEFAULT_SIGNALS[Signal.signame(sig)] rescue false - end - signals |= [:ABRT, :KILL] - case pgroup - when 0, true - pgroup = -pid - when nil, false - pgroup = pid - end - - lldb = true if /darwin/ =~ RUBY_PLATFORM - - while signal = signals.shift - - if lldb and [:ABRT, :KILL].include?(signal) - lldb = false - # sudo -n: --non-interactive - # lldb -p: attach - # -o: run command - system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) - true - end - - begin - Process.kill signal, pgroup - rescue Errno::EINVAL - next - rescue Errno::ESRCH - break - end - if signals.empty? or !reprieve - Process.wait(pid) - else - begin - Timeout.timeout(reprieve) {Process.wait(pid)} - rescue Timeout::Error - else - break - end - end - end - $? - end - module_function :terminate - - def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, - encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, - stdout_filter: nil, stderr_filter: nil, - signal: :TERM, - rubybin: EnvUtil.rubybin, precommand: nil, - **opt) - timeout = apply_timeout_scale(timeout) - - in_c, in_p = IO.pipe - out_p, out_c = IO.pipe if capture_stdout - err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout - opt[:in] = in_c - opt[:out] = out_c if capture_stdout - opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr - if encoding - out_p.set_encoding(encoding) if out_p - err_p.set_encoding(encoding) if err_p - end - c = "C" - child_env = {} - LANG_ENVS.each {|lc| child_env[lc] = c} - if Array === args and Hash === args.first - child_env.update(args.shift) - end - if RUBYLIB and lib = child_env["RUBYLIB"] - child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) - end - child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS'] - args = [args] if args.kind_of?(String) - pid = spawn(child_env, *precommand, rubybin, *args, **opt) - in_c.close - out_c&.close - out_c = nil - err_c&.close - err_c = nil - if block_given? - return yield in_p, out_p, err_p, pid - else - th_stdout = Thread.new { out_p.read } if capture_stdout - th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout - in_p.write stdin_data.to_str unless stdin_data.empty? - in_p.close - if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) - timeout_error = nil - else - status = terminate(pid, signal, opt[:pgroup], reprieve) - terminated = Time.now - end - stdout = th_stdout.value if capture_stdout - stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout - out_p.close if capture_stdout - err_p.close if capture_stderr && capture_stderr != :merge_to_stdout - status ||= Process.wait2(pid)[1] - stdout = stdout_filter.call(stdout) if stdout_filter - stderr = stderr_filter.call(stderr) if stderr_filter - if timeout_error - bt = caller_locations - msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" - msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n")) - raise timeout_error, msg, bt.map(&:to_s) - end - return stdout, stderr, status - end - ensure - [th_stdout, th_stderr].each do |th| - th.kill if th - end - [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| - io&.close - end - [th_stdout, th_stderr].each do |th| - th.join if th - end - end - module_function :invoke_ruby - - def verbose_warning - class << (stderr = "".dup) - alias write concat - def flush; end - end - stderr, $stderr = $stderr, stderr - $VERBOSE = true - yield stderr - return $stderr - ensure - stderr, $stderr = $stderr, stderr - $VERBOSE = EnvUtil.original_verbose - EnvUtil.original_warning&.each {|i, v| Warning[i] = v} - end - module_function :verbose_warning - - def default_warning - $VERBOSE = false - yield - ensure - $VERBOSE = EnvUtil.original_verbose - end - module_function :default_warning - - def suppress_warning - $VERBOSE = nil - yield - ensure - $VERBOSE = EnvUtil.original_verbose - end - module_function :suppress_warning - - def under_gc_stress(stress = true) - stress, GC.stress = GC.stress, stress - yield - ensure - GC.stress = stress - end - module_function :under_gc_stress - - def with_default_external(enc) - suppress_warning { Encoding.default_external = enc } - yield - ensure - suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding } - end - module_function :with_default_external - - def with_default_internal(enc) - suppress_warning { Encoding.default_internal = enc } - yield - ensure - suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding } - end - module_function :with_default_internal - - def labeled_module(name, &block) - Module.new do - singleton_class.class_eval { - define_method(:to_s) {name} - alias inspect to_s - alias name to_s - } - class_eval(&block) if block - end - end - module_function :labeled_module - - def labeled_class(name, superclass = Object, &block) - Class.new(superclass) do - singleton_class.class_eval { - define_method(:to_s) {name} - alias inspect to_s - alias name to_s - } - class_eval(&block) if block - end - end - module_function :labeled_class - - if /darwin/ =~ RUBY_PLATFORM - DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") - DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' - @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] - - def self.diagnostic_reports(signame, pid, now) - return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) - cmd = File.basename(rubybin) - cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd - path = DIAGNOSTIC_REPORTS_PATH - timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT - pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" - first = true - 30.times do - first ? (first = false) : sleep(0.1) - Dir.glob(pat) do |name| - log = File.read(name) rescue next - if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log - File.unlink(name) - File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil - return log - end - end - end - nil - end - else - def self.diagnostic_reports(signame, pid, now) - end - end - - def self.failure_description(status, now, message = "", out = "") - pid = status.pid - if signo = status.termsig - signame = Signal.signame(signo) - sigdesc = "signal #{signo}" - end - log = diagnostic_reports(signame, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc = "#{sigdesc} (core dumped)" - end - full_message = ''.dup - message = message.call if Proc === message - if message and !message.empty? - full_message << message << "\n" - end - full_message << "pid #{pid}" - full_message << " exit #{status.exitstatus}" if status.exited? - full_message << " killed by #{sigdesc}" if sigdesc - if out and !out.empty? - full_message << "\n" << out.b.gsub(/^/, '| ') - full_message.sub!(/(?$()]/ ? s.inspect : s }.join " " - @options = options - end - - private - def setup_options(opts, options) - opts.separator 'minitest options:' - opts.version = MiniTest::Unit::VERSION - - opts.on '-h', '--help', 'Display this help.' do - puts opts - exit - end - - opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m| - options[:seed] = m - end - - opts.on '-v', '--verbose', "Verbose. Show progress processing files." do - options[:verbose] = true - self.verbose = options[:verbose] - end - - opts.on '-n', '--name PATTERN', "Filter test method names on pattern: /REGEXP/ or STRING" do |a| - options[:filter] = a - end - - opts.on '--test-order=random|alpha|sorted', [:random, :alpha, :sorted] do |a| - MiniTest::Unit::TestCase.test_order = a - end - end - - def non_options(files, options) - true - end - end - - module Parallel # :nodoc: all - def process_args(args = []) - return @options if @options - options = super - if @options[:parallel] - @files = args - end - options - end - - def status(*args) - result = super - raise @interrupt if @interrupt - result - end - - private - def setup_options(opts, options) - super - - opts.separator "parallel test options:" - - options[:retry] = true - - opts.on '-j N', '--jobs N', "Allow run tests with N jobs at once" do |a| - if /^t/ =~ a - options[:testing] = true # For testing - options[:parallel] = a[1..-1].to_i - else - options[:parallel] = a.to_i - end - end - - opts.on '--separate', "Restart job process after one testcase has done" do - options[:parallel] ||= 1 - options[:separate] = true - end - - opts.on '--retry', "Retry running testcase when --jobs specified" do - options[:retry] = true - end - - opts.on '--no-retry', "Disable --retry" do - options[:retry] = false - end - - opts.on '--ruby VAL', "Path to ruby; It'll have used at -j option" do |a| - options[:ruby] = a.split(/ /).reject(&:empty?) - end - end - - class Worker - def self.launch(ruby,args=[]) - io = IO.popen([*ruby, - "#{File.dirname(__FILE__)}/unit/parallel.rb", - *args], "rb+") - new(io, io.pid, :waiting) - end - - attr_reader :quit_called - - def initialize(io, pid, status) - @io = io - @pid = pid - @status = status - @file = nil - @real_file = nil - @loadpath = [] - @hooks = {} - @quit_called = false - end - - def puts(*args) - @io.puts(*args) - end - - def run(task,type) - @file = File.basename(task, ".rb") - @real_file = task - begin - puts "loadpath #{[Marshal.dump($:-@loadpath)].pack("m0")}" - @loadpath = $:.dup - puts "run #{task} #{type}" - @status = :prepare - rescue Errno::EPIPE - died - rescue IOError - raise unless ["stream closed","closed stream"].include? $!.message - died - end - end - - def hook(id,&block) - @hooks[id] ||= [] - @hooks[id] << block - self - end - - def read - res = (@status == :quit) ? @io.read : @io.gets - res && res.chomp - end - - def close - @io.close unless @io.closed? - self - rescue IOError - end - - def quit - return if @io.closed? - @quit_called = true - @io.puts "quit" - @io.close - end - - def kill - Process.kill(:KILL, @pid) - rescue Errno::ESRCH - end - - def died(*additional) - @status = :quit - @io.close - status = $? - if status and status.signaled? - additional[0] ||= SignalException.new(status.termsig) - end - - call_hook(:dead,*additional) - end - - def to_s - if @file - "#{@pid}=#{@file}" - else - "#{@pid}:#{@status.to_s.ljust(7)}" - end - end - - attr_reader :io, :pid - attr_accessor :status, :file, :real_file, :loadpath - - private - - def call_hook(id,*additional) - @hooks[id] ||= [] - @hooks[id].each{|hook| hook[self,additional] } - self - end - - end - - def after_worker_down(worker, e=nil, c=false) - return unless @options[:parallel] - return if @interrupt - warn e if e - @need_quit = true - warn "" - warn "Some worker was crashed. It seems ruby interpreter's bug" - warn "or, a bug of test/unit/parallel.rb. try again without -j" - warn "option." - warn "" - STDERR.flush - exit c - end - - def after_worker_quit(worker) - return unless @options[:parallel] - return if @interrupt - @workers.delete(worker) - @dead_workers << worker - @ios = @workers.map(&:io) - end - - def launch_worker - begin - worker = Worker.launch(@options[:ruby], @run_options) - rescue => e - abort "ERROR: Failed to launch job process - #{e.class}: #{e.message}" - end - worker.hook(:dead) do |w,info| - after_worker_quit w - after_worker_down w, *info if !info.empty? && !worker.quit_called - end - @workers << worker - @ios << worker.io - @workers_hash[worker.io] = worker - worker - end - - def delete_worker(worker) - @workers_hash.delete worker.io - @workers.delete worker - @ios.delete worker.io - end - - def quit_workers - return if @workers.empty? - @workers.reject! do |worker| - begin - Timeout.timeout(1) do - worker.quit - end - rescue Errno::EPIPE - rescue Timeout::Error - end - worker.close - end - - return if @workers.empty? - begin - Timeout.timeout(0.2 * @workers.size) do - Process.waitall - end - rescue Timeout::Error - @workers.each do |worker| - worker.kill - end - @worker.clear - end - end - - def deal(io, type, result, rep, shutting_down = false) - worker = @workers_hash[io] - cmd = worker.read - cmd.sub!(/\A\.+/, '') if cmd # read may return nil - case cmd - when '' - # just only dots, ignore - when /^okay$/ - worker.status = :running - jobs_status - when /^ready(!)?$/ - bang = $1 - worker.status = :ready - - return nil unless task = @tasks.shift - if @options[:separate] and not bang - worker.quit - worker = add_worker - end - worker.run(task, type) - @test_count += 1 - - jobs_status - when /^done (.+?)$/ - begin - r = Marshal.load($1.unpack("m")[0]) - rescue - print "unknown object: #{$1.unpack("m")[0].dump}" - return true - end - result << r[0..1] unless r[0..1] == [nil,nil] - rep << {file: worker.real_file, report: r[2], result: r[3], testcase: r[5]} - $:.push(*r[4]).uniq! - return true - when /^p (.+?)$/ - del_jobs_status - print $1.unpack("m")[0] - jobs_status if @options[:job_status] == :replace - when /^after (.+?)$/ - @warnings << Marshal.load($1.unpack("m")[0]) - when /^bye (.+?)$/ - after_worker_down worker, Marshal.load($1.unpack("m")[0]) - when /^bye$/, nil - if shutting_down || worker.quit_called - after_worker_quit worker - else - after_worker_down worker - end - else - print "unknown command: #{cmd.dump}\n" - end - return false - end - - def _run_parallel suites, type, result - if @options[:parallel] < 1 - warn "Error: parameter of -j option should be greater than 0." - return - end - - # Require needed things for parallel running - require 'thread' - require 'timeout' - @tasks = @files.dup # Array of filenames. - @need_quit = false - @dead_workers = [] # Array of dead workers. - @warnings = [] - @total_tests = @tasks.size.to_s(10) - rep = [] # FIXME: more good naming - - @workers = [] # Array of workers. - @workers_hash = {} # out-IO => worker - @ios = [] # Array of worker IOs - begin - @options[:parallel].times {launch_worker} - - while _io = IO.select(@ios)[0] - break if _io.any? do |io| - @need_quit or - (deal(io, type, result, rep).nil? and - !@workers.any? {|x| [:running, :prepare].include? x.status}) - end - end - rescue Interrupt => ex - @interrupt = ex - return result - ensure - if @interrupt - @ios.select!{|x| @workers_hash[x].status == :running } - while !@ios.empty? && (__io = IO.select(@ios,[],[],10)) - __io[0].reject! {|io| deal(io, type, result, rep, true)} - end - end - - quit_workers - - unless @interrupt || !@options[:retry] || @need_quit - @options[:parallel] = false - suites, rep = rep.partition {|r| r[:testcase] && r[:file] && r[:report].any? {|e| !e[2].is_a?(MiniTest::Skip)}} - suites.map {|r| r[:file]}.uniq.each {|file| require file} - suites.map! {|r| eval("::"+r[:testcase])} - del_status_line or puts - unless suites.empty? - puts "Retrying..." - _run_suites(suites, type) - end - end - unless @options[:retry] - del_status_line or puts - end - unless rep.empty? - rep.each do |r| - r[:report].each do |f| - puke(*f) if f - end - end - if @options[:retry] - @errors += rep.map{|x| x[:result][0] }.inject(:+) - @failures += rep.map{|x| x[:result][1] }.inject(:+) - @skips += rep.map{|x| x[:result][2] }.inject(:+) - end - end - unless @warnings.empty? - warn "" - @warnings.uniq! {|w| w[1].message} - @warnings.each do |w| - warn "#{w[0]}: #{w[1].message} (#{w[1].class})" - end - warn "" - end - end - end - - def _run_suites suites, type - _prepare_run(suites, type) - @interrupt = nil - result = [] - GC.start - if @options[:parallel] - _run_parallel suites, type, result - else - suites.each {|suite| - begin - result << _run_suite(suite, type) - rescue Interrupt => e - @interrupt = e - break - end - } - end - result - end - end - - module Skipping # :nodoc: all - private - def setup_options(opts, options) - super - - opts.separator "skipping options:" - - options[:hide_skip] = true - - opts.on '-q', '--hide-skip', 'Hide skipped tests' do - options[:hide_skip] = true - end - - opts.on '--show-skip', 'Show skipped tests' do - options[:hide_skip] = false - end - end - - private - def _run_suites(suites, type) - result = super - report.reject!{|r| r.start_with? "Skipped:" } if @options[:hide_skip] - report.sort_by!{|r| r.start_with?("Skipped:") ? 0 : \ - (r.start_with?("Failure:") ? 1 : 2) } - result - end - end - - module StatusLine # :nodoc: all - def terminal_width - unless @terminal_width ||= nil - begin - require 'io/console' - width = $stdout.winsize[1] - rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL - width = ENV["COLUMNS"].to_i.nonzero? || 80 - end - width -= 1 if /mswin|mingw/ =~ RUBY_PLATFORM - @terminal_width = width - end - @terminal_width - end - - def del_status_line(flush = true) - @status_line_size ||= 0 - unless @options[:job_status] == :replace - $stdout.puts - return - end - print "\r"+" "*@status_line_size+"\r" - $stdout.flush if flush - @status_line_size = 0 - end - - def add_status(line, flush: true) - unless @options[:job_status] == :replace - print(line) - return - end - @status_line_size ||= 0 - line = line[0...(terminal_width-@status_line_size)] - print line - $stdout.flush if flush - @status_line_size += line.size - end - - def jobs_status - return unless @options[:job_status] - puts "" unless @options[:verbose] or @options[:job_status] == :replace - status_line = @workers.map(&:to_s).join(" ") - update_status(status_line) or (puts; nil) - end - - def del_jobs_status - return unless @options[:job_status] == :replace && @status_line_size.nonzero? - del_status_line - end - - def output - (@output ||= nil) || super - end - - def _prepare_run(suites, type) - options[:job_status] ||= :replace if @tty && !@verbose - case options[:color] - when :always - color = true - when :auto, nil - color = (@tty || @options[:job_status] == :replace) && /dumb/ !~ ENV["TERM"] - else - color = false - end - if color - # dircolors-like style - colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {} - begin - File.read(File.join(__dir__, "../../colors")).scan(/(\w+)=([^:\n]*)/) do |n, c| - colors[n] ||= c - end - rescue - end - @passed_color = "\e[;#{colors["pass"] || "32"}m" - @failed_color = "\e[;#{colors["fail"] || "31"}m" - @skipped_color = "\e[;#{colors["skip"] || "33"}m" - @reset_color = "\e[m" - else - @passed_color = @failed_color = @skipped_color = @reset_color = "" - end - if color or @options[:job_status] == :replace - @verbose = !options[:parallel] - @output = Output.new(self) - end - if /\A\/(.*)\/\z/ =~ (filter = options[:filter]) - filter = Regexp.new($1) - end - type = "#{type}_methods" - total = if filter - suites.inject(0) {|n, suite| n + suite.send(type).grep(filter).size} - else - suites.inject(0) {|n, suite| n + suite.send(type).size} - end - @test_count = 0 - @total_tests = total.to_s(10) - end - - def new_test(s) - @test_count += 1 - update_status(s) - end - - def update_status(s) - count = @test_count.to_s(10).rjust(@total_tests.size) - del_status_line(false) if @options[:job_status] == :replace - print(@passed_color) - add_status("[#{count}/#{@total_tests}]", flush: false) - print(@reset_color) - add_status(" #{s}") - end - - def _print(s); $stdout.print(s); end - def succeed; del_status_line; end - - def failed(s) - sep = "\n" - @report_count ||= 0 - report.each do |msg| - if msg.start_with? "Skipped:" - if @options[:hide_skip] - del_status_line - next - end - color = @skipped_color - else - color = @failed_color - end - msg = msg.split(/$/, 2) - $stdout.printf("%s%s%3d) %s%s%s\n", - sep, color, @report_count += 1, - msg[0], @reset_color, msg[1]) - sep = nil - end - report.clear - end - - def initialize - super - @tty = $stdout.tty? - end - - def run(*args) - result = super - puts "\nruby -v: #{RUBY_DESCRIPTION}" - result - end - - private - def setup_options(opts, options) - super - - opts.separator "status line options:" - - options[:job_status] = nil - - opts.on '--jobs-status [TYPE]', [:normal, :replace], - "Show status of jobs every file; Disabled when --jobs isn't specified." do |type| - options[:job_status] = type || :normal - end - - opts.on '--color[=WHEN]', - [:always, :never, :auto], - "colorize the output. WHEN defaults to 'always'", "or can be 'never' or 'auto'." do |c| - options[:color] = c || :always - end - - opts.on '--tty[=WHEN]', - [:yes, :no], - "force to output tty control. WHEN defaults to 'yes'", "or can be 'no'." do |c| - @tty = c != :no - end - end - - class Output < Struct.new(:runner) # :nodoc: all - def puts(*a) $stdout.puts(*a) unless a.empty? end - def respond_to_missing?(*a) $stdout.respond_to?(*a) end - def method_missing(*a, &b) $stdout.__send__(*a, &b) end - - def print(s) - case s - when /\A(.*\#.*) = \z/ - runner.new_test($1) - when /\A(.* s) = \z/ - runner.add_status(" = "+$1.chomp) - when /\A\.+\z/ - runner.succeed - when /\A[EFS]\z/ - runner.failed(s) - else - $stdout.print(s) - end - end - end - end - - module LoadPathOption # :nodoc: all - def non_options(files, options) - begin - require "rbconfig" - rescue LoadError - warn "#{caller(1)[0]}: warning: Parallel running disabled because can't get path to ruby; run specify with --ruby argument" - options[:parallel] = nil - else - options[:ruby] ||= [RbConfig.ruby] - end - - super - end - - def setup_options(parser, options) - super - parser.separator "load path options:" - parser.on '-Idirectory', 'Add library load path' do |dirs| - dirs.split(':').each { |d| $LOAD_PATH.unshift d } - end - end - end - - module GlobOption # :nodoc: all - @@testfile_prefix = "test" - - def setup_options(parser, options) - super - parser.separator "globbing options:" - parser.on '-b', '--basedir=DIR', 'Base directory of test suites.' do |dir| - options[:base_directory] = dir - end - parser.on '-x', '--exclude REGEXP', 'Exclude test files on pattern.' do |pattern| - (options[:reject] ||= []) << pattern - end - end - - def non_options(files, options) - paths = [options.delete(:base_directory), nil].uniq - if reject = options.delete(:reject) - reject_pat = Regexp.union(reject.map {|r| %r"#{r}"}) - end - files.map! {|f| - f = f.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR - ((paths if /\A\.\.?(?:\z|\/)/ !~ f) || [nil]).any? do |prefix| - if prefix - path = f.empty? ? prefix : "#{prefix}/#{f}" - else - next if f.empty? - path = f - end - if !(match = Dir["#{path}/**/#{@@testfile_prefix}_*.rb"]).empty? - if reject - match.reject! {|n| - n[(prefix.length+1)..-1] if prefix - reject_pat =~ n - } - end - break match - elsif !reject or reject_pat !~ f and File.exist? path - break path - end - end or - raise ArgumentError, "file not found: #{f}" - } - files.flatten! - super(files, options) - end - end - - module GCStressOption # :nodoc: all - def setup_options(parser, options) - super - parser.separator "GC options:" - parser.on '--[no-]gc-stress', 'Set GC.stress as true' do |flag| - options[:gc_stress] = flag - end - end - - def non_options(files, options) - if options.delete(:gc_stress) - MiniTest::Unit::TestCase.class_eval do - oldrun = instance_method(:run) - define_method(:run) do |runner| - begin - gc_stress, GC.stress = GC.stress, true - oldrun.bind(self).call(runner) - ensure - GC.stress = gc_stress - end - end - end - end - super - end - end - - module RequireFiles # :nodoc: all - def non_options(files, options) - return false if !super - errors = {} - result = false - files.each {|f| - d = File.dirname(path = File.realpath(f)) - unless $:.include? d - $: << d - end - begin - require path unless options[:parallel] - result = true - rescue LoadError - next if errors[$!.message] - errors[$!.message] = true - puts "#{f}: #{$!}" - end - } - result - end - end - - module ExcludesOption # :nodoc: all - class ExcludedMethods < Struct.new(:excludes) - def exclude(name, reason) - excludes[name] = reason - end - - def exclude_from(klass) - excludes = self.excludes - pattern = excludes.keys.grep(Regexp).tap {|k| - break (Regexp.new(k.join('|')) unless k.empty?) - } - klass.class_eval do - public_instance_methods(false).each do |method| - if excludes[method] or (pattern and pattern =~ method) - remove_method(method) - end - end - public_instance_methods(true).each do |method| - if excludes[method] or (pattern and pattern =~ method) - undef_method(method) - end - end - end - end - - def self.load(dirs, name) - return unless dirs and name - instance = nil - dirs.each do |dir| - path = File.join(dir, name.gsub(/::/, '/') + ".rb") - begin - src = File.read(path) - rescue Errno::ENOENT - nil - else - instance ||= new({}) - instance.instance_eval(src) - end - end - instance - end - end - - def setup_options(parser, options) - super - if excludes = ENV["EXCLUDES"] - excludes = excludes.split(File::PATH_SEPARATOR) - end - options[:excludes] = excludes || [] - parser.on '-X', '--excludes-dir DIRECTORY', "Directory name of exclude files" do |d| - options[:excludes].concat d.split(File::PATH_SEPARATOR) - end - end - - def _run_suite(suite, type) - if ex = ExcludedMethods.load(@options[:excludes], suite.name) - ex.exclude_from(suite) - end - super - end - end - - class Runner < MiniTest::Unit # :nodoc: all - include Test::Unit::Options - include Test::Unit::StatusLine - include Test::Unit::Parallel - include Test::Unit::Skipping - include Test::Unit::GlobOption - include Test::Unit::LoadPathOption - include Test::Unit::GCStressOption - include Test::Unit::ExcludesOption - include Test::Unit::RunCount - - class << self; undef autorun; end - - @@stop_auto_run = false - def self.autorun - at_exit { - Test::Unit::RunCount.run_once { - exit(Test::Unit::Runner.new.run(ARGV) || true) - } unless @@stop_auto_run - } unless @@installed_at_exit - @@installed_at_exit = true - end - - alias mini_run_suite _run_suite - - # Overriding of MiniTest::Unit#puke - def puke klass, meth, e - # TODO: - # this overriding is for minitest feature that skip messages are - # hidden when not verbose (-v), note this is temporally. - n = report.size - rep = super - if MiniTest::Skip === e and /no message given\z/ =~ e.message - report.slice!(n..-1) - rep = "." - end - rep - end - end - - class AutoRunner # :nodoc: all - class Runner < Test::Unit::Runner - include Test::Unit::RequireFiles - end - - attr_accessor :to_run, :options - - def initialize(force_standalone = false, default_dir = nil, argv = ARGV) - @force_standalone = force_standalone - @runner = Runner.new do |files, options| - options[:base_directory] ||= default_dir - files << default_dir if files.empty? and default_dir - @to_run = files - yield self if block_given? - files - end - Runner.runner = @runner - @options = @runner.option_parser - if @force_standalone - @options.banner.sub!(/\[options\]/, '\& tests...') - end - @argv = argv - end - - def process_args(*args) - @runner.process_args(*args) - !@to_run.empty? - end - - def run - if @force_standalone and not process_args(@argv) - abort @options.banner - end - @runner.run(@argv) || true - end - - def self.run(*args) - new(*args).run - end - end - - class ProxyError < StandardError # :nodoc: all - def initialize(ex) - @message = ex.message - @backtrace = ex.backtrace - end - - attr_accessor :message, :backtrace - end - end -end - -module MiniTest # :nodoc: all - class Unit - end -end - -class MiniTest::Unit::TestCase # :nodoc: all - test_order = self.test_order - class << self - attr_writer :test_order - undef test_order - end - def self.test_order - defined?(@test_order) ? @test_order : superclass.test_order - end - self.test_order = test_order - undef run_test - RUN_TEST_TRACE = "#{__FILE__}:#{__LINE__+3}:in `run_test'".freeze - def run_test(name) - progname, $0 = $0, "#{$0}: #{self.class}##{name}" - self.__send__(name) - ensure - $@.delete(RUN_TEST_TRACE) if $@ - $0 = progname - end -end - -Test::Unit::Runner.autorun diff --git a/test/lib/test/unit/assertions.rb b/test/lib/test/unit/assertions.rb deleted file mode 100644 index ee6a758f..00000000 --- a/test/lib/test/unit/assertions.rb +++ /dev/null @@ -1,940 +0,0 @@ -# frozen_string_literal: true -require 'minitest/unit' -require 'pp' - -module Test - module Unit - module Assertions - include MiniTest::Assertions - - def mu_pp(obj) #:nodoc: - obj.pretty_inspect.chomp - end - - MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: - - # :call-seq: - # assert(test, [failure_message]) - # - #Tests if +test+ is true. - # - #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used - #as the failure message. Otherwise, the result of calling +msg+ will be - #used as the message if the assertion fails. - # - #If no +msg+ is given, a default message will be used. - # - # assert(false, "This was expected to be true") - def assert(test, *msgs) - case msg = msgs.first - when String, Proc - when nil - msgs.shift - else - bt = caller.reject { |s| s.start_with?(MINI_DIR) } - raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt - end unless msgs.empty? - super - end - - # :call-seq: - # assert_block( failure_message = nil ) - # - #Tests the result of the given block. If the block does not return true, - #the assertion will fail. The optional +failure_message+ argument is the same as in - #Assertions#assert. - # - # assert_block do - # [1, 2, 3].any? { |num| num < 1 } - # end - def assert_block(*msgs) - assert yield, *msgs - end - - # :call-seq: - # assert_raise( *args, &block ) - # - #Tests if the given block raises an exception. Acceptable exception - #types may be given as optional arguments. If the last argument is a - #String, it will be used as the error message. - # - # assert_raise do #Fails, no Exceptions are raised - # end - # - # assert_raise NameError do - # puts x #Raises NameError, so assertion succeeds - # end - def assert_raise(*exp, &b) - case exp.last - when String, Proc - msg = exp.pop - end - - begin - yield - rescue MiniTest::Skip => e - return e if exp.include? MiniTest::Skip - raise e - rescue Exception => e - expected = exp.any? { |ex| - if ex.instance_of? Module then - e.kind_of? ex - else - e.instance_of? ex - end - } - - assert expected, proc { - exception_details(e, message(msg) {"#{mu_pp(exp)} exception expected, not"}.call) - } - - return e - end - - exp = exp.first if exp.size == 1 - - flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) - end - - def assert_raises(*exp, &b) - raise NoMethodError, "use assert_raise", caller - end - - # :call-seq: - # assert_raise_with_message(exception, expected, msg = nil, &block) - # - #Tests if the given block raises an exception with the expected - #message. - # - # assert_raise_with_message(RuntimeError, "foo") do - # nil #Fails, no Exceptions are raised - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise ArgumentError, "foo" #Fails, different Exception is raised - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise "bar" #Fails, RuntimeError is raised but the message differs - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise "foo" #Raises RuntimeError with the message, so assertion succeeds - # end - def assert_raise_with_message(exception, expected, msg = nil, &block) - case expected - when String - assert = :assert_equal - when Regexp - assert = :assert_match - else - raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" - end - - ex = m = nil - EnvUtil.with_default_internal(expected.encoding) do - ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do - yield - end - m = ex.message - end - msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} - - if assert == :assert_equal - assert_equal(expected, m, msg) - else - msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } - assert expected =~ m, msg - block.binding.eval("proc{|_|$~=_}").call($~) - end - ex - end - - # :call-seq: - # assert_nothing_raised( *args, &block ) - # - #If any exceptions are given as arguments, the assertion will - #fail if one of those exceptions are raised. Otherwise, the test fails - #if any exceptions are raised. - # - #The final argument may be a failure message. - # - # assert_nothing_raised RuntimeError do - # raise Exception #Assertion passes, Exception is not a RuntimeError - # end - # - # assert_nothing_raised do - # raise Exception #Assertion fails - # end - def assert_nothing_raised(*args) - self._assertions += 1 - if Module === args.last - msg = nil - else - msg = args.pop - end - begin - line = __LINE__; yield - rescue MiniTest::Skip - raise - rescue Exception => e - bt = e.backtrace - as = e.instance_of?(MiniTest::Assertion) - if as - ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o - bt.reject! {|ln| ans =~ ln} - end - if ((args.empty? && !as) || - args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a }) - msg = message(msg) { "Exception raised:\n<#{mu_pp(e)}>" } - raise MiniTest::Assertion, msg.call, bt - else - raise - end - end - end - - # :call-seq: - # assert_nothing_thrown( failure_message = nil, &block ) - # - #Fails if the given block uses a call to Kernel#throw, and - #returns the result of the block otherwise. - # - #An optional failure message may be provided as the final argument. - # - # assert_nothing_thrown "Something was thrown!" do - # throw :problem? - # end - def assert_nothing_thrown(msg=nil) - begin - ret = yield - rescue ArgumentError => error - raise error if /\Auncaught throw (.+)\z/m !~ error.message - msg = message(msg) { "<#{$1}> was thrown when nothing was expected" } - flunk(msg) - end - assert(true, "Expected nothing to be thrown") - ret - end - - # :call-seq: - # assert_throw( tag, failure_message = nil, &block ) - # - #Fails unless the given block throws +tag+, returns the caught - #value otherwise. - # - #An optional failure message may be provided as the final argument. - # - # tag = Object.new - # assert_throw(tag, "#{tag} was not thrown!") do - # throw tag - # end - def assert_throw(tag, msg = nil) - ret = catch(tag) do - begin - yield(tag) - rescue UncaughtThrowError => e - thrown = e.tag - end - msg = message(msg) { - "Expected #{mu_pp(tag)} to have been thrown"\ - "#{%Q[, not #{thrown}] if thrown}" - } - assert(false, msg) - end - assert(true) - ret - end - - # :call-seq: - # assert_equal( expected, actual, failure_message = nil ) - # - #Tests if +expected+ is equal to +actual+. - # - #An optional failure message may be provided as the final argument. - def assert_equal(exp, act, msg = nil) - msg = message(msg) { - exp_str = mu_pp(exp) - act_str = mu_pp(act) - exp_comment = '' - act_comment = '' - if exp_str == act_str - if (exp.is_a?(String) && act.is_a?(String)) || - (exp.is_a?(Regexp) && act.is_a?(Regexp)) - exp_comment = " (#{exp.encoding})" - act_comment = " (#{act.encoding})" - elsif exp.is_a?(Float) && act.is_a?(Float) - exp_str = "%\#.#{Float::DIG+2}g" % exp - act_str = "%\#.#{Float::DIG+2}g" % act - elsif exp.is_a?(Time) && act.is_a?(Time) - if exp.subsec * 1000_000_000 == exp.nsec - exp_comment = " (#{exp.nsec}[ns])" - else - exp_comment = " (subsec=#{exp.subsec})" - end - if act.subsec * 1000_000_000 == act.nsec - act_comment = " (#{act.nsec}[ns])" - else - act_comment = " (subsec=#{act.subsec})" - end - elsif exp.class != act.class - # a subclass of Range, for example. - exp_comment = " (#{exp.class})" - act_comment = " (#{act.class})" - end - elsif !Encoding.compatible?(exp_str, act_str) - if exp.is_a?(String) && act.is_a?(String) - exp_str = exp.dump - act_str = act.dump - exp_comment = " (#{exp.encoding})" - act_comment = " (#{act.encoding})" - else - exp_str = exp_str.dump - act_str = act_str.dump - end - end - "<#{exp_str}>#{exp_comment} expected but was\n<#{act_str}>#{act_comment}" - } - assert(exp == act, msg) - end - - # :call-seq: - # assert_not_nil( expression, failure_message = nil ) - # - #Tests if +expression+ is not nil. - # - #An optional failure message may be provided as the final argument. - def assert_not_nil(exp, msg=nil) - msg = message(msg) { "<#{mu_pp(exp)}> expected to not be nil" } - assert(!exp.nil?, msg) - end - - # :call-seq: - # assert_not_equal( expected, actual, failure_message = nil ) - # - #Tests if +expected+ is not equal to +actual+. - # - #An optional failure message may be provided as the final argument. - def assert_not_equal(exp, act, msg=nil) - msg = message(msg) { "<#{mu_pp(exp)}> expected to be != to\n<#{mu_pp(act)}>" } - assert(exp != act, msg) - end - - # :call-seq: - # assert_no_match( regexp, string, failure_message = nil ) - # - #Tests if the given Regexp does not match a given String. - # - #An optional failure message may be provided as the final argument. - def assert_no_match(regexp, string, msg=nil) - assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.") - self._assertions -= 1 - msg = message(msg) { "<#{mu_pp(regexp)}> expected to not match\n<#{mu_pp(string)}>" } - assert(regexp !~ string, msg) - end - - # :call-seq: - # assert_not_same( expected, actual, failure_message = nil ) - # - #Tests if +expected+ is not the same object as +actual+. - #This test uses Object#equal? to test equality. - # - #An optional failure message may be provided as the final argument. - # - # assert_not_same("x", "x") #Succeeds - def assert_not_same(expected, actual, message="") - msg = message(msg) { build_message(message, < -with id expected to not be equal\\? to - -with id . -EOT - assert(!actual.equal?(expected), msg) - end - - # :call-seq: - # assert_respond_to( object, method, failure_message = nil ) - # - #Tests if the given Object responds to +method+. - # - #An optional failure message may be provided as the final argument. - # - # assert_respond_to("hello", :reverse) #Succeeds - # assert_respond_to("hello", :does_not_exist) #Fails - def assert_respond_to(obj, (meth, *priv), msg = nil) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" - } - return assert obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) - return if obj.respond_to?(meth) - end - super(obj, meth, msg) - end - - # :call-seq: - # assert_not_respond_to( object, method, failure_message = nil ) - # - #Tests if the given Object does not respond to +method+. - # - #An optional failure message may be provided as the final argument. - # - # assert_not_respond_to("hello", :reverse) #Fails - # assert_not_respond_to("hello", :does_not_exist) #Succeeds - def assert_not_respond_to(obj, (meth, *priv), msg = nil) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" - } - return assert !obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) - return unless obj.respond_to?(meth) - end - refute_respond_to(obj, meth, msg) - end - - # :call-seq: - # assert_send( +send_array+, failure_message = nil ) - # - # Passes if the method send returns a true value. - # - # +send_array+ is composed of: - # * A receiver - # * A method - # * Arguments to the method - # - # Example: - # assert_send(["Hello world", :include?, "Hello"]) # -> pass - # assert_send(["Hello world", :include?, "Goodbye"]) # -> fail - def assert_send send_ary, m = nil - recv, msg, *args = send_ary - m = message(m) { - if args.empty? - argsstr = "" - else - (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') - end - "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return true" - } - assert recv.__send__(msg, *args), m - end - - # :call-seq: - # assert_not_send( +send_array+, failure_message = nil ) - # - # Passes if the method send doesn't return a true value. - # - # +send_array+ is composed of: - # * A receiver - # * A method - # * Arguments to the method - # - # Example: - # assert_not_send([[1, 2], :member?, 1]) # -> fail - # assert_not_send([[1, 2], :member?, 4]) # -> pass - def assert_not_send send_ary, m = nil - recv, msg, *args = send_ary - m = message(m) { - if args.empty? - argsstr = "" - else - (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') - end - "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return false" - } - assert !recv.__send__(msg, *args), m - end - - ms = instance_methods(true).map {|sym| sym.to_s } - ms.grep(/\Arefute_/) do |m| - mname = ('assert_not_'.dup << m.to_s[/.*?_(.*)/, 1]) - alias_method(mname, m) unless ms.include? mname - end - alias assert_include assert_includes - alias assert_not_include assert_not_includes - - def assert_all?(obj, m = nil, &blk) - failed = [] - obj.each do |*a, &b| - unless blk.call(*a, &b) - failed << (a.size > 1 ? a : a[0]) - end - end - assert(failed.empty?, message(m) {failed.pretty_inspect}) - end - - def assert_not_all?(obj, m = nil, &blk) - failed = [] - obj.each do |*a, &b| - if blk.call(*a, &b) - failed << a.size > 1 ? a : a[0] - end - end - assert(failed.empty?, message(m) {failed.pretty_inspect}) - end - - # compatibility with test-unit - alias pend skip - - if defined?(RubyVM::InstructionSequence) - def syntax_check(code, fname, line) - code = code.dup.force_encoding(Encoding::UTF_8) - RubyVM::InstructionSequence.compile(code, fname, fname, line) - :ok - end - else - def syntax_check(code, fname, line) - code = code.b - code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { - "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" - } - code = code.force_encoding(Encoding::UTF_8) - catch {|tag| eval(code, binding, fname, line - 1)} - end - end - - def prepare_syntax_check(code, fname = caller_locations(2, 1)[0], mesg = fname.to_s, verbose: nil) - verbose, $VERBOSE = $VERBOSE, verbose - case - when Array === fname - fname, line = *fname - when defined?(fname.path) && defined?(fname.lineno) - fname, line = fname.path, fname.lineno - else - line = 1 - end - yield(code, fname, line, mesg) - ensure - $VERBOSE = verbose - end - - def assert_valid_syntax(code, *args) - prepare_syntax_check(code, *args) do |src, fname, line, mesg| - yield if defined?(yield) - assert_nothing_raised(SyntaxError, mesg) do - assert_equal(:ok, syntax_check(src, fname, line), mesg) - end - end - end - - def assert_syntax_error(code, error, *args) - prepare_syntax_check(code, *args) do |src, fname, line, mesg| - yield if defined?(yield) - e = assert_raise(SyntaxError, mesg) do - syntax_check(src, fname, line) - end - assert_match(error, e.message, mesg) - e - end - end - - def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) - assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) - if child_env - child_env = [child_env] - else - child_env = [] - end - out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) - assert !status.signaled?, FailDesc[status, message, out] - end - - FailDesc = proc do |status, message = "", out = ""| - pid = status.pid - now = Time.now - faildesc = proc do - if signo = status.termsig - signame = Signal.signame(signo) - sigdesc = "signal #{signo}" - end - log = EnvUtil.diagnostic_reports(signame, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc = "#{sigdesc} (core dumped)" - end - full_message = ''.dup - message = message.call if Proc === message - if message and !message.empty? - full_message << message << "\n" - end - full_message << "pid #{pid}" - full_message << " exit #{status.exitstatus}" if status.exited? - full_message << " killed by #{sigdesc}" if sigdesc - if out and !out.empty? - full_message << "\n" << out.b.gsub(/^/, '| ') - full_message.sub!(/(? marshal_error - ignore_stderr = nil - end - if res - if bt = res.backtrace - bt.each do |l| - l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} - end - bt.concat(caller) - else - res.set_backtrace(caller) - end - raise res unless SystemExit === res - end - - # really is it succeed? - unless ignore_stderr - # the body of assert_separately must not output anything to detect error - assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) - end - assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) - raise marshal_error if marshal_error - end - - def assert_warning(pat, msg = nil) - stderr = EnvUtil.verbose_warning { - EnvUtil.with_default_internal(pat.encoding) { - yield - } - } - msg = message(msg) {diff pat, stderr} - assert(pat === stderr, msg) - end - - def assert_warn(*args) - assert_warning(*args) {$VERBOSE = false; yield} - end - - def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) - require_relative '../../memory_status' - raise MiniTest::Skip, "unsupported platform" unless defined?(Memory::Status) - - token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" - token_dump = token.dump - token_re = Regexp.quote(token) - envs = args.shift if Array === args and Hash === args.first - args = [ - "--disable=gems", - "-r", File.expand_path("../../../memory_status", __FILE__), - *args, - "-v", "-", - ] - if defined? Memory::NO_MEMORY_LEAK_ENVS then - envs ||= {} - newenvs = envs.merge(Memory::NO_MEMORY_LEAK_ENVS) { |_, _, _| break } - envs = newenvs if newenvs - end - args.unshift(envs) if envs - cmd = [ - 'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}', - prepare, - 'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")', - '$initial_size = $initial_status.size', - code, - 'GC.start', - ].join("\n") - _, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt) - before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1) - after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1) - assert(status.success?, FailDesc[status, message, err]) - ([:size, (rss && :rss)] & after.members).each do |n| - b = before[n] - a = after[n] - next unless a > 0 and b > 0 - assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) - end - rescue LoadError - skip - end - - def assert_cpu_usage_low(msg = nil, pct: 0.01) - require 'benchmark' - - tms = Benchmark.measure(msg || '') { yield } - max = pct * tms.real - if tms.real < 0.1 # TIME_QUANTUM_USEC in thread_pthread.c - warn "test #{msg || 'assert_cpu_usage_low'} too short to be accurate" - end - - # kernel resolution can limit the minimum time we can measure - # [ruby-core:81540] - min_hz = windows? ? 67 : 100 - min_measurable = 1.0 / min_hz - min_measurable *= 1.10 # add a little (10%) to account for misc. overheads - if max < min_measurable - max = min_measurable - end - - assert_operator tms.total, :<=, max, msg - end - - def assert_is_minus_zero(f) - assert(1.0/f == -Float::INFINITY, "#{f} is not -0.0") - end - - def assert_file - AssertFile - end - - # pattern_list is an array which contains regexp and :*. - # :* means any sequence. - # - # pattern_list is anchored. - # Use [:*, regexp, :*] for non-anchored match. - def assert_pattern_list(pattern_list, actual, message=nil) - rest = actual - anchored = true - pattern_list.each_with_index {|pattern, i| - if pattern == :* - anchored = false - else - if anchored - match = /\A#{pattern}/.match(rest) - else - match = pattern.match(rest) - end - unless match - msg = message(msg) { - expect_msg = "Expected #{mu_pp pattern}\n" - if /\n[^\n]/ =~ rest - actual_mesg = "to match\n" - rest.scan(/.*\n+/) { - actual_mesg << ' ' << $&.inspect << "+\n" - } - actual_mesg.sub!(/\+\n\z/, '') - else - actual_mesg = "to match #{mu_pp rest}" - end - actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" - expect_msg + actual_mesg - } - assert false, msg - end - rest = match.post_match - anchored = true - end - } - if anchored - assert_equal("", rest) - end - end - - # threads should respond to shift method. - # Array can be used. - def assert_join_threads(threads, message = nil) - errs = [] - values = [] - while th = threads.shift - begin - values << th.value - rescue Exception - errs << [th, $!] - end - end - if !errs.empty? - msg = "exceptions on #{errs.length} threads:\n" + - errs.map {|t, err| - "#{t.inspect}:\n" + - err.backtrace.map.with_index {|line, i| - if i == 0 - "#{line}: #{err.message} (#{err.class})" - else - "\tfrom #{line}" - end - }.join("\n") - }.join("\n---\n") - if message - msg = "#{message}\n#{msg}" - end - raise MiniTest::Assertion, msg - end - values - end - - class << (AssertFile = Struct.new(:failure_message).new) - include Assertions - def assert_file_predicate(predicate, *args) - if /\Anot_/ =~ predicate - predicate = $' - neg = " not" - end - result = File.__send__(predicate, *args) - result = !result if neg - mesg = "Expected file ".dup << args.shift.inspect - mesg << "#{neg} to be #{predicate}" - mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? - mesg << " #{failure_message}" if failure_message - assert(result, mesg) - end - alias method_missing assert_file_predicate - - def for(message) - clone.tap {|a| a.failure_message = message} - end - end - - class AllFailures - attr_reader :failures - - def initialize - @count = 0 - @failures = {} - end - - def for(key) - @count += 1 - yield - rescue Exception => e - @failures[key] = [@count, e] - end - - def foreach(*keys) - keys.each do |key| - @count += 1 - begin - yield key - rescue Exception => e - @failures[key] = [@count, e] - end - end - end - - def message - i = 0 - total = @count.to_s - fmt = "%#{total.size}d" - @failures.map {|k, (n, v)| - "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.message.b.gsub(/^/, ' | ')}" - }.join("\n") - end - - def pass? - @failures.empty? - end - end - - def assert_all_assertions(msg = nil) - all = AllFailures.new - yield all - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions assert_all_assertions - - def assert_all_assertions_foreach(msg = nil, *keys, &block) - all = AllFailures.new - all.foreach(*keys, &block) - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions_foreach assert_all_assertions_foreach - - def build_message(head, template=nil, *arguments) #:nodoc: - template &&= template.chomp - template.gsub(/\G((?:[^\\]|\\.)*?)(\\)?\?/) { $1 + ($2 ? "?" : mu_pp(arguments.shift)) } - end - - def message(msg = nil, *args, &default) # :nodoc: - if Proc === msg - super(nil, *args) do - ary = [msg.call, (default.call if default)].compact.reject(&:empty?) - if 1 < ary.length - ary[0...-1] = ary[0...-1].map {|str| str.sub(/(? e - begin - trace = e.backtrace || ['unknown method'] - err = ["#{trace.shift}: #{e.message} (#{e.class})"] + trace.map{|t| t.prepend("\t") } - - _report "bye", Marshal.dump(err.join("\n")) - rescue Errno::EPIPE;end - exit - ensure - @stdin.close if @stdin - @stdout.close if @stdout - end - end - - def _report(res, *args) # :nodoc: - res = "#{res} #{args.pack("m0")}" unless args.empty? - @stdout.puts(res) - end - - def puke(klass, meth, e) # :nodoc: - if e.is_a?(MiniTest::Skip) - new_e = MiniTest::Skip.new(e.message) - new_e.set_backtrace(e.backtrace) - e = new_e - end - @partial_report << [klass.name, meth, e.is_a?(MiniTest::Assertion) ? e : ProxyError.new(e)] - super - end - end - end -end - -if $0 == __FILE__ - module Test - module Unit - class TestCase < MiniTest::Unit::TestCase # :nodoc: all - undef on_parallel_worker? - def on_parallel_worker? - true - end - end - end - end - require 'rubygems' - module Gem # :nodoc: - end - class Gem::TestCase < MiniTest::Unit::TestCase # :nodoc: - @@project_dir = File.expand_path('../../../../..', __FILE__) - end - - Test::Unit::Worker.new.run(ARGV) -end diff --git a/test/lib/test/unit/testcase.rb b/test/lib/test/unit/testcase.rb deleted file mode 100644 index 10348b5c..00000000 --- a/test/lib/test/unit/testcase.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: false -require 'test/unit/assertions' - -module Test - module Unit - # remove silly TestCase class - remove_const(:TestCase) if defined?(self::TestCase) - - class TestCase < MiniTest::Unit::TestCase # :nodoc: all - include Assertions - - def on_parallel_worker? - false - end - - def run runner - @options = runner.options - super runner - end - - def self.test_order - :sorted - end - - def self.method_added(name) - super - return unless name.to_s.start_with?("test_") - @test_methods ||= {} - if @test_methods[name] - warn "test/unit warning: method #{ self }##{ name } is redefined" - end - @test_methods[name] = true - end - end - end -end From 4e7990234487bdc644d6470e828ddfc3621beb08 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 2 Mar 2021 21:29:42 +0900 Subject: [PATCH 212/546] Use Gemfile instead of Gem::Specification#add_development_dependency. --- Gemfile | 8 +++++++- bigdecimal.gemspec | 7 ------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index ed7382e0..6509f203 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,10 @@ source 'https://rubygems.org' -# Specify your gem's dependencies in bigdecimal.gemspec gemspec + +gem "benchmark_driver" +gem "fiddle" +gem "rake", ">= 12.3.3" +gem "rake-compiler", ">= 0.9" +gem "minitest", "< 5.0.0" +gem "irb" diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 2e017eee..36ff65a9 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -37,11 +37,4 @@ Gem::Specification.new do |s| ] s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") - - s.add_development_dependency "benchmark_driver" - s.add_development_dependency "fiddle" - s.add_development_dependency "rake", ">= 12.3.3" - s.add_development_dependency "rake-compiler", ">= 0.9" - s.add_development_dependency "minitest", "< 5.0.0" - s.add_development_dependency "irb" end From 0e5b65b05adc00b298dcf1982e3bf0ae25b54382 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 2 Mar 2021 21:30:11 +0900 Subject: [PATCH 213/546] Added test-unit --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 6509f203..06413ee9 100644 --- a/Gemfile +++ b/Gemfile @@ -8,3 +8,4 @@ gem "rake", ">= 12.3.3" gem "rake-compiler", ">= 0.9" gem "minitest", "< 5.0.0" gem "irb" +gem "test-unit" From 7877eaad69b90c3e50dc36789841cd6ef3cf7361 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 2 Mar 2021 21:54:16 +0900 Subject: [PATCH 214/546] Port the minimum test libraries from ruby/ruby --- Rakefile | 8 + test/lib/core_assertions.rb | 763 ++++++++++++++++++++++++++++++++++++ test/lib/envutil.rb | 365 +++++++++++++++++ test/lib/find_executable.rb | 22 ++ test/lib/helper.rb | 4 + 5 files changed, 1162 insertions(+) create mode 100644 test/lib/core_assertions.rb create mode 100644 test/lib/envutil.rb create mode 100644 test/lib/find_executable.rb create mode 100644 test/lib/helper.rb diff --git a/Rakefile b/Rakefile index f4a67197..2a2f4da9 100644 --- a/Rakefile +++ b/Rakefile @@ -10,6 +10,7 @@ Rake::ExtensionTask.new('bigdecimal', spec) Rake::TestTask.new do |t| t.libs << 'test/lib' + t.ruby_opts << '-rhelper' t.test_files = FileList['test/bigdecimal/**/test_*.rb'] t.warning = true end @@ -41,3 +42,10 @@ end desc "Run all benchmarks" task benchmark: benchmark_tasks + +task :sync_tool do + require 'fileutils' + FileUtils.cp "../ruby/tool/lib/test/unit/core_assertions.rb", "./test/lib" + FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib" + FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib" +end diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb new file mode 100644 index 00000000..118c0d11 --- /dev/null +++ b/test/lib/core_assertions.rb @@ -0,0 +1,763 @@ +# frozen_string_literal: true + +module Test + module Unit + module Assertions + def _assertions= n # :nodoc: + @_assertions = n + end + + def _assertions # :nodoc: + @_assertions ||= 0 + end + + ## + # Returns a proc that will output +msg+ along with the default message. + + def message msg = nil, ending = nil, &default + proc { + msg = msg.call.chomp(".") if Proc === msg + custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty? + "#{custom_message}#{default.call}#{ending || "."}" + } + end + end + + module CoreAssertions + if defined?(MiniTest) + require_relative '../../envutil' + # for ruby core testing + include MiniTest::Assertions + + # Compatibility hack for assert_raise + Test::Unit::AssertionFailedError = MiniTest::Assertion + else + module MiniTest + class Assertion < Exception; end + class Skip < Assertion; end + end + + require 'pp' + require_relative 'envutil' + include Test::Unit::Assertions + end + + def mu_pp(obj) #:nodoc: + obj.pretty_inspect.chomp + end + + def assert_file + AssertFile + end + + FailDesc = proc do |status, message = "", out = ""| + now = Time.now + proc do + EnvUtil.failure_description(status, now, message, out) + end + end + + def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, + success: nil, **opt) + args = Array(args).dup + args.insert((Hash === args[0] ? 1 : 0), '--disable=gems') + stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) + desc = FailDesc[status, message, stderr] + if block_given? + raise "test_stdout ignored, use block only or without block" if test_stdout != [] + raise "test_stderr ignored, use block only or without block" if test_stderr != [] + yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp }, status) + else + all_assertions(desc) do |a| + [["stdout", test_stdout, stdout], ["stderr", test_stderr, stderr]].each do |key, exp, act| + a.for(key) do + if exp.is_a?(Regexp) + assert_match(exp, act) + elsif exp.all? {|e| String === e} + assert_equal(exp, act.lines.map {|l| l.chomp }) + else + assert_pattern_list(exp, act) + end + end + end + unless success.nil? + a.for("success?") do + if success + assert_predicate(status, :success?) + else + assert_not_predicate(status, :success?) + end + end + end + end + status + end + end + + if defined?(RubyVM::InstructionSequence) + def syntax_check(code, fname, line) + code = code.dup.force_encoding(Encoding::UTF_8) + RubyVM::InstructionSequence.compile(code, fname, fname, line) + :ok + ensure + raise if SyntaxError === $! + end + else + def syntax_check(code, fname, line) + code = code.b + code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { + "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" + } + code = code.force_encoding(Encoding::UTF_8) + catch {|tag| eval(code, binding, fname, line - 1)} + end + end + + def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) + # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail + pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? + + require_relative '../../memory_status' + raise MiniTest::Skip, "unsupported platform" unless defined?(Memory::Status) + + token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" + token_dump = token.dump + token_re = Regexp.quote(token) + envs = args.shift if Array === args and Hash === args.first + args = [ + "--disable=gems", + "-r", File.expand_path("../../../memory_status", __FILE__), + *args, + "-v", "-", + ] + if defined? Memory::NO_MEMORY_LEAK_ENVS then + envs ||= {} + newenvs = envs.merge(Memory::NO_MEMORY_LEAK_ENVS) { |_, _, _| break } + envs = newenvs if newenvs + end + args.unshift(envs) if envs + cmd = [ + 'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}', + prepare, + 'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")', + '$initial_size = $initial_status.size', + code, + 'GC.start', + ].join("\n") + _, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt) + before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1) + after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1) + assert(status.success?, FailDesc[status, message, err]) + ([:size, (rss && :rss)] & after.members).each do |n| + b = before[n] + a = after[n] + next unless a > 0 and b > 0 + assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) + end + rescue LoadError + pend + end + + # :call-seq: + # assert_nothing_raised( *args, &block ) + # + #If any exceptions are given as arguments, the assertion will + #fail if one of those exceptions are raised. Otherwise, the test fails + #if any exceptions are raised. + # + #The final argument may be a failure message. + # + # assert_nothing_raised RuntimeError do + # raise Exception #Assertion passes, Exception is not a RuntimeError + # end + # + # assert_nothing_raised do + # raise Exception #Assertion fails + # end + def assert_nothing_raised(*args) + self._assertions += 1 + if Module === args.last + msg = nil + else + msg = args.pop + end + begin + line = __LINE__; yield + rescue MiniTest::Skip + raise + rescue Exception => e + bt = e.backtrace + as = e.instance_of?(MiniTest::Assertion) + if as + ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o + bt.reject! {|ln| ans =~ ln} + end + if ((args.empty? && !as) || + args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a }) + msg = message(msg) { + "Exception raised:\n<#{mu_pp(e)}>\n" + + "Backtrace:\n" + + e.backtrace.map{|frame| " #{frame}"}.join("\n") + } + raise MiniTest::Assertion, msg.call, bt + else + raise + end + end + end + + def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil) + fname ||= caller_locations(2, 1)[0] + mesg ||= fname.to_s + verbose, $VERBOSE = $VERBOSE, verbose + case + when Array === fname + fname, line = *fname + when defined?(fname.path) && defined?(fname.lineno) + fname, line = fname.path, fname.lineno + else + line = 1 + end + yield(code, fname, line, message(mesg) { + if code.end_with?("\n") + "```\n#{code}```\n" + else + "```\n#{code}\n```\n""no-newline" + end + }) + ensure + $VERBOSE = verbose + end + + def assert_valid_syntax(code, *args, **opt) + prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg| + yield if defined?(yield) + assert_nothing_raised(SyntaxError, mesg) do + assert_equal(:ok, syntax_check(src, fname, line), mesg) + end + end + end + + def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) + assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) + if child_env + child_env = [child_env] + else + child_env = [] + end + out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) + assert !status.signaled?, FailDesc[status, message, out] + end + + def assert_ruby_status(args, test_stdin="", message=nil, **opt) + out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt) + desc = FailDesc[status, message, out] + assert(!status.signaled?, desc) + message ||= "ruby exit status is not success:" + assert(status.success?, desc) + end + + ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") + + def separated_runner(out = nil) + out = out ? IO.new(out, 'w') : STDOUT + at_exit { + out.puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" + } + Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner) + end + + def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) + unless file and line + loc, = caller_locations(1,1) + file ||= loc.path + line ||= loc.lineno + end + capture_stdout = true + unless /mswin|mingw/ =~ RUBY_PLATFORM + capture_stdout = false + opt[:out] = MiniTest::Unit.output if defined?(MiniTest::Unit) + res_p, res_c = IO.pipe + opt[res_c.fileno] = res_c.fileno + end + src = < marshal_error + ignore_stderr = nil + res = nil + end + if res and !(SystemExit === res) + if bt = res.backtrace + bt.each do |l| + l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} + end + bt.concat(caller) + else + res.set_backtrace(caller) + end + raise res + end + + # really is it succeed? + unless ignore_stderr + # the body of assert_separately must not output anything to detect error + assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) + end + assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) + raise marshal_error if marshal_error + end + + # Run Ractor-related test without influencing the main test suite + def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt) + return unless defined?(Ractor) + + require = "require #{require.inspect}" if require + if require_relative + dir = File.dirname(caller_locations[0,1][0].absolute_path) + full_path = File.expand_path(require_relative, dir) + require = "#{require}; require #{full_path.inspect}" + end + + assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt) + #{require} + previous_verbose = $VERBOSE + $VERBOSE = nil + Ractor.new {} # trigger initial warning + $VERBOSE = previous_verbose + #{src} + RUBY + end + + # :call-seq: + # assert_throw( tag, failure_message = nil, &block ) + # + #Fails unless the given block throws +tag+, returns the caught + #value otherwise. + # + #An optional failure message may be provided as the final argument. + # + # tag = Object.new + # assert_throw(tag, "#{tag} was not thrown!") do + # throw tag + # end + def assert_throw(tag, msg = nil) + ret = catch(tag) do + begin + yield(tag) + rescue UncaughtThrowError => e + thrown = e.tag + end + msg = message(msg) { + "Expected #{mu_pp(tag)} to have been thrown"\ + "#{%Q[, not #{thrown}] if thrown}" + } + assert(false, msg) + end + assert(true) + ret + end + + # :call-seq: + # assert_raise( *args, &block ) + # + #Tests if the given block raises an exception. Acceptable exception + #types may be given as optional arguments. If the last argument is a + #String, it will be used as the error message. + # + # assert_raise do #Fails, no Exceptions are raised + # end + # + # assert_raise NameError do + # puts x #Raises NameError, so assertion succeeds + # end + def assert_raise(*exp, &b) + case exp.last + when String, Proc + msg = exp.pop + end + + begin + yield + rescue MiniTest::Skip => e + return e if exp.include? MiniTest::Skip + raise e + rescue Exception => e + expected = exp.any? { |ex| + if ex.instance_of? Module then + e.kind_of? ex + else + e.instance_of? ex + end + } + + assert expected, proc { + flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"}) + } + + return e + ensure + unless e + exp = exp.first if exp.size == 1 + + flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) + end + end + end + + # :call-seq: + # assert_raise_with_message(exception, expected, msg = nil, &block) + # + #Tests if the given block raises an exception with the expected + #message. + # + # assert_raise_with_message(RuntimeError, "foo") do + # nil #Fails, no Exceptions are raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise ArgumentError, "foo" #Fails, different Exception is raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "bar" #Fails, RuntimeError is raised but the message differs + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "foo" #Raises RuntimeError with the message, so assertion succeeds + # end + def assert_raise_with_message(exception, expected, msg = nil, &block) + case expected + when String + assert = :assert_equal + when Regexp + assert = :assert_match + else + raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" + end + + ex = m = nil + EnvUtil.with_default_internal(expected.encoding) do + ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do + yield + end + m = ex.message + end + msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} + + if assert == :assert_equal + assert_equal(expected, m, msg) + else + msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } + assert expected =~ m, msg + block.binding.eval("proc{|_|$~=_}").call($~) + end + ex + end + + MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: + + # :call-seq: + # assert(test, [failure_message]) + # + #Tests if +test+ is true. + # + #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used + #as the failure message. Otherwise, the result of calling +msg+ will be + #used as the message if the assertion fails. + # + #If no +msg+ is given, a default message will be used. + # + # assert(false, "This was expected to be true") + def assert(test, *msgs) + case msg = msgs.first + when String, Proc + when nil + msgs.shift + else + bt = caller.reject { |s| s.start_with?(MINI_DIR) } + raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt + end unless msgs.empty? + super + end + + # :call-seq: + # assert_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object responds to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_respond_to("hello", :reverse) #Succeeds + # assert_respond_to("hello", :does_not_exist) #Fails + def assert_respond_to(obj, (meth, *priv), msg = nil) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" + } + return assert obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + return if obj.respond_to?(meth) + end + super(obj, meth, msg) + end + + # :call-seq: + # assert_not_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object does not respond to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_not_respond_to("hello", :reverse) #Fails + # assert_not_respond_to("hello", :does_not_exist) #Succeeds + def assert_not_respond_to(obj, (meth, *priv), msg = nil) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" + } + return assert !obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + return unless obj.respond_to?(meth) + end + refute_respond_to(obj, meth, msg) + end + + # pattern_list is an array which contains regexp and :*. + # :* means any sequence. + # + # pattern_list is anchored. + # Use [:*, regexp, :*] for non-anchored match. + def assert_pattern_list(pattern_list, actual, message=nil) + rest = actual + anchored = true + pattern_list.each_with_index {|pattern, i| + if pattern == :* + anchored = false + else + if anchored + match = /\A#{pattern}/.match(rest) + else + match = pattern.match(rest) + end + unless match + msg = message(msg) { + expect_msg = "Expected #{mu_pp pattern}\n" + if /\n[^\n]/ =~ rest + actual_mesg = +"to match\n" + rest.scan(/.*\n+/) { + actual_mesg << ' ' << $&.inspect << "+\n" + } + actual_mesg.sub!(/\+\n\z/, '') + else + actual_mesg = "to match " + mu_pp(rest) + end + actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" + expect_msg + actual_mesg + } + assert false, msg + end + rest = match.post_match + anchored = true + end + } + if anchored + assert_equal("", rest) + end + end + + def assert_warning(pat, msg = nil) + result = nil + stderr = EnvUtil.with_default_internal(pat.encoding) { + EnvUtil.verbose_warning { + result = yield + } + } + msg = message(msg) {diff pat, stderr} + assert(pat === stderr, msg) + result + end + + def assert_warn(*args) + assert_warning(*args) {$VERBOSE = false; yield} + end + + def assert_deprecated_warning(mesg = /deprecated/) + assert_warning(mesg) do + Warning[:deprecated] = true + yield + end + end + + def assert_deprecated_warn(mesg = /deprecated/) + assert_warn(mesg) do + Warning[:deprecated] = true + yield + end + end + + class << (AssertFile = Struct.new(:failure_message).new) + include CoreAssertions + def assert_file_predicate(predicate, *args) + if /\Anot_/ =~ predicate + predicate = $' + neg = " not" + end + result = File.__send__(predicate, *args) + result = !result if neg + mesg = "Expected file ".dup << args.shift.inspect + mesg << "#{neg} to be #{predicate}" + mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? + mesg << " #{failure_message}" if failure_message + assert(result, mesg) + end + alias method_missing assert_file_predicate + + def for(message) + clone.tap {|a| a.failure_message = message} + end + end + + class AllFailures + attr_reader :failures + + def initialize + @count = 0 + @failures = {} + end + + def for(key) + @count += 1 + yield + rescue Exception => e + @failures[key] = [@count, e] + end + + def foreach(*keys) + keys.each do |key| + @count += 1 + begin + yield key + rescue Exception => e + @failures[key] = [@count, e] + end + end + end + + def message + i = 0 + total = @count.to_s + fmt = "%#{total.size}d" + @failures.map {|k, (n, v)| + v = v.message + "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}" + }.join("\n") + end + + def pass? + @failures.empty? + end + end + + # threads should respond to shift method. + # Array can be used. + def assert_join_threads(threads, message = nil) + errs = [] + values = [] + while th = threads.shift + begin + values << th.value + rescue Exception + errs << [th, $!] + th = nil + end + end + values + ensure + if th&.alive? + th.raise(Timeout::Error.new) + th.join rescue errs << [th, $!] + end + if !errs.empty? + msg = "exceptions on #{errs.length} threads:\n" + + errs.map {|t, err| + "#{t.inspect}:\n" + + RUBY_VERSION >= "2.5.0" ? err.full_message(highlight: false, order: :top) : err.message + }.join("\n---\n") + if message + msg = "#{message}\n#{msg}" + end + raise MiniTest::Assertion, msg + end + end + + def assert_all_assertions(msg = nil) + all = AllFailures.new + yield all + ensure + assert(all.pass?, message(msg) {all.message.chomp(".")}) + end + alias all_assertions assert_all_assertions + + def message(msg = nil, *args, &default) # :nodoc: + if Proc === msg + super(nil, *args) do + ary = [msg.call, (default.call if default)].compact.reject(&:empty?) + if 1 < ary.length + ary[0...-1] = ary[0...-1].map {|str| str.sub(/(? Date: Tue, 2 Mar 2021 22:06:56 +0900 Subject: [PATCH 215/546] use pend instead of skip --- test/bigdecimal/test_ractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb index f78663f1..97dce90a 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -6,7 +6,7 @@ class TestBigDecimalRactor < Test::Unit::TestCase def setup super - skip unless defined? Ractor + pend unless defined? Ractor end def test_ractor_shareable From 8192ef6d9d11142bdcba2e7ea7103cc301bfa353 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 8 Mar 2021 10:15:39 +0900 Subject: [PATCH 216/546] Update the latest version of CoreAssertions --- test/lib/core_assertions.rb | 3 ++- test/lib/envutil.rb | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index 118c0d11..ff78c2bd 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -260,6 +260,7 @@ def assert_ruby_status(args, test_stdin="", message=nil, **opt) ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") def separated_runner(out = nil) + include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) out = out ? IO.new(out, 'w') : STDOUT at_exit { out.puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" @@ -278,7 +279,7 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o capture_stdout = false opt[:out] = MiniTest::Unit.output if defined?(MiniTest::Unit) res_p, res_c = IO.pipe - opt[res_c.fileno] = res_c.fileno + opt[:ios] = [res_c] end src = < Date: Tue, 1 Jun 2021 21:32:16 +0900 Subject: [PATCH 217/546] Use omit_unless instead of pend unless --- test/bigdecimal/test_ractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb index 97dce90a..7ec7974e 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -6,7 +6,7 @@ class TestBigDecimalRactor < Test::Unit::TestCase def setup super - pend unless defined? Ractor + omit_unless(defined? Ractor) end def test_ractor_shareable From 19b81bd4fdbef1f3bd2607db9994aa0921ddddaf Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 1 Jun 2021 21:37:40 +0900 Subject: [PATCH 218/546] Drop to support Ruby 2.4. Because bigdecimal-3.0.0 is only support Ruby 2.5+ --- .github/workflows/ci.yml | 1 - bigdecimal.gemspec | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71833339..a2297f36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,6 @@ jobs: - 2.7 - 2.6 - 2.5 - - 2.4 - debug include: - { os: windows-latest , ruby: mingw } diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 36ff65a9..3b8f24dc 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -36,5 +36,5 @@ Gem::Specification.new do |s| sample/pi.rb ] - s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") + s.required_ruby_version = Gem::Requirement.new(">= 2.5.0") end From 7b6f5bfeb082db3024f6e4d71cfae1dfeded1fef Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 21 Sep 2021 14:21:15 +0900 Subject: [PATCH 219/546] Check the prerequisites of sync_tool task --- Rakefile | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 2a2f4da9..e8667eb2 100644 --- a/Rakefile +++ b/Rakefile @@ -45,7 +45,19 @@ task benchmark: benchmark_tasks task :sync_tool do require 'fileutils' - FileUtils.cp "../ruby/tool/lib/test/unit/core_assertions.rb", "./test/lib" - FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib" - FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib" + + sync_files = [ + "../ruby/tool/lib/test/unit/core_assertions.rb", + "../ruby/tool/lib/test/unit/core_assertions.rb", + "../ruby/tool/lib/envutil.rb" + ] + + unless sync_files.all? {|fn| File.file?(fn) } + $stderr.puts "ruby/ruby must be git-cloned at ../ruby before running `rake sync_tool` here." + abort + end + + sync_files.each do |file| + FileUtils.cp file, "./test/lib" + end end From 0bdf10d55d6453c40efc4473347164821866a481 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 21 Sep 2021 15:05:44 +0900 Subject: [PATCH 220/546] Version 3.1.0-dev --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 3b8f24dc..bb4610cb 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '3.0.1' +bigdecimal_version = '3.1.0.dev' Gem::Specification.new do |s| s.name = "bigdecimal" From 6d510e47bce2ba4dbff4c48c26ee8d5cd8de1758 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 6 Jul 2021 00:14:58 +0900 Subject: [PATCH 221/546] Fixed 'maybe_unused' attribute ``` ../../../src/ext/bigdecimal/bigdecimal.c:303:5: error: 'maybe_unused' attribute cannot be applied to types ENTER(1); ^ ``` --- ext/bigdecimal/bigdecimal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 704f0451..65e7c864 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -62,7 +62,11 @@ static ID id_eq; static ID id_half; /* MACRO's to guard objects from GC by keeping them in stack */ +#ifdef RBIMPL_ATTR_MAYBE_UNUSED +#define ENTER(n) RBIMPL_ATTR_MAYBE_UNUSED() volatile VALUE vStack[n];int iStack=0 +#else #define ENTER(n) volatile VALUE RB_UNUSED_VAR(vStack[n]);int iStack=0 +#endif #define PUSH(x) (vStack[iStack++] = (VALUE)(x)) #define SAVE(p) PUSH((p)->obj) #define GUARD_OBJ(p,y) ((p)=(y), SAVE(p)) From 4f1f6cb4f5e63ba6c1c121bf5bfe6191a627cc0a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 29 Apr 2021 15:12:44 +0200 Subject: [PATCH 222/546] Fix -Wundef warnings for patterns `#if HAVE` * See [Feature #17752] * Using this to detect them: git grep -P 'if\s+HAVE' | grep -Pv 'HAVE_LONG_LONG|/ChangeLog|HAVE_TYPEOF' --- ext/bigdecimal/missing.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index 11b58c09..79698491 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -45,7 +45,7 @@ extern "C" { # if __has_builtin(__builtin_unreachable) # define UNREACHABLE __builtin_unreachable() -# elif HAVE___ASSUME +# elif defined(HAVE___ASSUME) # define UNREACHABLE __assume(0) # else From 13b077c63c8a982d72fb7e666b481257561890c8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 22 Oct 2021 15:46:31 +0900 Subject: [PATCH 223/546] Use omit instead of omit_unless --- test/bigdecimal/test_ractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb index 7ec7974e..798cc494 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -6,7 +6,7 @@ class TestBigDecimalRactor < Test::Unit::TestCase def setup super - omit_unless(defined? Ractor) + omit unless defined? Ractor end def test_ractor_shareable From 159af10b17d6524f29511c5dedf49ea05fa8889c Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Fri, 15 Oct 2021 14:42:49 +0200 Subject: [PATCH 224/546] VpCheckException: improve grammar I added a space before the parenthesis, too. --- ext/bigdecimal/bigdecimal.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 65e7c864..ab3d8d6b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -169,13 +169,13 @@ static void VpCheckException(Real *p, bool always) { if (VpIsNaN(p)) { - VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'(Not a Number)", always); + VpException(VP_EXCEPTION_NaN, "Computation results in 'NaN' (Not a Number)", always); } else if (VpIsPosInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", always); + VpException(VP_EXCEPTION_INFINITY, "Computation results in 'Infinity'", always); } else if (VpIsNegInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", always); + VpException(VP_EXCEPTION_INFINITY, "Computation results in '-Infinity'", always); } } From a834eb92a28c9d411aee28b78926bc7e394df298 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 27 Oct 2021 17:29:40 +0900 Subject: [PATCH 225/546] Fix test against #196 --- test/bigdecimal/test_bigdecimal.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 49e6b2a7..e860c9d1 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -948,8 +948,8 @@ def test_div BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, false) - assert_raise_with_message(FloatDomainError, "Computation results to 'Infinity'") { BigDecimal("1") / 0 } - assert_raise_with_message(FloatDomainError, "Computation results to '-Infinity'") { BigDecimal("-1") / 0 } + assert_raise_with_message(FloatDomainError, "Computation results in 'Infinity'") { BigDecimal("1") / 0 } + assert_raise_with_message(FloatDomainError, "Computation results in '-Infinity'") { BigDecimal("-1") / 0 } end def test_div_with_float From bdf2364a364db6c46b86bf00c4aab715b50d8fb8 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 27 Oct 2021 17:51:21 +0900 Subject: [PATCH 226/546] CI: Stop gem install on ruby-debug, tentatively --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2297f36..e21739c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,3 +52,4 @@ jobs: - run: rake build - run: gem install pkg/*.gem + if: ${{ matrix.ruby != 'debug' }} From b5830045298b4ea004358da9b57ea849f8c990bd Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 9 Nov 2021 13:18:14 +0100 Subject: [PATCH 227/546] CI: Quote 3.0 to avoid YAML Float-to-String issue This helps the output of the "name" of the CI Job in the PR checks list not to say "3" when it meant "3.0". --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e21739c5..a8e603f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - macos-10.15 - windows-latest ruby: - - 3.0 + - "3.0" # Quoted to avoid 3.0 becoming "3" as a String. - 2.7 - 2.6 - 2.5 From 0f3d5d0eb7df3dd278078718c801aa15d181f51a Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 9 Nov 2021 12:56:45 +0100 Subject: [PATCH 228/546] Fix negative Bignum conversion Introduced in 4792a917d806ca1059c952f489413073ea51bf01 `rb_absint_size` return the number of bytes needed to fit the absolute integer, but negative integers need the sign, so one more bit, and potentially one more byte. --- ext/bigdecimal/bigdecimal.c | 6 +++++- test/bigdecimal/test_bigdecimal.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index ab3d8d6b..1d829132 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2770,8 +2770,12 @@ rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_ex { assert(RB_TYPE_P(val, T_BIGNUM)); - size_t size = rb_absint_size(val, NULL); + int leading_zeros; + size_t size = rb_absint_size(val, &leading_zeros); int sign = FIX2INT(rb_big_cmp(val, INT2FIX(0))); + if (sign < 0 && leading_zeros == 0) { + size += 1; + } if (size <= sizeof(long)) { if (sign < 0) { return rb_int64_convert_to_BigDecimal(NUM2LONG(val), digs, raise_exception); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e860c9d1..8d4d2353 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2089,6 +2089,16 @@ def test_initialize_copy_dup_clone_frozen_error assert_raise(err) { bd.send(:initialize_dup, bd2) } end + def test_llong_min + # https://github.com/ruby/bigdecimal/issues/199 + # Between LLONG_MIN and -ULLONG_MAX + llong_min = -(2 ** 63 + 1) + assert_equal BigDecimal(llong_min.to_s), BigDecimal(llong_min) + + minus_ullong_max = -(2 ** 64 - 1) + assert_equal BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max) + end + def assert_no_memory_leak(code, *rest, **opt) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" super(["-rbigdecimal"], From aa31ef2f33e9710bf73399c8a5d84ac2929f3dce Mon Sep 17 00:00:00 2001 From: Kenta Murata <3959+mrkn@users.noreply.github.com> Date: Fri, 12 Nov 2021 23:08:53 +0900 Subject: [PATCH 229/546] Fix the style in test/bigdecimal/test_bigdecimal.rb --- test/bigdecimal/test_bigdecimal.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 8d4d2353..3cc851ad 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2089,14 +2089,14 @@ def test_initialize_copy_dup_clone_frozen_error assert_raise(err) { bd.send(:initialize_dup, bd2) } end - def test_llong_min + def test_llong_min_gh_200 # https://github.com/ruby/bigdecimal/issues/199 # Between LLONG_MIN and -ULLONG_MAX llong_min = -(2 ** 63 + 1) - assert_equal BigDecimal(llong_min.to_s), BigDecimal(llong_min) + assert_equal(BigDecimal(llong_min.to_s), BigDecimal(llong_min), "[GH-200]") minus_ullong_max = -(2 ** 64 - 1) - assert_equal BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max) + assert_equal(BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max), "[GH-200]") end def assert_no_memory_leak(code, *rest, **opt) From 14e35f5a70919d82a45041bbfc539497b73a34a1 Mon Sep 17 00:00:00 2001 From: Kenta Murata <3959+mrkn@users.noreply.github.com> Date: Fri, 12 Nov 2021 23:18:27 +0900 Subject: [PATCH 230/546] Use values in RbConfig::LIMITS in test --- test/bigdecimal/test_bigdecimal.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 3cc851ad..d7b245b0 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -12,7 +12,12 @@ class TestBigDecimal < Test::Unit::TestCase require 'fiddle' LONG_MAX = (1 << (Fiddle::SIZEOF_LONG*8 - 1)) - 1 LONG_MIN = [LONG_MAX + 1].pack("L!").unpack("l!")[0] + LLONG_MAX = (1 << (Fiddle::SIZEOF_LONG_LONG*8 - 1)) - 1 + LLONG_MIN = [LLONG_MAX + 1].pack("Q!").unpack("q!")[0] + ULLONG_MAX = (1 << Fiddle::SIZEOF_LONG_LONG*8) - 1 LIMITS = { + "LLONG_MIN" => LLONG_MIN, + "ULLONG_MAX" => ULLONG_MAX, "FIXNUM_MIN" => LONG_MIN / 2, "FIXNUM_MAX" => LONG_MAX / 2, "INT64_MIN" => -9223372036854775808, @@ -2092,10 +2097,9 @@ def test_initialize_copy_dup_clone_frozen_error def test_llong_min_gh_200 # https://github.com/ruby/bigdecimal/issues/199 # Between LLONG_MIN and -ULLONG_MAX - llong_min = -(2 ** 63 + 1) - assert_equal(BigDecimal(llong_min.to_s), BigDecimal(llong_min), "[GH-200]") + assert_equal(BigDecimal(LIMITS["LLONG_MIN"].to_s), BigDecimal(LIMITS["LLONG_MIN"]), "[GH-200]") - minus_ullong_max = -(2 ** 64 - 1) + minus_ullong_max = -LIMITS["ULLONG_MAX"] assert_equal(BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max), "[GH-200]") end From 31a7a3742623fb6e3f1ff8260e6957a1e5b26687 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 14 Oct 2021 13:57:36 -0500 Subject: [PATCH 231/546] Enhanced RDoc for bigdecimal.c --- ext/bigdecimal/bigdecimal.c | 266 +++++++++++++++++++++++++++--------- 1 file changed, 201 insertions(+), 65 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1d829132..22d9c5c0 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -275,11 +275,13 @@ GetVpValue(VALUE v, int must) } /* call-seq: - * BigDecimal.double_fig + * BigDecimal.double_fig + * + * Returns the number of digits a Float object is allowed to have; + * the result is system-dependent: + * + * BigDecimal.double_fig # => 16 * - * The BigDecimal.double_fig class method returns the number of digits a - * Float number is allowed to have. The result depends upon the CPU and OS - * in use. */ static VALUE BigDecimal_double_fig(VALUE self) @@ -288,9 +290,9 @@ BigDecimal_double_fig(VALUE self) } /* call-seq: - * big_decimal.precs -> array + * precs -> array * - * Returns an Array of two Integer values that represent platform-dependent + * Returns an array of two integer values that represent platform-dependent * internal storage properties. * * This method is deprecated and will be removed in the future. @@ -298,7 +300,6 @@ BigDecimal_double_fig(VALUE self) * significant digits in scientific notation, and BigDecimal#precision for * obtaining the number of digits in decimal notation. * - * BigDecimal('5').precs #=> [9, 18] */ static VALUE @@ -319,20 +320,26 @@ BigDecimal_prec(VALUE self) } /* - * call-seq: - * big_decimal.precision -> intreger + * call-seq: + * precision -> integer * - * Returns the number of decimal digits in this number. + * Returns the number of decimal digits in +self+: * - * Example: + * %w[0 1 -1e20 1e-20 Infinity -Infinity NaN].each do |s| + * precision = BigDecimal(s).precision + * puts format("%9s has precision %2d", s, precision) + * end + * + * Output: + * + * 0 has precision 0 + * 1 has precision 1 + * -1e20 has precision 21 + * 1e-20 has precision 20 + * Infinity has precision 0 + * -Infinity has precision 0 + * NaN has precision 0 * - * BigDecimal("0").precision # => 0 - * BigDecimal("1").precision # => 1 - * BigDecimal("-1e20").precision # => 21 - * BigDecimal("1e-20").precision # => 20 - * BigDecimal("Infinity").precision # => 0 - * BigDecimal("-Infinity").precision # => 0 - * BigDecimal("NaN").precision # => 0 */ static VALUE BigDecimal_precision(VALUE self) @@ -412,12 +419,18 @@ BigDecimal_n_significant_digits(VALUE self) } /* - * call-seq: hash + * call-seq: + * hash -> integer + * + * Returns the integer hash value for +self+. + * + * Two instances of \BigDecimal have the same hash value if and only if + * they have equal: * - * Creates a hash for this BigDecimal. + * - Sign. + * - Fractional part. + * - Exponent. * - * Two BigDecimals with equal sign, - * fractional part and exponent have the same hash. */ static VALUE BigDecimal_hash(VALUE self) @@ -437,16 +450,16 @@ BigDecimal_hash(VALUE self) } /* - * call-seq: _dump + * call-seq: + * _dump * - * Method used to provide marshalling support. + * Returns a string representing the marshalling of +self+. + * See module Marshal. * - * inf = BigDecimal('Infinity') - * #=> Infinity - * BigDecimal._load(inf._dump) - * #=> Infinity + * inf = BigDecimal('Infinity') # => Infinity + * dumped = inf._dump # => "9:Infinity" + * BigDecimal._load(dumped) # => Infinity * - * See the Marshal module. */ static VALUE BigDecimal_dump(int argc, VALUE *argv, VALUE self) @@ -580,42 +593,165 @@ check_rounding_mode(VALUE const v) return sw; } -/* call-seq: - * BigDecimal.mode(mode, value) - * - * Controls handling of arithmetic exceptions and rounding. If no value - * is supplied, the current value is returned. - * - * Six values of the mode parameter control the handling of arithmetic - * exceptions: - * - * BigDecimal::EXCEPTION_NaN - * BigDecimal::EXCEPTION_INFINITY - * BigDecimal::EXCEPTION_UNDERFLOW - * BigDecimal::EXCEPTION_OVERFLOW - * BigDecimal::EXCEPTION_ZERODIVIDE - * BigDecimal::EXCEPTION_ALL - * - * For each mode parameter above, if the value set is false, computation - * continues after an arithmetic exception of the appropriate type. - * When computation continues, results are as follows: - * - * EXCEPTION_NaN:: NaN - * EXCEPTION_INFINITY:: +Infinity or -Infinity - * EXCEPTION_UNDERFLOW:: 0 - * EXCEPTION_OVERFLOW:: +Infinity or -Infinity - * EXCEPTION_ZERODIVIDE:: +Infinity or -Infinity - * - * One value of the mode parameter controls the rounding of numeric values: - * BigDecimal::ROUND_MODE. The values it can take are: - * - * ROUND_UP, :up:: round away from zero - * ROUND_DOWN, :down, :truncate:: round towards zero (truncate) - * ROUND_HALF_UP, :half_up, :default:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round away from zero. (default) - * ROUND_HALF_DOWN, :half_down:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round towards zero. - * ROUND_HALF_EVEN, :half_even, :banker:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round towards the even neighbor (Banker's rounding) - * ROUND_CEILING, :ceiling, :ceil:: round towards positive infinity (ceil) - * ROUND_FLOOR, :floor:: round towards negative infinity (floor) +/* call-seq: + * BigDecimal.mode(mode, setting = nil) -> integer + * + * Returns an integer representing the mode settings + * for exception handling and rounding. + * + * These modes control exception handling: + * + * - \BigDecimal::EXCEPTION_NaN. + * - \BigDecimal::EXCEPTION_INFINITY. + * - \BigDecimal::EXCEPTION_UNDERFLOW. + * - \BigDecimal::EXCEPTION_OVERFLOW. + * - \BigDecimal::EXCEPTION_ZERODIVIDE. + * - \BigDecimal::EXCEPTION_ALL. + * + * Values for +setting+ for exception handling: + * + * - +true+: sets the given +mode+ to +true+. + * - +false+: sets the given +mode+ to +false+. + * - +nil+: does not modify the mode settings. + * + * You can use method BigDecimal.save_exception_mode + * to temporarily change, and then automatically restore, exception modes. + * + * For clarity, some examples below begins by setting all + * exception modes to +false+. + * + * This mode controls the way rounding is to be performed: + * + * - \BigDecimal::ROUND_MODE + * + * You can use method BigDecimal.save_rounding_mode + * to temporarily change, and then automatically restore, the rounding mode. + * + * NaNs + * + * Mode \BigDecimal::EXCEPTION_NaN controls behavior + * when a \BigDecimal NaN is created. + * Settings" + * + * - +false+ (default): Returns BigDecimal('NaN'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * BigDecimal('NaN') # => NaN + * BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) # => 2 + * BigDecimal('NaN') # Raises FloatDomainError + * + * Infinities + * + * Mode \BigDecimal::EXCEPTION_INFINITY controls behavior + * when a \BigDecimal Infinity or -Infinity is created. + * Settings: + * + * - +false+ (default): Returns BigDecimal('Infinity') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * BigDecimal('Infinity') # => Infinity + * BigDecimal('-Infinity') # => -Infinity + * BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) # => 1 + * BigDecimal('Infinity') # Raises FloatDomainError + * BigDecimal('-Infinity') # Raises FloatDomainError + * + * Underflow + * + * Mode \BigDecimal::EXCEPTION_UNDERFLOW controls behavior + * when a \BigDecimal underflow occurs. + * Settings: + * + * - +false+ (default): Returns BigDecimal('0') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * def flow_under + * x = BigDecimal('0.1') + * 100.times { x *= x } + * end + * flow_under # => 100 + * BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, true) # => 4 + * flow_under # Raises FloatDomainError + * + * Overflow + * + * Mode \BigDecimal::EXCEPTION_OVERFLOW controls behavior + * when a \BigDecimal overflow occurs. + * Settings: + * + * - +false+ (default): Returns BigDecimal('Infinity') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * def flow_over + * x = BigDecimal('10') + * 100.times { x *= x } + * end + * flow_over # => 100 + * BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true) # => 1 + * flow_over # Raises FloatDomainError + * + * Zero Division + * + * Mode \BigDecimal::EXCEPTION_ZERODIVIDE controls behavior + * when a zero-division occurs. + * Settings: + * + * - +false+ (default): Returns BigDecimal('Infinity') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * one = BigDecimal('1') + * zero = BigDecimal('0') + * one / zero # => Infinity + * BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, true) # => 16 + * one / zero # Raises FloatDomainError + * + * All Exceptions + * + * Mode \BigDecimal::EXCEPTION_ALL controls all of the above: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, true) # => 23 + * + * Rounding + * + * Mode \BigDecimal::ROUND_MODE controls the way rounding is to be performed; + * its +setting+ values are: + * + * - +ROUND_UP+: Round away from zero. + * Aliased as +:up+. + * - +ROUND_DOWN+: Round toward zero. + * Aliased as +:down+ and +:truncate+. + * - +ROUND_HALF_UP+: Round toward the nearer neighbor; + * if the neighbors are equidistant, round away from zero. + * Aliased as +:half_up+ and +:default+. + * - +ROUND_HALF_DOWN+: Round toward the nearer neighbor; + * if the neighbors are equidistant, round toward from zero. + * Aliased as +:half_down+. + * - +ROUND_HALF_EVEN+ (Banker's rounding): Round toward the nearest neighbor; + * if the neighbors are equidistant, round toward the even neighbor. + * Aliased as +:half_even+ and +:banker+. + * - +ROUND_CEILING+: Round toward positive infinity. + * Aliased as +:ceiling+ and +:ceil+. + * - +ROUND_FLOOR+: Round toward negative infinity. + * Aliased as +:floor:+. * */ static VALUE From 3a35f92f8b2cca70b329a851596681bb8536cdda Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 15 Oct 2021 12:27:26 -0500 Subject: [PATCH 232/546] Enhanced RDoc for bigdecimal.c --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 22d9c5c0..cee74a58 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -617,7 +617,7 @@ check_rounding_mode(VALUE const v) * You can use method BigDecimal.save_exception_mode * to temporarily change, and then automatically restore, exception modes. * - * For clarity, some examples below begins by setting all + * For clarity, some examples below begin by setting all * exception modes to +false+. * * This mode controls the way rounding is to be performed: From 681cd2d81d06c2b419eedb6be04ce7058bd5b181 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 12 Nov 2021 08:46:30 -0600 Subject: [PATCH 233/546] Respond to review for #precision --- ext/bigdecimal/bigdecimal.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index cee74a58..68cb31d2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -325,20 +325,13 @@ BigDecimal_prec(VALUE self) * * Returns the number of decimal digits in +self+: * - * %w[0 1 -1e20 1e-20 Infinity -Infinity NaN].each do |s| - * precision = BigDecimal(s).precision - * puts format("%9s has precision %2d", s, precision) - * end - * - * Output: - * - * 0 has precision 0 - * 1 has precision 1 - * -1e20 has precision 21 - * 1e-20 has precision 20 - * Infinity has precision 0 - * -Infinity has precision 0 - * NaN has precision 0 + * BigDecimal("0").precision # => 0 + * BigDecimal("1").precision # => 1 + * BigDecimal("-1e20").precision # => 21 + * BigDecimal("1e-20").precision # => 20 + * BigDecimal("Infinity").precision # => 0 + * BigDecimal("-Infinity").precision # => 0 + * BigDecimal("NaN").precision # => 0 * */ static VALUE From 6d69422e3795d2c09197c93b7e25d9d5a134868e Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 12 Nov 2021 09:14:09 -0600 Subject: [PATCH 234/546] Respond to review --- ext/bigdecimal/bigdecimal.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 68cb31d2..ffa705da 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -292,7 +292,7 @@ BigDecimal_double_fig(VALUE self) /* call-seq: * precs -> array * - * Returns an array of two integer values that represent platform-dependent + * Returns an Array of two Integer values that represent platform-dependent * internal storage properties. * * This method is deprecated and will be removed in the future. @@ -624,7 +624,8 @@ check_rounding_mode(VALUE const v) * * Mode \BigDecimal::EXCEPTION_NaN controls behavior * when a \BigDecimal NaN is created. - * Settings" + * + * Settings: * * - +false+ (default): Returns BigDecimal('NaN'). * - +true+: Raises FloatDomainError. From 4eadcdf0a63d3bab1ec2f2b6e11f0eb4f53de84e Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 12 Nov 2021 09:27:52 -0600 Subject: [PATCH 235/546] Respond to review --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index ffa705da..e7eb7e4b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -733,10 +733,10 @@ check_rounding_mode(VALUE const v) * Aliased as +:up+. * - +ROUND_DOWN+: Round toward zero. * Aliased as +:down+ and +:truncate+. - * - +ROUND_HALF_UP+: Round toward the nearer neighbor; + * - +ROUND_HALF_UP+: Round toward the nearest neighbor; * if the neighbors are equidistant, round away from zero. * Aliased as +:half_up+ and +:default+. - * - +ROUND_HALF_DOWN+: Round toward the nearer neighbor; + * - +ROUND_HALF_DOWN+: Round toward the nearest neighbor; * if the neighbors are equidistant, round toward from zero. * Aliased as +:half_down+. * - +ROUND_HALF_EVEN+ (Banker's rounding): Round toward the nearest neighbor; From f528a0006e99c03715fad4dc009e51284368df19 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 12 Nov 2021 09:52:10 -0600 Subject: [PATCH 236/546] Respond to review --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e7eb7e4b..8a107386 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -737,7 +737,7 @@ check_rounding_mode(VALUE const v) * if the neighbors are equidistant, round away from zero. * Aliased as +:half_up+ and +:default+. * - +ROUND_HALF_DOWN+: Round toward the nearest neighbor; - * if the neighbors are equidistant, round toward from zero. + * if the neighbors are equidistant, round toward zero. * Aliased as +:half_down+. * - +ROUND_HALF_EVEN+ (Banker's rounding): Round toward the nearest neighbor; * if the neighbors are equidistant, round toward the even neighbor. From e864828b47d873c9adf34e9eeca1ef0100366b67 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 27 Oct 2021 17:31:45 +0900 Subject: [PATCH 237/546] Add tests for the issue GH-192 --- test/bigdecimal/helper.rb | 11 +++++++++++ test/bigdecimal/test_bigdecimal.rb | 14 +++++++++++++- test/bigdecimal/test_bigdecimal_util.rb | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 22b05f09..46721fb9 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -1,8 +1,19 @@ # frozen_string_literal: false require "test/unit" require "bigdecimal" +require 'rbconfig/sizeof' module TestBigDecimalBase + if RbConfig::SIZEOF.key?("int64_t") + SIZEOF_DECDIG = RbConfig::SIZEOF["int32_t"] + BASE = 1_000_000_000 + BASE_FIG = 9 + else + SIZEOF_DECDIG = RbConfig::SIZEOF["int16_t"] + BASE = 1000 + BASE_FIG = 4 + end + def setup @mode = BigDecimal.mode(BigDecimal::EXCEPTION_ALL) BigDecimal.mode(BigDecimal::EXCEPTION_ALL, true) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index d7b245b0..1e6c5953 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1,7 +1,6 @@ # frozen_string_literal: false require_relative "helper" require 'bigdecimal/math' -require 'rbconfig/sizeof' class TestBigDecimal < Test::Unit::TestCase include TestBigDecimalBase @@ -101,6 +100,19 @@ def test_BigDecimal_bug7522 assert_not_same(bd, BigDecimal(bd, 1, exception: false)) end + def test_BigDecimal_issue_192 + # https://github.com/ruby/bigdecimal/issues/192 + # https://github.com/rails/rails/pull/42125 + if BASE_FIG == 9 + int = 1_000_000_000_12345_0000 + big = BigDecimal("0.100000000012345e19") + else # BASE_FIG == 4 + int = 1_0000_12_00 + big = BigDecimal("0.1000012e9") + end + assert_equal(BigDecimal(int), big, "[ruby/bigdecimal#192]") + end + def test_BigDecimal_with_invalid_string [ '', '.', 'e1', 'd1', '.e', '.d', '1.e', '1.d', '.1e', '.1d', diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index ffd4c5f6..2f27163e 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -25,6 +25,8 @@ def test_Float_to_d_without_precision assert_equal(9.05, 9.05.to_d.to_f) assert_equal("9.05", 9.05.to_d.to_s('F')) + assert_equal("65.6", 65.6.to_d.to_s("F")) + assert_equal(Math::PI, Math::PI.to_d.to_f) bug9214 = '[ruby-core:58858]' @@ -60,6 +62,19 @@ def test_Float_to_d_bug13331 "[ruby-core:80234] [Bug #13331]") end + def test_Float_to_d_issue_192 + # https://github.com/ruby/bigdecimal/issues/192 + # https://github.com/rails/rails/pull/42125 + if BASE_FIG == 9 + flo = 1_000_000_000.12345 + big = BigDecimal("0.100000000012345e10") + else # BASE_FIG == 4 + flo = 1_0000.12 + big = BigDecimal("0.1000012e5") + end + assert_equal(flo.to_d, big, "[ruby/bigdecimal#192]") + end + def test_Rational_to_d digits = 100 delta = 1.0/10**(digits) From eebc98b85a8323eddd3d371e3e85a3d09d28d093 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 18 Nov 2021 10:59:12 +0900 Subject: [PATCH 238/546] Fix trailing zeros handling in rb_uint64_convert_to_BigDecimal Fix GH-192 --- ext/bigdecimal/bigdecimal.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 8a107386..7cec33b7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2862,21 +2862,29 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r } else { DECDIG buf[BIGDECIMAL_INT64_MAX_LENGTH] = {0,}; - size_t exp = 0, ntz = 0; - for (; uval > 0; ++exp) { - DECDIG r = uval % BASE; - if (r == 0) ++ntz; - buf[BIGDECIMAL_INT64_MAX_LENGTH - exp - 1] = r; + DECDIG r = uval % BASE; + size_t len = 0, ntz = 0; + if (r == 0) { + // Count and skip trailing zeros + for (; r == 0 && uval > 0; ++ntz) { + uval /= BASE; + r = uval % BASE; + } + } + for (; uval > 0; ++len) { + // Store digits + buf[BIGDECIMAL_INT64_MAX_LENGTH - len - 1] = r; uval /= BASE; + r = uval % BASE; } - const size_t len = exp - ntz; + const size_t exp = len + ntz; vp = VpAllocReal(len); vp->MaxPrec = len; vp->Prec = len; vp->exponent = exp; VpSetSign(vp, 1); - MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - exp, DECDIG, len); + MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - len, DECDIG, len); } return BigDecimal_wrap_struct(obj, vp); From 99442c75d3c04178ec294881d6b94d8b9cf1c701 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 22 Jan 2021 12:03:37 +0900 Subject: [PATCH 239/546] Use larger precision in divide for irrational or recurring results Just in case for irrational or recurring results, the precision of the quotient is set to at least more than 2*Float::DIG plus alpha. [Bug #13754] [Fix GH-94] --- ext/bigdecimal/bigdecimal.c | 13 ++++++------- test/bigdecimal/test_bigdecimal.rb | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7cec33b7..71499a7a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1508,13 +1508,12 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) SAVE(b); *div = b; - mx = a->Prec + vabs(a->exponent); - if (mx < b->Prec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); - mx++; /* NOTE: An additional digit is needed for the compatibility to - the version 1.2.1 and the former. */ - mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ((*c), VpCreateRbObject(mx, "#0", true)); - GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); + mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; + mx *= BASE_FIG; + if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) + mx = 2*BIGDECIMAL_DOUBLE_FIGURES; + GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); + GUARD_OBJ((*res), VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); VpDivd(*c, *res, a, b); return Qnil; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 1e6c5953..66b58aa1 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -953,9 +953,13 @@ def test_div assert_equal(2, BigDecimal("2") / 1) assert_equal(-2, BigDecimal("2") / -1) - assert_equal(BigDecimal('1486.868686869'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') + assert_equal(BigDecimal('1486.868686869'), + (BigDecimal('1472.0') / BigDecimal('0.99')).round(9), + '[ruby-core:59365] [#9316]') - assert_equal(4.124045235, BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0')), '[#9305]') + assert_in_delta(4.124045235, + (BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0'))).round(9, half: :up), + 10**Float::MIN_10_EXP, '[#9305]') BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) assert_positive_zero(BigDecimal("1.0") / BigDecimal("Infinity")) @@ -969,6 +973,15 @@ def test_div assert_raise_with_message(FloatDomainError, "Computation results in '-Infinity'") { BigDecimal("-1") / 0 } end + def test_dev_precision + bug13754 = '[ruby-core:82107] [Bug #13754]' + a = BigDecimal('101') + b = BigDecimal('0.9163472602589686') + c = a/b + assert(c.precision > b.precision, + "(101/0.9163472602589686).precision >= (0.9163472602589686).precision #{bug13754}") + end + def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) From 11cb2c884087addd84007fe4f6e9cdb80852f529 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 22 Jan 2021 13:49:46 +0900 Subject: [PATCH 240/546] Let BigDecimal_DoDivmod use the same precision calculation as BigDecimal_divide --- ext/bigdecimal/bigdecimal.c | 33 ++++++++++++++++++------------ test/bigdecimal/test_bigdecimal.rb | 7 +++++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 71499a7a..0558a17f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1613,26 +1613,33 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) return Qtrue; } - mx = a->Prec + vabs(a->exponent); - if (mxPrec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); - mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); + mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; + mx *= BASE_FIG; + if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) + mx = 2*BIGDECIMAL_DOUBLE_FIGURES; + + GUARD_OBJ(c, VpCreateRbObject(mx + 2*BASE_FIG, "0", true)); + GUARD_OBJ(res, VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); VpDivd(c, res, a, b); - mx = c->Prec * (VpBaseFig() + 1); + + mx = c->Prec * BASE_FIG; GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); + VpMult(res, d, b); VpAddSub(c, a, res, -1); + if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - VpAddSub(res, d, VpOne(), -1); + /* remainder adjustment for negative case */ + VpAddSub(res, d, VpOne(), -1); GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); - VpAddSub(d, c, b, 1); - *div = res; - *mod = d; - } else { - *div = d; - *mod = c; + VpAddSub(d, c, b, 1); + *div = res; + *mod = d; + } + else { + *div = d; + *mod = c; } return Qtrue; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 66b58aa1..26d346b3 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1043,6 +1043,13 @@ def test_divmod assert_raise(ZeroDivisionError){BigDecimal("0").divmod(0)} end + def test_divmod_precision + a = BigDecimal('2e55') + b = BigDecimal('1.23456789e10') + q, r = a.divmod(b) + assert_equal((a/b), q) + end + def test_divmod_error assert_raise(TypeError) { BigDecimal(20).divmod('2') } end From 8dc8cd339dcfe0fe357c398551819976f9f64dd4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 22 Jan 2021 13:50:26 +0900 Subject: [PATCH 241/546] Fix the precision of the adjusted quotient --- ext/bigdecimal/bigdecimal.c | 6 ++++-- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0558a17f..1cbdbd88 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1630,9 +1630,11 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) VpAddSub(c, a, res, -1); if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - /* remainder adjustment for negative case */ + /* result adjustment for negative case */ + res = VpReallocReal(res, d->MaxPrec); + res->MaxPrec = d->MaxPrec; VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); + GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b) * 2*BASE_FIG, "0", true)); VpAddSub(d, c, b, 1); *div = res; *mod = d; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 26d346b3..784560d1 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1048,6 +1048,10 @@ def test_divmod_precision b = BigDecimal('1.23456789e10') q, r = a.divmod(b) assert_equal((a/b), q) + + b = BigDecimal('-1.23456789e10') + q, r = a.divmod(b) + assert_equal((a/b), q) end def test_divmod_error From 252748de177e120920ca4fa1512e74ee7e809fd3 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 24 Nov 2021 00:20:42 +0900 Subject: [PATCH 242/546] Keep obj-to-Real link when VpReallocReal returns different pointer --- ext/bigdecimal/bigdecimal.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1cbdbd88..668d9d1d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -869,7 +869,18 @@ VpCreateRbObject(size_t mx, const char *str, bool raise_exception) } #define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(DECDIG)) -#define VpReallocReal(ptr, prec) (Real *)VpMemRealloc((ptr), offsetof(Real, frac) + (prec) * sizeof(DECDIG)) + +static Real * +VpReallocReal(Real *pv, size_t prec) +{ + VALUE obj = pv ? pv->obj : 0; + Real *new_pv = (Real *)VpMemRealloc(pv, offsetof(Real, frac) + prec * sizeof(DECDIG)); + if (obj) { + new_pv->obj = 0; + BigDecimal_wrap_struct(obj, new_pv); + } + return new_pv; +} static Real * VpCopy(Real *pv, Real const* const x) From 7d198394a21135a34957243c7fcca43e6b6aad00 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 25 Nov 2021 14:55:29 +0900 Subject: [PATCH 243/546] Fix BigDecimal#precision for single DECDIG case Fix GH-205 --- ext/bigdecimal/bigdecimal.c | 22 ++++++++++++++++++---- test/bigdecimal/test_bigdecimal.rb | 16 ++++++++-------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 668d9d1d..a8c0a48a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -341,11 +341,19 @@ BigDecimal_precision(VALUE self) Real *p; GUARD_OBJ(p, GetVpValue(self, 1)); + if (VpIsZero(p) || !VpIsDef(p)) return INT2FIX(0); /* * The most significant digit is frac[0], and the least significant digit is frac[Prec-1]. * When the exponent is zero, the decimal point is located just before frac[0]. + * + * * When the exponent is negative, the decimal point moves to leftward. + * In this case, the precision can be calculated by BASE_FIG * (-exponent + Prec) - ntz. + * + * 0 . 0000 0000 | frac[0] frac[1] ... frac[Prec-1] + * <----------| exponent == -2 + * * Conversely, when the exponent is positive, the decimal point moves to rightward. * * | frac[0] frac[1] frac[2] . frac[3] frac[4] ... frac[Prec-1] @@ -353,24 +361,30 @@ BigDecimal_precision(VALUE self) */ ssize_t ex = p->exponent; - ssize_t precision = 0; + + /* Count the number of decimal digits before frac[1]. */ + ssize_t precision = BASE_FIG; /* The number of decimal digits in frac[0]. */ if (ex < 0) { - precision = (-ex + 1) * BASE_FIG; /* 1 is for p->frac[0] */ + precision += -ex * BASE_FIG; /* The number of leading zeros before frac[0]. */ ex = 0; } - else if (p->Prec > 0) { + else if (ex > 0) { + /* Count the number of decimal digits without the leading zeros in + * the most significant digit in the integral part. */ DECDIG x = p->frac[0]; for (precision = 0; x > 0; x /= 10) { ++precision; } } + /* Count the number of decimal digits after frac[0]. */ if (ex > (ssize_t)p->Prec) { + /* In this case the number is an integer with multiple trailing zeros. */ precision += (ex - 1) * BASE_FIG; } else if (p->Prec > 0) { ssize_t n = (ssize_t)p->Prec - 1; - while (n > 0 && p->frac[n] == 0) --n; + while (n > 0 && p->frac[n] == 0) --n; /* Skip trailing zeros, just in case. */ precision += n * BASE_FIG; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 784560d1..5a4108c6 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2036,10 +2036,14 @@ def test_precision_only_integer def test_precision_only_fraction assert_equal(1, BigDecimal("0.1").precision) assert_equal(1, BigDecimal("-0.1").precision) - assert_equal(1, BigDecimal("0.01").precision) - assert_equal(1, BigDecimal("-0.01").precision) + assert_equal(2, BigDecimal("0.01").precision) + assert_equal(2, BigDecimal("-0.01").precision) assert_equal(2, BigDecimal("0.11").precision) assert_equal(2, BigDecimal("-0.11").precision) + assert_equal(9, BigDecimal("0.000_000_001").precision) + assert_equal(9, BigDecimal("-0.000_000_001").precision) + assert_equal(10, BigDecimal("0.000_000_000_1").precision) + assert_equal(10, BigDecimal("-0.000_000_000_1").precision) assert_equal(21, BigDecimal("0.000_000_000_000_000_000_001").precision) assert_equal(21, BigDecimal("-0.000_000_000_000_000_000_001").precision) assert_equal(100, BigDecimal("111e-100").precision) @@ -2047,12 +2051,8 @@ def test_precision_only_fraction end def test_precision_full - assert_equal(1, BigDecimal("0.1").precision) - assert_equal(1, BigDecimal("-0.1").precision) - assert_equal(1, BigDecimal("0.01").precision) - assert_equal(1, BigDecimal("-0.01").precision) - assert_equal(2, BigDecimal("0.11").precision) - assert_equal(2, BigDecimal("-0.11").precision) + assert_equal(5, BigDecimal("11111e-2").precision) + assert_equal(5, BigDecimal("-11111e-2").precision) assert_equal(5, BigDecimal("11111e-2").precision) assert_equal(5, BigDecimal("-11111e-2").precision) assert_equal(21, BigDecimal("100.000_000_000_000_000_001").precision) From 6139ea1092861b74fda6172120cffe9bfe793627 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 19 Nov 2021 11:53:55 -0600 Subject: [PATCH 244/546] Enhanced RDoc for selected methods --- ext/bigdecimal/bigdecimal.c | 53 ++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a8c0a48a..f2b93ccc 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1121,12 +1121,14 @@ BigDecimal_coerce(VALUE self, VALUE other) } /* - * call-seq: - * +big_decimal -> big_decimal + * call-seq: + * +big_decimal -> big_decimal + * + * Returns +self+: * - * Return self. + * +BigDecimal(5) # => 0.5e1 + * +BigDecimal(-5) # => -0.5e1 * - * +BigDecimal('5') #=> 0.5e1 */ static VALUE @@ -1136,22 +1138,16 @@ BigDecimal_uplus(VALUE self) } /* - * Document-method: BigDecimal#add - * Document-method: BigDecimal#+ + * call-seq: + * self + value -> new_bigdecimal * - * call-seq: - * add(value, digits) + * Returns the sum of +self+ and +value+: * - * Add the specified value. - * - * e.g. - * c = a.add(b,n) - * c = a + b + * b = BigDecimal('111111.111') # => 0.111111111e6 + * b + 1 # => 0.111112111e6 * - * digits:: If specified and less than the number of significant digits of the - * result, the result is rounded to that number of digits, according - * to BigDecimal.mode. */ + static VALUE BigDecimal_add(VALUE self, VALUE r) { @@ -1882,6 +1878,31 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) return BigDecimal_div2(self, b, n); } + /* + * call-seq: + * add(value, ndigits) + * + * Returns the sum of +self+ and +value+ + * with a precision of +ndigits+ decimal digits. + * + * When +ndigits+ is less than the number of significant digits + * in the sum, the sum is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. + * + * Examples: + * + * BigDecimal('111111.111').add(1, 0) # => 0.111112111e6 + * BigDecimal('111111.111').add(1, 2) # => 0.11e6 + * BigDecimal('111111.111').add(1, 3) # => 0.111e6 + * BigDecimal('111111.111').add(1, 4) # => 0.1111e6 + * BigDecimal('111111.111').add(1, 5) # => 0.11111e6 + * BigDecimal('111111.111').add(1, 6) # => 0.111112e6 + * BigDecimal('111111.111').add(1, 7) # => 0.1111121e6 + * BigDecimal('111111.111').add(1, 8) # => 0.11111211e6 + * BigDecimal('111111.111').add(1, 9) # => 0.111112111e6 + * + */ + static VALUE BigDecimal_add2(VALUE self, VALUE b, VALUE n) { From 0de9298d15698058c7cc9b5b2a4905a76e4c8e1f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 19 Nov 2021 12:07:34 -0600 Subject: [PATCH 245/546] Enhanced RDoc for selected methods --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f2b93ccc..0157ef53 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1122,7 +1122,7 @@ BigDecimal_coerce(VALUE self, VALUE other) /* * call-seq: - * +big_decimal -> big_decimal + * +big_decimal -> self * * Returns +self+: * @@ -1880,7 +1880,7 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) /* * call-seq: - * add(value, ndigits) + * add(value, ndigits) -> new_bigdecimal * * Returns the sum of +self+ and +value+ * with a precision of +ndigits+ decimal digits. From 42c999f728ba36c364a7583e292c95ab9f77260f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 23 Nov 2021 13:26:14 -0600 Subject: [PATCH 246/546] Set rounding mode in example --- ext/bigdecimal/bigdecimal.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0157ef53..6c845fcd 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1891,6 +1891,8 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) * * Examples: * + * # Set the rounding mode. + * BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) * BigDecimal('111111.111').add(1, 0) # => 0.111112111e6 * BigDecimal('111111.111').add(1, 2) # => 0.11e6 * BigDecimal('111111.111').add(1, 3) # => 0.111e6 From 8fc83dd2fe9fb9afa4213d5b007d455dd2b6eb3f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 25 Nov 2021 10:42:34 -0600 Subject: [PATCH 247/546] Set rounding mode in example --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6c845fcd..e0496c2d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1892,7 +1892,7 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) * Examples: * * # Set the rounding mode. - * BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) + * BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) * BigDecimal('111111.111').add(1, 0) # => 0.111112111e6 * BigDecimal('111111.111').add(1, 2) # => 0.11e6 * BigDecimal('111111.111').add(1, 3) # => 0.111e6 From 4fbec55680e95692ebd24f21f873d39de3a2e91b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 25 Nov 2021 14:51:13 +0900 Subject: [PATCH 248/546] Add BigDecimal#scale Fixes GH-198. --- ext/bigdecimal/bigdecimal.c | 201 ++++++++++++++++++++--------- test/bigdecimal/test_bigdecimal.rb | 53 ++++++++ 2 files changed, 195 insertions(+), 59 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e0496c2d..fa7ae62f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -319,86 +319,168 @@ BigDecimal_prec(VALUE self) return obj; } -/* - * call-seq: - * precision -> integer - * - * Returns the number of decimal digits in +self+: - * - * BigDecimal("0").precision # => 0 - * BigDecimal("1").precision # => 1 - * BigDecimal("-1e20").precision # => 21 - * BigDecimal("1e-20").precision # => 20 - * BigDecimal("Infinity").precision # => 0 - * BigDecimal("-Infinity").precision # => 0 - * BigDecimal("NaN").precision # => 0 - * - */ -static VALUE -BigDecimal_precision(VALUE self) +static void +BigDecimal_count_precision_and_scale(VALUE self, ssize_t *out_precision, ssize_t *out_scale) { ENTER(1); + if (out_precision == NULL && out_scale == NULL) + return; + Real *p; GUARD_OBJ(p, GetVpValue(self, 1)); - if (VpIsZero(p) || !VpIsDef(p)) return INT2FIX(0); + if (VpIsZero(p) || !VpIsDef(p)) { + zero: + if (out_precision) *out_precision = 0; + if (out_scale) *out_scale = 0; + return; + } + + DECDIG x; + + ssize_t n = p->Prec; /* The length of frac without zeros. */ + while (n > 0 && p->frac[n-1] == 0) --n; + if (n == 0) goto zero; + + int nlz = BASE_FIG; + for (x = p->frac[0]; x > 0; x /= 10) --nlz; + + int ntz = 0; + for (x = p->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz; /* - * The most significant digit is frac[0], and the least significant digit is frac[Prec-1]. - * When the exponent is zero, the decimal point is located just before frac[0]. + * Calculate the precision and the scale + * ------------------------------------- * + * The most significant digit is frac[0], and the least significant digit + * is frac[Prec-1]. When the exponent is zero, the decimal point is + * located just before frac[0]. * * When the exponent is negative, the decimal point moves to leftward. - * In this case, the precision can be calculated by BASE_FIG * (-exponent + Prec) - ntz. + * In this case, the precision can be calculated by + * + * precision = BASE_FIG * (-exponent + n) - ntz, + * + * and the scale is the same as precision. * - * 0 . 0000 0000 | frac[0] frac[1] ... frac[Prec-1] - * <----------| exponent == -2 + * 0 . 0000 0000 | frac[0] ... frac[n-1] | + * |<----------| exponent == -2 | + * |---------------------------------->| precision + * |---------------------------------->| scale * - * Conversely, when the exponent is positive, the decimal point moves to rightward. * - * | frac[0] frac[1] frac[2] . frac[3] frac[4] ... frac[Prec-1] - * |------------------------> exponent == 3 + * Conversely, when the exponent is positive, the decimal point moves to + * rightward. In this case, the scale equals to + * + * BASE_FIG * (n - exponent) - ntz. + * + * the precision equals to + * + * scale + BASE_FIG * exponent - nlz. + * + * | frac[0] frac[1] . frac[2] ... frac[n-1] | + * |---------------->| exponent == 2 | + * | |---------------------->| scale + * |---------------------------------------->| precision */ ssize_t ex = p->exponent; /* Count the number of decimal digits before frac[1]. */ - ssize_t precision = BASE_FIG; /* The number of decimal digits in frac[0]. */ + ssize_t n_digits_head = BASE_FIG; if (ex < 0) { - precision += -ex * BASE_FIG; /* The number of leading zeros before frac[0]. */ - ex = 0; + n_digits_head += (-ex) * BASE_FIG; /* The number of leading zeros before frac[0]. */ + ex = 0; } else if (ex > 0) { - /* Count the number of decimal digits without the leading zeros in - * the most significant digit in the integral part. */ - DECDIG x = p->frac[0]; - for (precision = 0; x > 0; x /= 10) { - ++precision; - } + /* Count the number of decimal digits without the leading zeros in + * the most significant digit in the integral part. + */ + n_digits_head -= nlz; /* Make the number of digits */ } - /* Count the number of decimal digits after frac[0]. */ - if (ex > (ssize_t)p->Prec) { - /* In this case the number is an integer with multiple trailing zeros. */ - precision += (ex - 1) * BASE_FIG; + if (out_precision) { + ssize_t precision = n_digits_head; + + /* Count the number of decimal digits after frac[0]. */ + if (ex > (ssize_t)n) { + /* In this case the number is an integer with some trailing zeros. */ + precision += (ex - 1) * BASE_FIG; + } + else if (n > 0) { + precision += (n - 1) * BASE_FIG; + + if (ex < (ssize_t)n) { + precision -= ntz; + } + } + + *out_precision = precision; } - else if (p->Prec > 0) { - ssize_t n = (ssize_t)p->Prec - 1; - while (n > 0 && p->frac[n] == 0) --n; /* Skip trailing zeros, just in case. */ - precision += n * BASE_FIG; + if (out_scale) { + ssize_t scale = 0; - if (ex < (ssize_t)p->Prec) { - DECDIG x = p->frac[n]; - for (; x > 0 && x % 10 == 0; x /= 10) { - --precision; - } + if (p->exponent < 0) { + scale = n_digits_head + (n - 1) * BASE_FIG - ntz; + } + else if (n > p->exponent) { + scale = (n - p->exponent) * BASE_FIG - ntz; } + + *out_scale = scale; } +} +/* + * call-seq: + * precision -> integer + * + * Returns the number of decimal digits in +self+: + * + * BigDecimal("0").precision # => 0 + * BigDecimal("1").precision # => 1 + * BigDecimal("1.1").precision # => 2 + * BigDecimal("3.1415").precision # => 5 + * BigDecimal("-1e20").precision # => 21 + * BigDecimal("1e-20").precision # => 20 + * BigDecimal("Infinity").precision # => 0 + * BigDecimal("-Infinity").precision # => 0 + * BigDecimal("NaN").precision # => 0 + * + */ +static VALUE +BigDecimal_precision(VALUE self) +{ + ssize_t precision; + BigDecimal_count_precision_and_scale(self, &precision, NULL); return SSIZET2NUM(precision); } +/* + * call-seq: + * scale -> integer + * + * Returns the number of decimal digits following the decimal digits in +self+. + * + * BigDecimal("0").scale # => 0 + * BigDecimal("1").scale # => 1 + * BigDecimal("1.1").scale # => 1 + * BigDecimal("3.1415").scale # => 4 + * BigDecimal("-1e20").precision # => 0 + * BigDecimal("1e-20").precision # => 20 + * BigDecimal("Infinity").scale # => 0 + * BigDecimal("-Infinity").scale # => 0 + * BigDecimal("NaN").scale # => 0 + */ +static VALUE +BigDecimal_scale(VALUE self) +{ + ssize_t scale; + BigDecimal_count_precision_and_scale(self, NULL, &scale); + return SSIZET2NUM(scale); +} + static VALUE BigDecimal_n_significant_digits(VALUE self) { @@ -406,23 +488,23 @@ BigDecimal_n_significant_digits(VALUE self) Real *p; GUARD_OBJ(p, GetVpValue(self, 1)); - - ssize_t n = p->Prec; - while (n > 0 && p->frac[n-1] == 0) --n; - if (n <= 0) { + if (VpIsZero(p) || !VpIsDef(p)) { return INT2FIX(0); } - int nlz, ntz; + ssize_t n = p->Prec; /* The length of frac without trailing zeros. */ + for (n = p->Prec; n > 0 && p->frac[n-1] == 0; --n); + if (n == 0) return INT2FIX(0); - DECDIG x = p->frac[0]; - for (nlz = BASE_FIG; x > 0; x /= 10) --nlz; + DECDIG x; + int nlz = BASE_FIG; + for (x = p->frac[0]; x > 0; x /= 10) --nlz; - x = p->frac[n-1]; - for (ntz = 0; x > 0 && x % 10 == 0; x /= 10) ++ntz; + int ntz = 0; + for (x = p->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz; - ssize_t n_digits = BASE_FIG * n - nlz - ntz; - return SSIZET2NUM(n_digits); + ssize_t n_significant_digits = BASE_FIG*n - nlz - ntz; + return SSIZET2NUM(n_significant_digits); } /* @@ -4129,6 +4211,7 @@ Init_bigdecimal(void) /* instance methods */ rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); rb_define_method(rb_cBigDecimal, "precision", BigDecimal_precision, 0); + rb_define_method(rb_cBigDecimal, "scale", BigDecimal_scale, 0); rb_define_method(rb_cBigDecimal, "n_significant_digits", BigDecimal_n_significant_digits, 0); rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 5a4108c6..f2162caf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2070,6 +2070,59 @@ def test_precision_special end end + def test_scale_only_integer + assert_equal(0, BigDecimal(0).scale) + assert_equal(0, BigDecimal(1).scale) + assert_equal(0, BigDecimal(-1).scale) + assert_equal(0, BigDecimal(10).scale) + assert_equal(0, BigDecimal(-10).scale) + assert_equal(0, BigDecimal(100_000_000).scale) + assert_equal(0, BigDecimal(-100_000_000).scale) + assert_equal(0, BigDecimal(100_000_000_000).scale) + assert_equal(0, BigDecimal(-100_000_000_000).scale) + assert_equal(0, BigDecimal(100_000_000_000_000_000_000).scale) + assert_equal(0, BigDecimal(-100_000_000_000_000_000_000).scale) + assert_equal(0, BigDecimal("111e100").scale) + assert_equal(0, BigDecimal("-111e100").scale) + end + + def test_scale_only_fraction + assert_equal(1, BigDecimal("0.1").scale) + assert_equal(1, BigDecimal("-0.1").scale) + assert_equal(2, BigDecimal("0.01").scale) + assert_equal(2, BigDecimal("-0.01").scale) + assert_equal(2, BigDecimal("0.11").scale) + assert_equal(2, BigDecimal("-0.11").scale) + assert_equal(21, BigDecimal("0.000_000_000_000_000_000_001").scale) + assert_equal(21, BigDecimal("-0.000_000_000_000_000_000_001").scale) + assert_equal(100, BigDecimal("111e-100").scale) + assert_equal(100, BigDecimal("-111e-100").scale) + end + + def test_scale_full + assert_equal(1, BigDecimal("0.1").scale) + assert_equal(1, BigDecimal("-0.1").scale) + assert_equal(2, BigDecimal("0.01").scale) + assert_equal(2, BigDecimal("-0.01").scale) + assert_equal(2, BigDecimal("0.11").scale) + assert_equal(2, BigDecimal("-0.11").scale) + assert_equal(2, BigDecimal("11111e-2").scale) + assert_equal(2, BigDecimal("-11111e-2").scale) + assert_equal(18, BigDecimal("100.000_000_000_000_000_001").scale) + assert_equal(18, BigDecimal("-100.000_000_000_000_000_001").scale) + end + + def test_scale_special + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + + assert_equal(0, BigDecimal("Infinity").scale) + assert_equal(0, BigDecimal("-Infinity").scale) + assert_equal(0, BigDecimal("NaN").scale) + end + end + def test_n_significant_digits_only_integer assert_equal(0, BigDecimal(0).n_significant_digits) assert_equal(1, BigDecimal(1).n_significant_digits) From ceaf16b03eecc6402583ac5a0e47b5831f6cc804 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 2 Dec 2021 17:03:33 +0900 Subject: [PATCH 249/546] [Doc] Add documentation of BigDecimal#n_significant_digits --- ext/bigdecimal/bigdecimal.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index fa7ae62f..70e1bf7e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -481,6 +481,22 @@ BigDecimal_scale(VALUE self) return SSIZET2NUM(scale); } +/* + * call-seq: + * n_significant_digits -> integer + * + * Returns the number of decimal significant digits in +self+. + * + * BigDecimal("0").scale # => 0 + * BigDecimal("1").scale # => 1 + * BigDecimal("1.1").scale # => 2 + * BigDecimal("3.1415").scale # => 5 + * BigDecimal("-1e20").precision # => 1 + * BigDecimal("1e-20").precision # => 1 + * BigDecimal("Infinity").scale # => 0 + * BigDecimal("-Infinity").scale # => 0 + * BigDecimal("NaN").scale # => 0 + */ static VALUE BigDecimal_n_significant_digits(VALUE self) { From c019caeabaf70ef9a8c5c6f49218ff8818ce5e4b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 2 Dec 2021 17:06:52 +0900 Subject: [PATCH 250/546] Add BigDecimal#precision_scale --- ext/bigdecimal/bigdecimal.c | 19 +++++++++++++++++++ test/bigdecimal/test_bigdecimal.rb | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 70e1bf7e..47b10d6a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -481,6 +481,24 @@ BigDecimal_scale(VALUE self) return SSIZET2NUM(scale); } +/* + * call-seq: + * precision_scale -> [integer, integer] + * + * Returns a 2-length array; the first item is the result of + * BigDecimal#precision and the second one is of BigDecimal#scale. + * + * See BigDecimal#precision. + * See BigDecimal#scale. + */ +static VALUE +BigDecimal_precision_scale(VALUE self) +{ + ssize_t precision, scale; + BigDecimal_count_precision_and_scale(self, &precision, &scale); + return rb_assoc_new(SSIZET2NUM(precision), SSIZET2NUM(scale)); +} + /* * call-seq: * n_significant_digits -> integer @@ -4228,6 +4246,7 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); rb_define_method(rb_cBigDecimal, "precision", BigDecimal_precision, 0); rb_define_method(rb_cBigDecimal, "scale", BigDecimal_scale, 0); + rb_define_method(rb_cBigDecimal, "precision_scale", BigDecimal_precision_scale, 0); rb_define_method(rb_cBigDecimal, "n_significant_digits", BigDecimal_n_significant_digits, 0); rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f2162caf..273abf78 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2123,6 +2123,17 @@ def test_scale_special end end + def test_precision_scale + assert_equal([2, 0], BigDecimal("11.0").precision_scale) + assert_equal([2, 1], BigDecimal("1.1").precision_scale) + assert_equal([2, 2], BigDecimal("0.11").precision_scale) + + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + assert_equal([0, 0], BigDecimal("Infinity").precision_scale) + end + end + def test_n_significant_digits_only_integer assert_equal(0, BigDecimal(0).n_significant_digits) assert_equal(1, BigDecimal(1).n_significant_digits) From bccaa66f2ca868c80e79946a98edc9f006d9658f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 2 Dec 2021 12:02:21 +0100 Subject: [PATCH 251/546] Improve extconf to allow using bigdecimal as a git gem e.g. ``` gem "bigdecimal", github: "ruby/bigdecimal" ``` It would fail because bundler regenerates the `gemspec`, so `bigdecimal_version` is gone. --- bigdecimal.gemspec | 4 +--- ext/bigdecimal/extconf.rb | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index bb4610cb..1d8b36ee 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,10 +1,8 @@ # coding: utf-8 -bigdecimal_version = '3.1.0.dev' - Gem::Specification.new do |s| s.name = "bigdecimal" - s.version = bigdecimal_version + s.version = "3.1.0.dev" s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"] diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index c92aacb3..9b0c55b2 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -3,10 +3,7 @@ def check_bigdecimal_version(gemspec_path) message "checking RUBY_BIGDECIMAL_VERSION... " - - bigdecimal_version = - IO.readlines(gemspec_path) - .grep(/\Abigdecimal_version\s+=\s+/)[0][/\'([^\']+)\'/, 1] + bigdecimal_version = File.read(gemspec_path).match(/^\s*s\.version\s+=\s+['"]([^'"]+)['"]\s*$/)[1] version_components = bigdecimal_version.split('.') bigdecimal_version = version_components[0, 3].join('.') From 900bb7fcf52e046a2d22934383431a4406c9d299 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 9 Dec 2021 21:35:33 +0900 Subject: [PATCH 252/546] Allow passing both float and precision in BigDecimal#div Fix GH-212. --- ext/bigdecimal/bigdecimal.c | 6 +++++- test/bigdecimal/test_bigdecimal.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 47b10d6a..36173f35 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1938,11 +1938,15 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) Real *res = NULL; Real *av = NULL, *bv = NULL, *cv = NULL; size_t mx = ix + VpBaseFig()*2; + size_t b_prec = ix; size_t pl = VpSetPrecLimit(0); GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0", true)); GUARD_OBJ(av, GetVpValue(self, 1)); - GUARD_OBJ(bv, GetVpValue(b, 1)); + if (RB_FLOAT_TYPE_P(b) && b_prec > BIGDECIMAL_DOUBLE_FIGURES) { + b_prec = BIGDECIMAL_DOUBLE_FIGURES; + } + GUARD_OBJ(bv, GetVpValueWithPrec(b, b_prec, 1)); mx = av->Prec + bv->Prec + 2; if (mx <= cv->MaxPrec) mx = cv->MaxPrec + 1; GUARD_OBJ(res, VpCreateRbObject((mx * 2 + 2)*VpBaseFig(), "#0", true)); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 273abf78..9e8f0d59 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1091,6 +1091,16 @@ def test_div_bigdecimal end end + def test_div_bigdecimal_with_float_and_precision + x = BigDecimal(5) + y = 5.1 + assert_equal(x.div(BigDecimal(y, 0), 8), + x.div(y, 8)) + + assert_equal(x.div(BigDecimal(y, 0), 100), + x.div(y, 100)) + end + def test_abs_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(1267650600228229401496703205376, x.abs) From ef9cf4e69e810ce9bb7e57d18cce1c589dfccb28 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 9 Dec 2021 21:50:46 +0900 Subject: [PATCH 253/546] Add TODO comment --- ext/bigdecimal/bigdecimal.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 36173f35..c648616b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1943,6 +1943,9 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0", true)); GUARD_OBJ(av, GetVpValue(self, 1)); + /* TODO: I want to refactor this precision control for a float value later + * by introducing an implicit conversion function instead of + * GetVpValueWithPrec. */ if (RB_FLOAT_TYPE_P(b) && b_prec > BIGDECIMAL_DOUBLE_FIGURES) { b_prec = BIGDECIMAL_DOUBLE_FIGURES; } From 299d7e9dfd5f2a049c5fb15daa8dc2872794948a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 14 Dec 2021 20:31:49 -0800 Subject: [PATCH 254/546] s/RubyVM::JIT/RubyVM::MJIT/g --- test/lib/core_assertions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index ff78c2bd..a4abff41 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -115,7 +115,7 @@ def syntax_check(code, fname, line) def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail - pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? + pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? require_relative '../../memory_status' raise MiniTest::Skip, "unsupported platform" unless defined?(Memory::Status) From ba57a569a36fe1b536879dd2664e4ba27212bfc6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 16 Dec 2021 13:58:08 +0900 Subject: [PATCH 255/546] Skip installation test with Windows --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8e603f6..87658190 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,4 +52,4 @@ jobs: - run: rake build - run: gem install pkg/*.gem - if: ${{ matrix.ruby != 'debug' }} + if: ${{ ( matrix.ruby != 'debug' && matrix.os == 'linux-latest' ) || ( matrix.ruby != 'debug' && matrix.os == 'macos-latest' ) }} From 13e0e93f37a6f7db110d1d95ce6b3617fb0775d7 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 9 Dec 2021 22:24:12 +0900 Subject: [PATCH 256/546] Let BigDecimal#quo accept precision Fix GH-214. --- ext/bigdecimal/bigdecimal.c | 96 ++++++++++++++++++++++-------- test/bigdecimal/test_bigdecimal.rb | 24 ++++++++ 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c648616b..3be0ffec 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -115,6 +115,8 @@ static ID id_half; */ static unsigned short VpGetException(void); static void VpSetException(unsigned short f); +static void VpCheckException(Real *p, bool always); +static VALUE VpCheckGetValue(Real *p); static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); static Real *VpCopy(Real *pv, Real const* const x); @@ -165,27 +167,6 @@ is_kind_of_BigDecimal(VALUE const v) return rb_typeddata_is_kind_of(v, &BigDecimal_data_type); } -static void -VpCheckException(Real *p, bool always) -{ - if (VpIsNaN(p)) { - VpException(VP_EXCEPTION_NaN, "Computation results in 'NaN' (Not a Number)", always); - } - else if (VpIsPosInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results in 'Infinity'", always); - } - else if (VpIsNegInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results in '-Infinity'", always); - } -} - -static VALUE -VpCheckGetValue(Real *p) -{ - VpCheckException(p, false); - return p->obj; -} - NORETURN(static void cannot_be_coerced_into_BigDecimal(VALUE, VALUE)); static void @@ -1656,12 +1637,15 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) } /* call-seq: - * a / b -> bigdecimal - * quo(value) -> bigdecimal + * a / b -> bigdecimal * * Divide by the specified value. * + * The result precision will be the precision of the larger operand, + * but its minimum is 2*Float::DIG. + * * See BigDecimal#div. + * See BigDecimal#quo. */ static VALUE BigDecimal_div(VALUE self, VALUE r) @@ -1683,6 +1667,45 @@ BigDecimal_div(VALUE self, VALUE r) return VpCheckGetValue(c); } +static VALUE BigDecimal_round(int argc, VALUE *argv, VALUE self); + +/* call-seq: + * quo(value) -> bigdecimal + * quo(value, digits) -> bigdecimal + * + * Divide by the specified value. + * + * digits:: If specified and less than the number of significant digits of + * the result, the result is rounded to the given number of digits, + * according to the rounding mode indicated by BigDecimal.mode. + * + * If digits is 0 or omitted, the result is the same as for the + * / operator. + * + * See BigDecimal#/. + * See BigDecimal#div. + */ +static VALUE +BigDecimal_quo(int argc, VALUE *argv, VALUE self) +{ + VALUE value, digits, result; + SIGNED_VALUE n = -1; + + argc = rb_scan_args(argc, argv, "11", &value, &digits); + if (argc > 1) { + n = GetPrecisionInt(digits); + } + + if (n > 0) { + result = BigDecimal_div2(self, value, digits); + } + else { + result = BigDecimal_div(self, value); + } + + return result; +} + /* * %: mod = a%b = a - (a.to_f/b).floor * b * div = (a.to_f/b).floor @@ -1964,6 +1987,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) * Document-method: BigDecimal#div * * call-seq: + * div(value) -> integer * div(value, digits) -> bigdecimal or integer * * Divide by the specified value. @@ -1978,6 +2002,9 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) * If digits is not specified, the result is an integer, * by analogy with Float#div; see also BigDecimal#divmod. * + * See BigDecimal#/. + * See BigDecimal#quo. + * * Examples: * * a = BigDecimal("4") @@ -4272,7 +4299,7 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "-@", BigDecimal_neg, 0); rb_define_method(rb_cBigDecimal, "*", BigDecimal_mult, 1); rb_define_method(rb_cBigDecimal, "/", BigDecimal_div, 1); - rb_define_method(rb_cBigDecimal, "quo", BigDecimal_div, 1); + rb_define_method(rb_cBigDecimal, "quo", BigDecimal_quo, -1); rb_define_method(rb_cBigDecimal, "%", BigDecimal_mod, 1); rb_define_method(rb_cBigDecimal, "modulo", BigDecimal_mod, 1); rb_define_method(rb_cBigDecimal, "remainder", BigDecimal_remainder, 1); @@ -4446,6 +4473,27 @@ VpSetException(unsigned short f) bigdecimal_set_thread_local_exception_mode(f); } +static void +VpCheckException(Real *p, bool always) +{ + if (VpIsNaN(p)) { + VpException(VP_EXCEPTION_NaN, "Computation results in 'NaN' (Not a Number)", always); + } + else if (VpIsPosInf(p)) { + VpException(VP_EXCEPTION_INFINITY, "Computation results in 'Infinity'", always); + } + else if (VpIsNegInf(p)) { + VpException(VP_EXCEPTION_INFINITY, "Computation results in '-Infinity'", always); + } +} + +static VALUE +VpCheckGetValue(Real *p) +{ + VpCheckException(p, false); + return p->obj; +} + /* * Precision limit. */ diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 9e8f0d59..bbfcbec4 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1101,6 +1101,30 @@ def test_div_bigdecimal_with_float_and_precision x.div(y, 100)) end + def test_quo_without_prec + x = BigDecimal(5) + y = BigDecimal(229) + assert_equal(BigDecimal("0.021834061135371179039301310043668122"), x.quo(y)) + end + + def test_quo_with_prec + begin + saved_mode = BigDecimal.mode(BigDecimal::ROUND_MODE) + BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) + + x = BigDecimal(5) + y = BigDecimal(229) + assert_equal(BigDecimal("0.021834061135371179039301310043668122"), x.quo(y, 0)) + assert_equal(BigDecimal("0.022"), x.quo(y, 2)) + assert_equal(BigDecimal("0.0218"), x.quo(y, 3)) + assert_equal(BigDecimal("0.0218341"), x.quo(y, 6)) + assert_equal(BigDecimal("0.02183406114"), x.quo(y, 10)) + assert_equal(BigDecimal("0.021834061135371179039301310043668122270742358078603"), x.quo(y, 50)) + ensure + BigDecimal.mode(BigDecimal::ROUND_MODE, saved_mode) + end + end + def test_abs_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(1267650600228229401496703205376, x.abs) From acabb132a4e8d8947a33ae5c2d53adc8193ff894 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 21 Dec 2021 02:17:16 -0600 Subject: [PATCH 257/546] Enhanced RDoc for BigDecimal (#209) * Enhanced RDoc for BigDecimal * Update ext/bigdecimal/bigdecimal.c Remove the instance number of `Float::DIG`. * Update ext/bigdecimal/bigdecimal.c Add BigDecimal call-seq without ndigits. * Update ext/bigdecimal/bigdecimal.c Replace the word sum with value or result in the description of BigDecimal(). * Update ext/bigdecimal/bigdecimal.c Remove the instance value of Float::DIG. * Update ext/bigdecimal/bigdecimal.c Fix mis-description of precision * Update ext/bigdecimal/bigdecimal.c Fix the description of precision determination * Update ext/bigdecimal/bigdecimal.c Add the description of the precision in the Rational case. Co-authored-by: Kenta Murata <3959+mrkn@users.noreply.github.com> --- ext/bigdecimal/bigdecimal.c | 265 ++++++++++++++++++++++-------------- 1 file changed, 163 insertions(+), 102 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 3be0ffec..5af6c7c5 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -256,7 +256,7 @@ GetVpValue(VALUE v, int must) } /* call-seq: - * BigDecimal.double_fig + * BigDecimal.double_fig -> integer * * Returns the number of digits a Float object is allowed to have; * the result is system-dependent: @@ -524,7 +524,7 @@ BigDecimal_n_significant_digits(VALUE self) /* * call-seq: - * hash -> integer + * hash -> integer * * Returns the integer hash value for +self+. * @@ -555,7 +555,7 @@ BigDecimal_hash(VALUE self) /* * call-seq: - * _dump + * _dump -> string * * Returns a string representing the marshalling of +self+. * See module Marshal. @@ -1236,12 +1236,17 @@ BigDecimal_uplus(VALUE self) /* * call-seq: - * self + value -> new_bigdecimal + * self + value -> bigdecimal * - * Returns the sum of +self+ and +value+: + * Returns the \BigDecimal sum of +self+ and +value+: * * b = BigDecimal('111111.111') # => 0.111111111e6 - * b + 1 # => 0.111112111e6 + * b + 2 # => 0.111113111e6 + * b + 2.0 # => 0.111113111e6 + * b + Rational(2, 1) # => 0.111113111e6 + * b + Complex(2, 0) # => (0.111113111e6+0i) + * + * See the {Note About Precision}[BigDecimal.html#class-BigDecimal-label-A+Note+About+Precision]. * */ @@ -1286,21 +1291,18 @@ BigDecimal_add(VALUE self, VALUE r) return VpCheckGetValue(c); } - /* call-seq: - * a - b -> bigdecimal - * - * Subtract the specified value. - * - * e.g. - * c = a - b + /* call-seq: + * self - value -> bigdecimal * - * The precision of the result value depends on the type of +b+. + * Returns the \BigDecimal difference of +self+ and +value+: * - * If +b+ is a Float, the precision of the result is Float::DIG+1. + * b = BigDecimal('333333.333') # => 0.333333333e6 + * b - 2 # => 0.333331333e6 + * b - 2.0 # => 0.333331333e6 + * b - Rational(2, 1) # => 0.333331333e6 + * b - Complex(2, 0) # => (0.333331333e6+0i) * - * If +b+ is a BigDecimal, the precision of the result is +b+'s precision of - * internal representation from platform. So, it's return value is platform - * dependent. + * See the {Note About Precision}[BigDecimal.html#class-BigDecimal-label-A+Note+About+Precision]. * */ static VALUE @@ -1479,12 +1481,19 @@ BigDecimal_eq(VALUE self, VALUE r) return BigDecimalCmp(self, r, '='); } -/* call-seq: - * a < b +/* call-seq: + * self < other -> true or false + * + * Returns +true+ if +self+ is less than +other+, +false+ otherwise: + * + * b = BigDecimal('1.5') # => 0.15e1 + * b < 2 # => true + * b < 2.0 # => true + * b < Rational(2, 1) # => true + * b < 1.5 # => false * - * Returns true if a is less than b. + * Raises an exception if the comparison cannot be made. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce). */ static VALUE BigDecimal_lt(VALUE self, VALUE r) @@ -1492,12 +1501,20 @@ BigDecimal_lt(VALUE self, VALUE r) return BigDecimalCmp(self, r, '<'); } -/* call-seq: - * a <= b +/* call-seq: + * self <= other -> true or false + * + * Returns +true+ if +self+ is less or equal to than +other+, +false+ otherwise: + * + * b = BigDecimal('1.5') # => 0.15e1 + * b <= 2 # => true + * b <= 2.0 # => true + * b <= Rational(2, 1) # => true + * b <= 1.5 # => true + * b < 1 # => false * - * Returns true if a is less than or equal to b. + * Raises an exception if the comparison cannot be made. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce). */ static VALUE BigDecimal_le(VALUE self, VALUE r) @@ -1505,12 +1522,19 @@ BigDecimal_le(VALUE self, VALUE r) return BigDecimalCmp(self, r, 'L'); } -/* call-seq: - * a > b +/* call-seq: + * self > other -> true or false + * + * Returns +true+ if +self+ is greater than +other+, +false+ otherwise: * - * Returns true if a is greater than b. + * b = BigDecimal('1.5') + * b > 1 # => true + * b > 1.0 # => true + * b > Rational(1, 1) # => true + * b > 2 # => false + * + * Raises an exception if the comparison cannot be made. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce). */ static VALUE BigDecimal_gt(VALUE self, VALUE r) @@ -1518,12 +1542,20 @@ BigDecimal_gt(VALUE self, VALUE r) return BigDecimalCmp(self, r, '>'); } -/* call-seq: - * a >= b +/* call-seq: + * self >= other -> true or false + * + * Returns +true+ if +self+ is greater than or equal to +other+, +false+ otherwise: * - * Returns true if a is greater than or equal to b. + * b = BigDecimal('1.5') + * b >= 1 # => true + * b >= 1.0 # => true + * b >= Rational(1, 1) # => true + * b >= 1.5 # => true + * b > 2 # => false + * + * Raises an exception if the comparison cannot be made. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce) */ static VALUE BigDecimal_ge(VALUE self, VALUE r) @@ -1533,11 +1565,14 @@ BigDecimal_ge(VALUE self, VALUE r) /* * call-seq: - * -big_decimal -> big_decimal + * -self -> bigdecimal + * + * Returns the \BigDecimal negation of self: * - * Return the negation of self. + * b0 = BigDecimal('1.5') + * b1 = -b0 # => -0.15e1 + * b2 = -b1 # => 0.15e1 * - * -BigDecimal('5') #=> -0.5e1 */ static VALUE @@ -1551,21 +1586,6 @@ BigDecimal_neg(VALUE self) return VpCheckGetValue(c); } - /* - * Document-method: BigDecimal#mult - * - * call-seq: mult(value, digits) - * - * Multiply by the specified value. - * - * e.g. - * c = a.mult(b,n) - * c = a * b - * - * digits:: If specified and less than the number of significant digits of the - * result, the result is rounded to that number of digits, according - * to BigDecimal.mode. - */ static VALUE BigDecimal_mult(VALUE self, VALUE r) { @@ -2032,7 +2052,7 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) * call-seq: * add(value, ndigits) -> new_bigdecimal * - * Returns the sum of +self+ and +value+ + * Returns the \BigDecimal sum of +self+ and +value+ * with a precision of +ndigits+ decimal digits. * * When +ndigits+ is less than the number of significant digits @@ -2043,15 +2063,13 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) * * # Set the rounding mode. * BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) - * BigDecimal('111111.111').add(1, 0) # => 0.111112111e6 - * BigDecimal('111111.111').add(1, 2) # => 0.11e6 - * BigDecimal('111111.111').add(1, 3) # => 0.111e6 - * BigDecimal('111111.111').add(1, 4) # => 0.1111e6 - * BigDecimal('111111.111').add(1, 5) # => 0.11111e6 - * BigDecimal('111111.111').add(1, 6) # => 0.111112e6 - * BigDecimal('111111.111').add(1, 7) # => 0.1111121e6 - * BigDecimal('111111.111').add(1, 8) # => 0.11111211e6 - * BigDecimal('111111.111').add(1, 9) # => 0.111112111e6 + * b = BigDecimal('111111.111') + * b.add(1, 0) # => 0.111112111e6 + * b.add(1, 3) # => 0.111e6 + * b.add(1, 6) # => 0.111112e6 + * b.add(1, 15) # => 0.111112111e6 + * b.add(1.0, 15) # => 0.111112111e6 + * b.add(Rational(1, 1), 15) # => 0.111112111e6 * */ @@ -2102,6 +2120,31 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) } } + /* + * call-seq: + * mult(other, ndigits) -> bigdecimal + * + * Returns the \BigDecimal product of +self+ and +value+ + * with a precision of +ndigits+ decimal digits. + * + * When +ndigits+ is less than the number of significant digits + * in the sum, the sum is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. + * + * Examples: + * + * # Set the rounding mode. + * BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) + * b = BigDecimal('555555.555') + * b.mult(3, 0) # => 0.1666666665e7 + * b.mult(3, 3) # => 0.167e7 + * b.mult(3, 6) # => 0.166667e7 + * b.mult(3, 15) # => 0.1666666665e7 + * b.mult(3.0, 0) # => 0.1666666665e7 + * b.mult(Rational(3, 1), 0) # => 0.1666666665e7 + * b.mult(Complex(3, 0), 0) # => (0.1666666665e7+0.0i) + * + */ static VALUE BigDecimal_mult2(VALUE self, VALUE b, VALUE n) @@ -2122,12 +2165,13 @@ BigDecimal_mult2(VALUE self, VALUE b, VALUE n) /* * call-seq: - * big_decimal.abs -> big_decimal + * abs -> bigdecimal * - * Returns the absolute value, as a BigDecimal. + * Returns the \BigDecimal absolute value of +self+: + * + * BigDecimal('5').abs # => 0.5e1 + * BigDecimal('-3').abs # => 0.3e1 * - * BigDecimal('5').abs #=> 0.5e1 - * BigDecimal('-3').abs #=> 0.3e1 */ static VALUE @@ -2973,12 +3017,18 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) return VpCheckGetValue(y); } -/* call-seq: - * a ** n -> bigdecimal +/* call-seq: + * self ** other -> bigdecimal * - * Returns the value raised to the power of n. + * Returns the \BigDecimal value of +self+ raised to power +other+: + * + * b = BigDecimal('3.14') + * b ** 2 # => 0.98596e1 + * b ** 2.0 # => 0.98596e1 + * b ** Rational(2, 1) # => 0.98596e1 + * + * Related: BigDecimal#power. * - * See BigDecimal#power. */ static VALUE BigDecimal_power_op(VALUE self, VALUE exp) @@ -3426,50 +3476,49 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) return rb_str_convert_to_BigDecimal(str, digs, raise_exception); } -/* call-seq: - * BigDecimal(arg, exception: true) - * BigDecimal(arg, digits, exception: true) - * - * Returns arg converted to a BigDecimal. Numeric types are converted - * directly. Other types except for String are first converted to String - * by to_str. Strings can be converted when it has appropriate - * forms of decimal numbers. Exceptions can be suppressed by passing - * exception: false. +/* call-seq: + * BigDecimal(value, exception: true) -> bigdecimal + * BigDecimal(value, ndigits, exception: true) -> bigdecimal * - * When arg is a Float and digits is 0, the number - * of digits is determined by the algorithm of dtoa function - * written by David M. Gay. That algorithm is based on "How to Print Floating- - * Point Numbers Accurately" by Guy L. Steele, Jr. and Jon L. White [Proc. ACM - * SIGPLAN '90, pp. 112-126]. + * Returns the \BigDecimal converted from +value+ + * with a precision of +ndigits+ decimal digits. * - * arg:: The value converted to a BigDecimal. + * When +ndigits+ is less than the number of significant digits + * in the value, the result is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. * - * If it is a String, spaces are ignored and unrecognized characters - * terminate the value. + * Returns +value+ converted to a \BigDecimal, depending on the type of +value+: * - * digits:: The number of significant digits, as an Integer. If omitted, - * the number of significant digits is determined from arg. + * - Integer, Float, Rational, Complex, or BigDecimal: converted directly: * - * The actual number of significant digits used in computation is - * usually larger than the specified number. + * # Integer, Complex, or BigDecimal value does not require ndigits; ignored if given. + * BigDecimal(2) # => 0.2e1 + * BigDecimal(Complex(2, 0)) # => 0.2e1 + * BigDecimal(BigDecimal(2)) # => 0.2e1 + * # Float or Rational value requires ndigits. + * BigDecimal(2.0, 0) # => 0.2e1 + * BigDecimal(Rational(2, 1), 0) # => 0.2e1 * - * exception:: Whether an exception should be raised on invalid arguments. - * +true+ by default, if passed +false+, just returns +nil+ - * for invalid. + * - String: converted by parsing if it contains an integer or floating-point literal; + * leading and trailing whitespace is ignored: * + * # String does not require ndigits; ignored if given. + * BigDecimal('2') # => 0.2e1 + * BigDecimal('2.0') # => 0.2e1 + * BigDecimal('0.2e1') # => 0.2e1 + * BigDecimal(' 2.0 ') # => 0.2e1 * - * ==== Exceptions + * - Other type that responds to method :to_str: + * first converted to a string, then converted to a \BigDecimal, as above. * - * TypeError:: If the +initial+ type is neither Integer, Float, - * Rational, nor BigDecimal, this exception is raised. + * - Other type: * - * TypeError:: If the +digits+ is not an Integer, this exception is raised. + * - Raises an exception if keyword argument +exception+ is +true+. + * - Returns +nil+ if keyword argument +exception+ is +true+. * - * ArgumentError:: If +initial+ is a Float, and the +digits+ is larger than - * Float::DIG + 1, this exception is raised. + * Raises an exception if +value+ evaluates to a Float + * and +digits+ is larger than Float::DIG + 1. * - * ArgumentError:: If the +initial+ is a Float or Rational, and the +digits+ - * value is omitted, this exception is raised. */ static VALUE f_BigDecimal(int argc, VALUE *argv, VALUE self) @@ -4019,6 +4068,18 @@ BigDecimal_negative_zero(void) * * (1.2 - 1.0) == 0.2 #=> false * + * == A Note About Precision + * + * For a calculation using a \BigDecimal and another +value+, + * the precision of the result depends on the type of +value+: + * + * - If +value+ is a \Float, + * the precision is Float::DIG + 1. + * - If +value+ is a \Rational, the precision is larger than Float::DIG + 1. + * - If +value+ is a \BigDecimal, the precision is +value+'s precision in the + * internal representation, which is platform-dependent. + * - If +value+ is other object, the precision is determined by the result of +BigDecimal(value)+. + * * == Special features of accurate decimal arithmetic * * Because BigDecimal is more accurate than normal binary floating point From 5c29b730f508fb88311a2456b960a4f2feb68bef Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 3 May 2021 15:52:18 +0900 Subject: [PATCH 258/546] Add 3.0.2 entry --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5132d6e3..815e655a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,15 @@ # CHANGES +## 3.0.2 + +*This version is totally same as 3.0.0. This was released for reverting 3.0.1.* + +* Revert the changes in 3.0.1 due to remaining bugs. + ## 3.0.1 +*This version is yanked due to the remaining bugs.* + * Improve the conversion speed of BigDecimal() and to_d methods. **Kenta Murata** From 84f4d3bb903e259d2032579e2d1af91c1f0b0b6c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 21 Dec 2021 18:40:20 +0900 Subject: [PATCH 259/546] CHANGES: Add 3.1.0 entries --- CHANGES.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 815e655a..209572c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,29 +1,71 @@ # CHANGES -## 3.0.2 +## 3.1.0 -*This version is totally same as 3.0.0. This was released for reverting 3.0.1.* +* Improve documentation [GH-209] -* Revert the changes in 3.0.1 due to remaining bugs. + **Burdette Lamar** -## 3.0.1 +* Let BigDecimal#quo accept precision. [GH-214] [Bug #8826] -*This version is yanked due to the remaining bugs.* + Reported by Földes László + +* Allow passing both float and precision in BigDecimal#div. [GH-212] [Bug #8826] + + Reported by Földes László + +* Add `BigDecimal#scale` and `BigDecimal#precision_scale` + + **Kenta Murata** + +* Fix a bug of `BigDecimal#precision` for the case that a BigDecimal has single internal digit [GH-205] + + **Kenta Murata** + +* Fix segmentation fault due to a bug of `VpReallocReal` + + **Kenta Murata** + +* Use larger precision in divide for irrational or recurring results. [GH-94] [Bug #13754] -* Improve the conversion speed of BigDecimal() and to_d methods. + Reported by Lionel PERRIN + +* Fix negative Bignum conversion [GH-196] + + **Jean byroot Boussier** + +* Fix grammar in error messages. [GH-196] + + **Olle Jonsson** + +* Improve the conversion speed of `Kernel#BigDecimal` and `to_d` methods. **Kenta Murata** -* Permit 0 digits in BigDecimal(float) and Float#to_d. +* Fix trailing zeros handling in `rb_uint64_convert_to_BigDecimal`. [GH-192] + + Reported by @kamipo + +* Permit 0 digits in `BigDecimal(float)` and `Float#to_d`. It means auto-detection of the smallest number of digits to represent - the given Float number without error. + the given Float number without error. [GH-180] **Kenta Murata** -* Fix precision issue of Float [GH-70] +* Fix precision issue of Float. [GH-70] [Bug #13331] Reported by @casperisfine +## 3.0.2 + +*This version is totally same as 3.0.0. This was released for reverting 3.0.1.* + +* Revert the changes in 3.0.1 due to remaining bugs. + +## 3.0.1 + +*This version is yanked due to the remaining bugs.* + ## 3.0.0 * Deprecate `BigDecimal#precs`. From c3453d2b97dc39e3084ecad29552861acbe3ef21 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 21 Dec 2021 18:40:45 +0900 Subject: [PATCH 260/546] Version 3.1.0 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 1d8b36ee..2b1ff6a6 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "bigdecimal" - s.version = "3.1.0.dev" + s.version = "3.1.0" s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"] From a32f6cb9e23ee04b56bf9b9257c60fabbeeceb2e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 24 Dec 2021 00:26:34 +0900 Subject: [PATCH 261/546] Fix the result precision of BigDecimal#divmod --- ext/bigdecimal/bigdecimal.c | 21 +++++++++++++++++---- test/bigdecimal/test_bigdecimal.rb | 6 ++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 5af6c7c5..4111fbd7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1618,7 +1618,8 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) /* For c = self.div(r): with round operation */ { ENTER(5); - Real *a, *b; + Real *a, *b, *d; + ssize_t a_prec, b_prec; size_t mx; TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); @@ -1644,18 +1645,27 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) TypedData_Get_Struct(rr, Real, &BigDecimal_data_type, b); SAVE(b); - *div = b; + mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; mx *= BASE_FIG; + + BigDecimal_count_precision_and_scale(self, &a_prec, NULL); + BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); + mx = (a_prec > b_prec) ? a_prec : b_prec; + if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; + GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); GUARD_OBJ((*res), VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); VpDivd(*c, *res, a, b); + return Qnil; } +static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod); + /* call-seq: * a / b -> bigdecimal * @@ -1736,6 +1746,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) ENTER(8); Real *c=NULL, *d=NULL, *res=NULL; Real *a, *b; + ssize_t a_prec, b_prec; size_t mx; TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); @@ -1793,8 +1804,10 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) return Qtrue; } - mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; - mx *= BASE_FIG; + BigDecimal_count_precision_and_scale(self, &a_prec, NULL); + BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); + + mx = (a_prec > b_prec) ? a_prec : b_prec; if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index bbfcbec4..825d7ec9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1047,11 +1047,13 @@ def test_divmod_precision a = BigDecimal('2e55') b = BigDecimal('1.23456789e10') q, r = a.divmod(b) - assert_equal((a/b), q) + assert_equal((a/b).round(0, :down), q) + assert_equal((a - q*b), r) b = BigDecimal('-1.23456789e10') q, r = a.divmod(b) - assert_equal((a/b), q) + assert_equal((a/b).round(0, :down) - 1, q) + assert_equal((a - q*b), r) end def test_divmod_error From f8337c55a86c0b5f01f93c85989e27e1fed50524 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 24 Dec 2021 01:19:47 +0900 Subject: [PATCH 262/546] CHANGES: Add 3.1.1 entry --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 209572c4..a30fcac2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.1.1 + +* Fix the result precision of `BigDecimal#divmod`. [GH-219] + + **Kenta Murata** + ## 3.1.0 * Improve documentation [GH-209] From 2ef67c2cc775bc11d0dcd9eee7545f0c37c304cb Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 24 Dec 2021 01:20:15 +0900 Subject: [PATCH 263/546] Version 3.1.1 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 2b1ff6a6..fd49c1b0 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "bigdecimal" - s.version = "3.1.0" + s.version = "3.1.1" s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"] From ac7daa5f15c4583b3cdf7db046e0dcad772360af Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 24 Dec 2021 09:43:22 +0900 Subject: [PATCH 264/546] Remove unused variable --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4111fbd7..f4dcb2ee 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1618,7 +1618,7 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) /* For c = self.div(r): with round operation */ { ENTER(5); - Real *a, *b, *d; + Real *a, *b; ssize_t a_prec, b_prec; size_t mx; From 79c09b4dac66042810463e2a91e803ec4cd82d68 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 19 Jan 2022 14:31:17 +0900 Subject: [PATCH 265/546] Fix typo --- test/bigdecimal/test_bigdecimal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 825d7ec9..11e69282 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -973,7 +973,7 @@ def test_div assert_raise_with_message(FloatDomainError, "Computation results in '-Infinity'") { BigDecimal("-1") / 0 } end - def test_dev_precision + def test_div_precision bug13754 = '[ruby-core:82107] [Bug #13754]' a = BigDecimal('101') b = BigDecimal('0.9163472602589686') From 127a1b5a31c4290945035ec7e07bb7b185a81ee2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 19 Jan 2022 15:53:36 +0900 Subject: [PATCH 266/546] Fix the maximum precision of the quotient Fixes GH-220 --- ext/bigdecimal/bigdecimal.c | 15 +++++++-------- test/bigdecimal/test_bigdecimal.rb | 7 +++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f4dcb2ee..fc74c0be 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1647,18 +1647,16 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) SAVE(b); *div = b; - mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; - mx *= BASE_FIG; - BigDecimal_count_precision_and_scale(self, &a_prec, NULL); BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); mx = (a_prec > b_prec) ? a_prec : b_prec; + mx *= 2; if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); - GUARD_OBJ((*res), VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); + GUARD_OBJ((*res), VpCreateRbObject((mx + 1)*2 + 2*BASE_FIG, "#0", true)); VpDivd(*c, *res, a, b); return Qnil; @@ -1808,6 +1806,8 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); mx = (a_prec > b_prec) ? a_prec : b_prec; + mx *= 2; + if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; @@ -5931,18 +5931,17 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) word_c = c->MaxPrec; word_r = r->MaxPrec; - ind_c = 0; - ind_r = 1; - if (word_a >= word_r) goto space_error; + ind_r = 1; r->frac[0] = 0; while (ind_r <= word_a) { r->frac[ind_r] = a->frac[ind_r - 1]; ++ind_r; } - while (ind_r < word_r) r->frac[ind_r++] = 0; + + ind_c = 0; while (ind_c < word_c) c->frac[ind_c++] = 0; /* initial procedure */ diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 11e69282..0cd85249 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -973,6 +973,13 @@ def test_div assert_raise_with_message(FloatDomainError, "Computation results in '-Infinity'") { BigDecimal("-1") / 0 } end + def test_div_gh220 + x = BigDecimal("1.0") + y = BigDecimal("3672577333.6608990499165058135986328125") + c = BigDecimal("0.272288343892592687909520102748926752911779209181321744700032723729015151607289998e-9") + assert_equal(c, x / y, "[GH-220]") + end + def test_div_precision bug13754 = '[ruby-core:82107] [Bug #13754]' a = BigDecimal('101') From 57e2194135f38db8320db08e7bc8dcc32f7d7e42 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 19 Jan 2022 16:53:26 +0900 Subject: [PATCH 267/546] Version 3.1.2 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index fd49c1b0..2ed7d093 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "bigdecimal" - s.version = "3.1.1" + s.version = "3.1.2" s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"] From 9899d86d86b38473347a5c04d3fcdd1a0e4589df Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 19 Jan 2022 16:55:12 +0900 Subject: [PATCH 268/546] CHANGES: Add 3.1.2 entries --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a30fcac2..f94d6602 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.1.2 + +* Fix the maximum precision of the quotient. [GH-220] + + Reported by @grk + ## 3.1.1 * Fix the result precision of `BigDecimal#divmod`. [GH-219] From 260ad015338650016e057cd03cdb1b6950ef92c0 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Fri, 21 May 2021 12:57:50 -0600 Subject: [PATCH 269/546] Include Ruby 2.7 in table (It wasn't released when this table was first written, but it still applies) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a062bb71..927cce11 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The differences among versions are given below: | ------- | --------------- | ----------------------- | | 3.0.0 | You can use BigDecimal with Ractor on Ruby 3.0 | 2.5 .. | | 2.0.x | You cannot use BigDecimal.new and do subclassing | 2.4 .. | -| 1.4.x | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.6 | +| 1.4.x | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.7 | | 1.3.5 | You can use BigDecimal.new and subclassing without warning | .. 2.5 | You can select the version you want to use using `gem` method in Gemfile or scripts. From 70146fb6ada66eb0ae418603ac61e5e7d68314b3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 14 Jan 2022 00:52:11 +0900 Subject: [PATCH 270/546] Adjust a local variable type to exponent --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index fc74c0be..97510fad 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3281,7 +3281,7 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) VALUE inum; size_t RB_UNUSED_VAR(prec) = 0; - size_t exp = 0; + SIGNED_VALUE exp = 0; if (decpt > 0) { if (decpt < len10) { /* From a18522e9cac13f85946bd25352c1397103482958 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 13 Apr 2022 13:30:31 -0400 Subject: [PATCH 271/546] Fix docs rdoc parses f[i] as a link, which results in a broken link. --- lib/bigdecimal/jacobian.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/bigdecimal/jacobian.rb b/lib/bigdecimal/jacobian.rb index 5e293042..4448024c 100644 --- a/lib/bigdecimal/jacobian.rb +++ b/lib/bigdecimal/jacobian.rb @@ -42,8 +42,8 @@ def isEqual(a,b,zero=0.0,e=1.0e-8) end - # Computes the derivative of f[i] at x[i]. - # fx is the value of f at x. + # Computes the derivative of +f[i]+ at +x[i]+. + # +fx+ is the value of +f+ at +x+. def dfdxi(f,fx,x,i) nRetry = 0 n = x.size @@ -75,7 +75,7 @@ def dfdxi(f,fx,x,i) deriv end - # Computes the Jacobian of f at x. fx is the value of f at x. + # Computes the Jacobian of +f+ at +x+. +fx+ is the value of +f+ at +x+. def jacobian(f,fx,x) n = x.size dfdx = Array.new(n*n) From 3ede8860a6f12df0a7fab0b7f82eb84a1471b176 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 23 Apr 2022 14:45:08 -0500 Subject: [PATCH 272/546] Correct indentation in Kernel#BigDecimal --- ext/bigdecimal/bigdecimal.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 97510fad..b3ef70a2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3493,12 +3493,12 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) * BigDecimal(value, exception: true) -> bigdecimal * BigDecimal(value, ndigits, exception: true) -> bigdecimal * - * Returns the \BigDecimal converted from +value+ - * with a precision of +ndigits+ decimal digits. + * Returns the \BigDecimal converted from +value+ + * with a precision of +ndigits+ decimal digits. * - * When +ndigits+ is less than the number of significant digits - * in the value, the result is rounded to that number of digits, - * according to the current rounding mode; see BigDecimal.mode. + * When +ndigits+ is less than the number of significant digits + * in the value, the result is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. * * Returns +value+ converted to a \BigDecimal, depending on the type of +value+: * From 8ecf99719bf2c178a7bc96d3db31a5d1375c9932 Mon Sep 17 00:00:00 2001 From: Thomas Winsnes Date: Tue, 5 Jul 2022 10:44:46 +1000 Subject: [PATCH 273/546] Updated license information so it matches and the license file and it can now be picked up by automatic license checking tools --- LICENSE.txt => LICENSE | 0 bigdecimal.gemspec | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename LICENSE.txt => LICENSE (100%) diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 2ed7d093..89101f2d 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Arbitrary-precision decimal floating-point number library." s.description = "This library provides arbitrary-precision decimal floating-point number class." s.homepage = "https://github.com/ruby/bigdecimal" - s.license = "Ruby" + s.license = "bsd-2-clause" s.require_paths = %w[lib] s.extensions = %w[ext/bigdecimal/extconf.rb] From 175bbacd43df8929850c0d83772e59c9d6557439 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 14 Jul 2022 15:17:35 +0900 Subject: [PATCH 274/546] Remove checks for `struct RRational` and `struct RComplex` These are used to see only if `RRATIONAL` and `RCOMPLEX` are available, however, these two are macros and can be checked with `#ifdef` directly. --- ext/bigdecimal/extconf.rb | 2 -- ext/bigdecimal/missing.h | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 9b0c55b2..4920374b 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -67,10 +67,8 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_header("ruby/internal/has/builtin.h") have_header("ruby/internal/static_assert.h") -have_type("struct RRational", "ruby.h") have_func("rb_rational_num", "ruby.h") have_func("rb_rational_den", "ruby.h") -have_type("struct RComplex", "ruby.h") have_func("rb_complex_real", "ruby.h") have_func("rb_complex_imag", "ruby.h") have_func("rb_array_const_ptr", "ruby.h") diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index 79698491..49b7c766 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -126,7 +126,7 @@ char *BigDecimal_dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, c static inline VALUE rb_rational_num(VALUE rat) { -#ifdef HAVE_TYPE_STRUCT_RRATIONAL +#ifdef RRATIONAL return RRATIONAL(rat)->num; #else return rb_funcall(rat, rb_intern("numerator"), 0); @@ -138,7 +138,7 @@ rb_rational_num(VALUE rat) static inline VALUE rb_rational_den(VALUE rat) { -#ifdef HAVE_TYPE_STRUCT_RRATIONAL +#ifdef RRATIONAL return RRATIONAL(rat)->den; #else return rb_funcall(rat, rb_intern("denominator"), 0); @@ -152,7 +152,7 @@ rb_rational_den(VALUE rat) static inline VALUE rb_complex_real(VALUE cmp) { -#ifdef HAVE_TYPE_STRUCT_RCOMPLEX +#ifdef RCOMPLEX return RCOMPLEX(cmp)->real; #else return rb_funcall(cmp, rb_intern("real"), 0); @@ -164,7 +164,7 @@ rb_complex_real(VALUE cmp) static inline VALUE rb_complex_imag(VALUE cmp) { -# ifdef HAVE_TYPE_STRUCT_RCOMPLEX +# ifdef RCOMPLEX return RCOMPLEX(cmp)->imag; # else return rb_funcall(cmp, rb_intern("imag"), 0); From c55480b2060cefbffc78a11e50fbd50d2f8ab5e3 Mon Sep 17 00:00:00 2001 From: Thomas Winsnes Date: Tue, 2 Aug 2022 02:22:22 +0200 Subject: [PATCH 275/546] Updated to use multiple licenses Co-authored-by: Hiroshi SHIBATA --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 89101f2d..96e1aa83 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Arbitrary-precision decimal floating-point number library." s.description = "This library provides arbitrary-precision decimal floating-point number class." s.homepage = "https://github.com/ruby/bigdecimal" - s.license = "bsd-2-clause" + s.license = ["Ruby", "bsd-2-clause"] s.require_paths = %w[lib] s.extensions = %w[ext/bigdecimal/extconf.rb] From 13165b29b8f13b1132f5ec22eab2077b54937acc Mon Sep 17 00:00:00 2001 From: Thomas Winsnes Date: Tue, 2 Aug 2022 02:23:34 +0200 Subject: [PATCH 276/546] Updated to use the correct spec for muilti license --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 96e1aa83..1feed332 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Arbitrary-precision decimal floating-point number library." s.description = "This library provides arbitrary-precision decimal floating-point number class." s.homepage = "https://github.com/ruby/bigdecimal" - s.license = ["Ruby", "bsd-2-clause"] + s.licenses = ["Ruby", "bsd-2-clause"] s.require_paths = %w[lib] s.extensions = %w[ext/bigdecimal/extconf.rb] From 741fb8df4e130b136f51acb2e461dea8509fbbc9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Sep 2022 14:31:45 +0900 Subject: [PATCH 277/546] Fixed filenames for test-suite from ruby/ruby --- Rakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index e8667eb2..8fd7c14b 100644 --- a/Rakefile +++ b/Rakefile @@ -47,8 +47,8 @@ task :sync_tool do require 'fileutils' sync_files = [ - "../ruby/tool/lib/test/unit/core_assertions.rb", - "../ruby/tool/lib/test/unit/core_assertions.rb", + "../ruby/tool/lib/core_assertions.rb", + "../ruby/tool/lib/find_executable.rb", "../ruby/tool/lib/envutil.rb" ] From 0b6645ffae6262573af2e6843ca99c0ced4b53ec Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Sep 2022 14:32:27 +0900 Subject: [PATCH 278/546] Update the latest version of core_assertions.rb --- test/lib/core_assertions.rb | 163 +++++++++++++++++------------------- test/lib/envutil.rb | 7 +- 2 files changed, 84 insertions(+), 86 deletions(-) diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index a4abff41..7cd598b1 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -3,6 +3,10 @@ module Test module Unit module Assertions + def assert_raises(*exp, &b) + raise NoMethodError, "use assert_raise", caller + end + def _assertions= n # :nodoc: @_assertions = n end @@ -16,31 +20,24 @@ def _assertions # :nodoc: def message msg = nil, ending = nil, &default proc { - msg = msg.call.chomp(".") if Proc === msg - custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty? - "#{custom_message}#{default.call}#{ending || "."}" + ending ||= (ending_pattern = /(? e - bt = e.backtrace - as = e.instance_of?(MiniTest::Assertion) - if as - ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o - bt.reject! {|ln| ans =~ ln} - end - if ((args.empty? && !as) || - args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a }) - msg = message(msg) { - "Exception raised:\n<#{mu_pp(e)}>\n" + - "Backtrace:\n" + - e.backtrace.map{|frame| " #{frame}"}.join("\n") - } - raise MiniTest::Assertion, msg.call, bt - else - raise - end + rescue *(args.empty? ? Exception : args) => e + msg = message(msg) { + "Exception raised:\n<#{mu_pp(e)}>\n""Backtrace:\n" << + Test.filter_backtrace(e.backtrace).map{|frame| " #{frame}"}.join("\n") + } + raise Test::Unit::AssertionFailedError, msg.call, e.backtrace end end @@ -259,11 +242,11 @@ def assert_ruby_status(args, test_stdin="", message=nil, **opt) ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") - def separated_runner(out = nil) + def separated_runner(token, out = nil) include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) out = out ? IO.new(out, 'w') : STDOUT at_exit { - out.puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" + out.puts "#{token}", [Marshal.dump($!)].pack('m'), "#{token}", "#{token}assertions=#{self._assertions}" } Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner) end @@ -275,22 +258,24 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o line ||= loc.lineno end capture_stdout = true - unless /mswin|mingw/ =~ RUBY_PLATFORM + unless /mswin|mingw/ =~ RbConfig::CONFIG['host_os'] capture_stdout = false - opt[:out] = MiniTest::Unit.output if defined?(MiniTest::Unit) + opt[:out] = Test::Unit::Runner.output if defined?(Test::Unit::Runner) res_p, res_c = IO.pipe opt[:ios] = [res_c] end + token_dump, token_re = new_test_token src = <\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m")) rescue => marshal_error ignore_stderr = nil res = nil @@ -402,8 +387,8 @@ def assert_raise(*exp, &b) begin yield - rescue MiniTest::Skip => e - return e if exp.include? MiniTest::Skip + rescue Test::Unit::PendedError => e + return e if exp.include? Test::Unit::PendedError raise e rescue Exception => e expected = exp.any? { |ex| @@ -478,7 +463,7 @@ def assert_raise_with_message(exception, expected, msg = nil, &block) ex end - MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: + TEST_DIR = File.join(__dir__, "test/unit") #:nodoc: # :call-seq: # assert(test, [failure_message]) @@ -498,7 +483,7 @@ def assert(test, *msgs) when nil msgs.shift else - bt = caller.reject { |s| s.start_with?(MINI_DIR) } + bt = caller.reject { |s| s.start_with?(TEST_DIR) } raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt end unless msgs.empty? super @@ -521,7 +506,7 @@ def assert_respond_to(obj, (meth, *priv), msg = nil) return assert obj.respond_to?(meth, *priv), msg end #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) return if obj.respond_to?(meth) end super(obj, meth, msg) @@ -544,7 +529,7 @@ def assert_not_respond_to(obj, (meth, *priv), msg = nil) return assert !obj.respond_to?(meth, *priv), msg end #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) return unless obj.respond_to?(meth) end refute_respond_to(obj, meth, msg) @@ -563,11 +548,13 @@ def assert_pattern_list(pattern_list, actual, message=nil) anchored = false else if anchored - match = /\A#{pattern}/.match(rest) + match = rest.rindex(pattern, 0) else - match = pattern.match(rest) + match = rest.index(pattern) end - unless match + if match + post_match = $~ ? $~.post_match : rest[match+pattern.size..-1] + else msg = message(msg) { expect_msg = "Expected #{mu_pp pattern}\n" if /\n[^\n]/ =~ rest @@ -584,7 +571,7 @@ def assert_pattern_list(pattern_list, actual, message=nil) } assert false, msg end - rest = match.post_match + rest = post_match anchored = true end } @@ -611,19 +598,20 @@ def assert_warn(*args) def assert_deprecated_warning(mesg = /deprecated/) assert_warning(mesg) do - Warning[:deprecated] = true + Warning[:deprecated] = true if Warning.respond_to?(:[]=) yield end end def assert_deprecated_warn(mesg = /deprecated/) assert_warn(mesg) do - Warning[:deprecated] = true + Warning[:deprecated] = true if Warning.respond_to?(:[]=) yield end end class << (AssertFile = Struct.new(:failure_message).new) + include Assertions include CoreAssertions def assert_file_predicate(predicate, *args) if /\Anot_/ =~ predicate @@ -655,7 +643,7 @@ def initialize def for(key) @count += 1 - yield + yield key rescue Exception => e @failures[key] = [@count, e] end @@ -709,15 +697,25 @@ def assert_join_threads(threads, message = nil) msg = "exceptions on #{errs.length} threads:\n" + errs.map {|t, err| "#{t.inspect}:\n" + - RUBY_VERSION >= "2.5.0" ? err.full_message(highlight: false, order: :top) : err.message + err.full_message(highlight: false, order: :top) }.join("\n---\n") if message msg = "#{message}\n#{msg}" end - raise MiniTest::Assertion, msg + raise Test::Unit::AssertionFailedError, msg end end + def assert_all?(obj, m = nil, &blk) + failed = [] + obj.each do |*a, &b| + unless blk.call(*a, &b) + failed << (a.size > 1 ? a : a[0]) + end + end + assert(failed.empty?, message(m) {failed.pretty_inspect}) + end + def assert_all_assertions(msg = nil) all = AllFailures.new yield all @@ -726,23 +724,13 @@ def assert_all_assertions(msg = nil) end alias all_assertions assert_all_assertions - def message(msg = nil, *args, &default) # :nodoc: - if Proc === msg - super(nil, *args) do - ary = [msg.call, (default.call if default)].compact.reject(&:empty?) - if 1 < ary.length - ary[0...-1] = ary[0...-1].map {|str| str.sub(/(? Date: Mon, 19 Sep 2022 10:15:04 -0400 Subject: [PATCH 279/546] Remove array defs in missing.h for old Rubies Commit 02b6053 added these to support Ruby 2.0.0. The rb_array_const_ptr function is defined since Ruby 2.3. --- ext/bigdecimal/extconf.rb | 1 - ext/bigdecimal/missing.h | 29 ----------------------------- 2 files changed, 30 deletions(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 4920374b..e74196e1 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -71,7 +71,6 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_func("rb_rational_den", "ruby.h") have_func("rb_complex_real", "ruby.h") have_func("rb_complex_imag", "ruby.h") -have_func("rb_array_const_ptr", "ruby.h") have_func("rb_sym2str", "ruby.h") have_func("rb_opts_exception_p", "ruby.h") have_func("rb_category_warn", "ruby.h") diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index 49b7c766..02d2348e 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -172,35 +172,6 @@ rb_complex_imag(VALUE cmp) } #endif -/* array */ - -#ifndef FIX_CONST_VALUE_PTR -# if defined(__fcc__) || defined(__fcc_version) || \ - defined(__FCC__) || defined(__FCC_VERSION) -/* workaround for old version of Fujitsu C Compiler (fcc) */ -# define FIX_CONST_VALUE_PTR(x) ((const VALUE *)(x)) -# else -# define FIX_CONST_VALUE_PTR(x) (x) -# endif -#endif - -#ifndef HAVE_RB_ARRAY_CONST_PTR -static inline const VALUE * -rb_array_const_ptr(VALUE a) -{ - return FIX_CONST_VALUE_PTR((RBASIC(a)->flags & RARRAY_EMBED_FLAG) ? - RARRAY(a)->as.ary : RARRAY(a)->as.heap.ptr); -} -#endif - -#ifndef RARRAY_CONST_PTR -# define RARRAY_CONST_PTR(a) rb_array_const_ptr(a) -#endif - -#ifndef RARRAY_AREF -# define RARRAY_AREF(a, i) (RARRAY_CONST_PTR(a)[i]) -#endif - /* symbol */ #ifndef HAVE_RB_SYM2STR From be366c9cf276dc7e07a1898b4843b349e25992e8 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 19 Sep 2022 10:23:29 -0400 Subject: [PATCH 280/546] Remove symbol defs in missing.h for old Rubies Commit 2885514 added these to support Ruby 2.1. The rb_sym2str function is defined since Ruby 2.2. --- ext/bigdecimal/extconf.rb | 1 - ext/bigdecimal/missing.h | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index e74196e1..17e7905d 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -71,7 +71,6 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_func("rb_rational_den", "ruby.h") have_func("rb_complex_real", "ruby.h") have_func("rb_complex_imag", "ruby.h") -have_func("rb_sym2str", "ruby.h") have_func("rb_opts_exception_p", "ruby.h") have_func("rb_category_warn", "ruby.h") have_const("RB_WARN_CATEGORY_DEPRECATED", "ruby.h") diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index 02d2348e..307147c0 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -172,16 +172,6 @@ rb_complex_imag(VALUE cmp) } #endif -/* symbol */ - -#ifndef HAVE_RB_SYM2STR -static inline VALUE -rb_sym2str(VALUE sym) -{ - return rb_id2str(SYM2ID(sym)); -} -#endif - /* st */ #ifndef ST2FIX From 5415b120ab28bbd52566dc809a767a9b84403dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciek=20Rz=C4=85sa?= Date: Thu, 28 Jul 2022 14:30:10 +0200 Subject: [PATCH 281/546] Improve documentation of BigDecimal#sign Fixes https://github.com/ruby/bigdecimal/issues/78 by describing behaviour for positive and negative zero in the docs. --- ext/bigdecimal/bigdecimal.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b3ef70a2..06c8251c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3606,8 +3606,10 @@ BigDecimal_limit(int argc, VALUE *argv, VALUE self) /* Returns the sign of the value. * - * Returns a positive value if > 0, a negative value if < 0, and a - * zero if == 0. + * Returns a positive value if > 0, a negative value if < 0. + * It behaves the same with zeros - + * it returns a positive value for a positive zero (BigDecimal('0')) and + * a negative value for a negative zero (BigDecimal('-0')). * * The specific value returned indicates the type and sign of the BigDecimal, * as follows: From 223d193f015bc5a3e8b57801a8e1482dc2df7e46 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 25 Sep 2022 23:34:19 +1300 Subject: [PATCH 282/546] Remove trailing whitespace. --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 06c8251c..61a8d4d8 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3607,9 +3607,9 @@ BigDecimal_limit(int argc, VALUE *argv, VALUE self) /* Returns the sign of the value. * * Returns a positive value if > 0, a negative value if < 0. - * It behaves the same with zeros - + * It behaves the same with zeros - * it returns a positive value for a positive zero (BigDecimal('0')) and - * a negative value for a negative zero (BigDecimal('-0')). + * a negative value for a negative zero (BigDecimal('-0')). * * The specific value returned indicates the type and sign of the BigDecimal, * as follows: From 4f0894c6c03dc13e0aa10b6d82a5483f21e148b1 Mon Sep 17 00:00:00 2001 From: Maciej Rzasa Date: Fri, 29 Jul 2022 11:33:23 +0200 Subject: [PATCH 283/546] Document precision=0 and ndigits=0 for converting from Float --- ext/bigdecimal/bigdecimal.c | 3 +++ lib/bigdecimal/util.rb | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 61a8d4d8..1483f327 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3500,6 +3500,9 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) * in the value, the result is rounded to that number of digits, * according to the current rounding mode; see BigDecimal.mode. * + * When +ndigits+ is 0, the number of digits to correctly represent a float number + * is determined automatically. + * * Returns +value+ converted to a \BigDecimal, depending on the type of +value+: * * - Integer, Float, Rational, Complex, or BigDecimal: converted directly: diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index cb645d2a..ad92f7cf 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -33,12 +33,16 @@ class Float < Numeric # # Returns the value of +float+ as a BigDecimal. # The +precision+ parameter is used to determine the number of - # significant digits for the result (the default is Float::DIG). + # significant digits for the result. When +precision+ is set to +0+, + # the number of digits to represent the float being converted is determined + # automatically. + # The default +precision+ is +0+. # # require 'bigdecimal' # require 'bigdecimal/util' # # 0.5.to_d # => 0.5e0 + # 1.234.to_d # => 0.1234e1 # 1.234.to_d(2) # => 0.12e1 # # See also BigDecimal::new. From cd35868aa6f58f9b2f05c181e964672970f19cb5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Oct 2022 22:21:18 +0900 Subject: [PATCH 284/546] Suppress macro redefinition warnings `HAVE_` macros by autoconf are defined as 1. --- ext/bigdecimal/missing.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index 307147c0..325554b5 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -35,10 +35,10 @@ extern "C" { #endif /* RB_UNUSED_VAR */ #if defined(_MSC_VER) && _MSC_VER >= 1310 -# define HAVE___ASSUME +# define HAVE___ASSUME 1 #elif defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1300 -# define HAVE___ASSUME +# define HAVE___ASSUME 1 #endif #ifndef UNREACHABLE From 4ecf04da7a77a5239430db273e5e1971ac99c64f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 10:09:16 +0900 Subject: [PATCH 285/546] Make BigDecimal_double_fig inline --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1483f327..51bef764 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -264,7 +264,7 @@ GetVpValue(VALUE v, int must) * BigDecimal.double_fig # => 16 * */ -static VALUE +static inline VALUE BigDecimal_double_fig(VALUE self) { return INT2FIX(VpDblFig()); From 1b642e2e593d4958dfe92ed7617a17be39743292 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 10:11:27 +0900 Subject: [PATCH 286/546] Make GetVpValue inline --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 51bef764..c8f55bce 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -249,7 +249,7 @@ GetVpValueWithPrec(VALUE v, long prec, int must) return NULL; /* NULL means to coerce */ } -static Real* +static inline Real* GetVpValue(VALUE v, int must) { return GetVpValueWithPrec(v, -1, must); From 91b72a93415c2b63b2919bd1472a7a90a7a6a516 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 10:13:54 +0900 Subject: [PATCH 287/546] [Doc] Fix the document of n_significant_digits --- ext/bigdecimal/bigdecimal.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c8f55bce..20158c27 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -486,15 +486,15 @@ BigDecimal_precision_scale(VALUE self) * * Returns the number of decimal significant digits in +self+. * - * BigDecimal("0").scale # => 0 - * BigDecimal("1").scale # => 1 - * BigDecimal("1.1").scale # => 2 - * BigDecimal("3.1415").scale # => 5 - * BigDecimal("-1e20").precision # => 1 - * BigDecimal("1e-20").precision # => 1 - * BigDecimal("Infinity").scale # => 0 - * BigDecimal("-Infinity").scale # => 0 - * BigDecimal("NaN").scale # => 0 + * BigDecimal("0").n_significant_digits # => 0 + * BigDecimal("1").n_significant_digits # => 1 + * BigDecimal("1.1").n_significant_digits # => 2 + * BigDecimal("3.1415").n_significant_digits # => 5 + * BigDecimal("-1e20").n_significant_digits # => 1 + * BigDecimal("1e-20").n_significant_digits # => 1 + * BigDecimal("Infinity").n_significant_digits # => 0 + * BigDecimal("-Infinity").n_significant_digits # => 0 + * BigDecimal("NaN").n_significant_digits # => 0 */ static VALUE BigDecimal_n_significant_digits(VALUE self) From 23eaff3ae51eca11a81c9232b2b253c1ecd21101 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 10:50:33 +0900 Subject: [PATCH 288/546] Rewrite check_rounding_mode function Use table-lookup algorithm instead of consecutive if-statements. --- ext/bigdecimal/bigdecimal.c | 79 ++++++++++++++++++++----------------- ext/bigdecimal/bigdecimal.h | 37 ++++++++++++----- 2 files changed, 70 insertions(+), 46 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 20158c27..40a825df 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -61,6 +61,13 @@ static ID id_to_r; static ID id_eq; static ID id_half; +#define RBD_NUM_ROUNDING_MODES 11 + +static struct { + ID id; + uint8_t mode; +} rbd_rounding_modes[RBD_NUM_ROUNDING_MODES]; + /* MACRO's to guard objects from GC by keeping them in stack */ #ifdef RBIMPL_ATTR_MAYBE_UNUSED #define ENTER(n) RBIMPL_ATTR_MAYBE_UNUSED() volatile VALUE vStack[n];int iStack=0 @@ -667,34 +674,23 @@ check_rounding_mode(VALUE const v) { unsigned short sw; ID id; - switch (TYPE(v)) { - case T_SYMBOL: - id = SYM2ID(v); - if (id == id_up) - return VP_ROUND_UP; - if (id == id_down || id == id_truncate) - return VP_ROUND_DOWN; - if (id == id_half_up || id == id_default) - return VP_ROUND_HALF_UP; - if (id == id_half_down) - return VP_ROUND_HALF_DOWN; - if (id == id_half_even || id == id_banker) - return VP_ROUND_HALF_EVEN; - if (id == id_ceiling || id == id_ceil) - return VP_ROUND_CEIL; - if (id == id_floor) - return VP_ROUND_FLOOR; - rb_raise(rb_eArgError, "invalid rounding mode"); - - default: - break; + if (RB_TYPE_P(v, T_SYMBOL)) { + int i; + id = SYM2ID(v); + for (i = 0; i < RBD_NUM_ROUNDING_MODES; ++i) { + if (rbd_rounding_modes[i].id == id) { + return rbd_rounding_modes[i].mode; + } + } + rb_raise(rb_eArgError, "invalid rounding mode (%"PRIsVALUE")", v); } - - sw = NUM2USHORT(v); - if (!VpIsRoundMode(sw)) { - rb_raise(rb_eArgError, "invalid rounding mode"); + else { + sw = NUM2USHORT(v); + if (!VpIsRoundMode(sw)) { + rb_raise(rb_eArgError, "invalid rounding mode (%"PRIsVALUE")", v); + } + return sw; } - return sw; } /* call-seq: @@ -4419,17 +4415,26 @@ Init_bigdecimal(void) rb_define_singleton_method(rb_mBigMath, "exp", BigMath_s_exp, 2); rb_define_singleton_method(rb_mBigMath, "log", BigMath_s_log, 2); - id_up = rb_intern_const("up"); - id_down = rb_intern_const("down"); - id_truncate = rb_intern_const("truncate"); - id_half_up = rb_intern_const("half_up"); - id_default = rb_intern_const("default"); - id_half_down = rb_intern_const("half_down"); - id_half_even = rb_intern_const("half_even"); - id_banker = rb_intern_const("banker"); - id_ceiling = rb_intern_const("ceiling"); - id_ceil = rb_intern_const("ceil"); - id_floor = rb_intern_const("floor"); +#define ROUNDING_MODE(i, name, value) \ + id_##name = rb_intern_const(#name); \ + rbd_rounding_modes[i].id = id_##name; \ + rbd_rounding_modes[i].mode = value; + + ROUNDING_MODE(0, up, RBD_ROUND_UP); + ROUNDING_MODE(1, down, RBD_ROUND_DOWN); + ROUNDING_MODE(2, half_up, RBD_ROUND_HALF_UP); + ROUNDING_MODE(3, half_down, RBD_ROUND_HALF_DOWN); + ROUNDING_MODE(4, ceil, RBD_ROUND_CEIL); + ROUNDING_MODE(5, floor, RBD_ROUND_FLOOR); + ROUNDING_MODE(6, half_even, RBD_ROUND_HALF_EVEN); + + ROUNDING_MODE(7, default, RBD_ROUND_DEFAULT); + ROUNDING_MODE(8, truncate, RBD_ROUND_TRUNCATE); + ROUNDING_MODE(9, banker, RBD_ROUND_BANKER); + ROUNDING_MODE(10, ceiling, RBD_ROUND_CEILING); + +#undef ROUNDING_MODE + id_to_r = rb_intern_const("to_r"); id_eq = rb_intern_const("=="); id_half = rb_intern_const("half"); diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index bd1c4674..38dee51e 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -102,7 +102,7 @@ extern VALUE rb_cBigDecimal; */ #define VP_EXPORT static -/* Exception codes */ +/* Exception mode */ #define VP_EXCEPTION_ALL ((unsigned short)0x00FF) #define VP_EXCEPTION_INFINITY ((unsigned short)0x0001) #define VP_EXCEPTION_NaN ((unsigned short)0x0002) @@ -115,18 +115,36 @@ extern VALUE rb_cBigDecimal; #define BIGDECIMAL_EXCEPTION_MODE_DEFAULT 0U -/* Computation mode */ +/* This is used in BigDecimal#mode */ #define VP_ROUND_MODE ((unsigned short)0x0100) -#define VP_ROUND_UP 1 -#define VP_ROUND_DOWN 2 -#define VP_ROUND_HALF_UP 3 -#define VP_ROUND_HALF_DOWN 4 -#define VP_ROUND_CEIL 5 -#define VP_ROUND_FLOOR 6 -#define VP_ROUND_HALF_EVEN 7 + +/* Rounding mode */ +#define VP_ROUND_UP RBD_ROUND_UP +#define VP_ROUND_DOWN RBD_ROUND_DOWN +#define VP_ROUND_HALF_UP RBD_ROUND_HALF_UP +#define VP_ROUND_HALF_DOWN RBD_ROUND_HALF_DOWN +#define VP_ROUND_CEIL RBD_ROUND_CEIL +#define VP_ROUND_FLOOR RBD_ROUND_FLOOR +#define VP_ROUND_HALF_EVEN RBD_ROUND_HALF_EVEN + +enum rbd_rounding_mode { + RBD_ROUND_UP = 1, + RBD_ROUND_DOWN = 2, + RBD_ROUND_HALF_UP = 3, + RBD_ROUND_HALF_DOWN = 4, + RBD_ROUND_CEIL = 5, + RBD_ROUND_FLOOR = 6, + RBD_ROUND_HALF_EVEN = 7, + + RBD_ROUND_DEFAULT = RBD_ROUND_HALF_UP, + RBD_ROUND_TRUNCATE = RBD_ROUND_DOWN, + RBD_ROUND_BANKER = RBD_ROUND_HALF_EVEN, + RBD_ROUND_CEILING = RBD_ROUND_CEIL +}; #define BIGDECIMAL_ROUNDING_MODE_DEFAULT VP_ROUND_HALF_UP +/* Sign flag */ #define VP_SIGN_NaN 0 /* NaN */ #define VP_SIGN_POSITIVE_ZERO 1 /* Positive zero */ #define VP_SIGN_NEGATIVE_ZERO -1 /* Negative zero */ @@ -135,6 +153,7 @@ extern VALUE rb_cBigDecimal; #define VP_SIGN_POSITIVE_INFINITE 3 /* Positive infinite number */ #define VP_SIGN_NEGATIVE_INFINITE -3 /* Negative infinite number */ +/* The size of fraction part array */ #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) #define FLEXIBLE_ARRAY_SIZE /* */ #elif defined(__GNUC__) && !defined(__STRICT_ANSI__) From e1c6c9be25b41436bb1ac49a8cf7c566656624be Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 11:01:09 +0900 Subject: [PATCH 289/546] Tweak check_rounding_mode_option --- ext/bigdecimal/bigdecimal.c | 19 +++++++++---------- test/bigdecimal/test_bigdecimal.rb | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 40a825df..5ae0b96a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -630,18 +630,19 @@ check_rounding_mode_option(VALUE const opts) assert(RB_TYPE_P(opts, T_HASH)); if (NIL_P(opts)) - goto noopt; + goto no_opt; mode = rb_hash_lookup2(opts, ID2SYM(id_half), Qundef); if (mode == Qundef || NIL_P(mode)) - goto noopt; + goto no_opt; if (SYMBOL_P(mode)) mode = rb_sym2str(mode); else if (!RB_TYPE_P(mode, T_STRING)) { - VALUE str_mode = rb_check_string_type(mode); - if (NIL_P(str_mode)) goto invalid; - mode = str_mode; + VALUE str_mode = rb_check_string_type(mode); + if (NIL_P(str_mode)) + goto invalid; + mode = str_mode; } s = RSTRING_PTR(mode); l = RSTRING_LEN(mode); @@ -659,13 +660,11 @@ check_rounding_mode_option(VALUE const opts) default: break; } + invalid: - if (NIL_P(mode)) - rb_raise(rb_eArgError, "invalid rounding mode: nil"); - else - rb_raise(rb_eArgError, "invalid rounding mode: %"PRIsVALUE, mode); + rb_raise(rb_eArgError, "invalid rounding mode (%"PRIsVALUE")", mode); - noopt: + no_opt: return VpGetRoundMode(); } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 0cd85249..02282405 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1370,8 +1370,18 @@ def test_round_half_nil end def test_round_half_invalid_option - assert_raise_with_message(ArgumentError, "invalid rounding mode: invalid") { BigDecimal('12.5').round(half: :invalid) } - assert_raise_with_message(ArgumentError, "invalid rounding mode: invalid") { BigDecimal('2.15').round(1, half: :invalid) } + assert_raise_with_message(ArgumentError, "invalid rounding mode (upp)") do + BigDecimal('12.5').round(half: :upp) + end + assert_raise_with_message(ArgumentError, "invalid rounding mode (evenn)") do + BigDecimal('2.15').round(1, half: :evenn) + end + assert_raise_with_message(ArgumentError, "invalid rounding mode (downn)") do + BigDecimal('2.15').round(1, half: :downn) + end + assert_raise_with_message(ArgumentError, "invalid rounding mode (42)") do + BigDecimal('2.15').round(1, half: 42) + end end def test_truncate From 69d0588a3b6e10262416830337f7d53ecd4358de Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 11:46:00 +0900 Subject: [PATCH 290/546] Twak GetPrecisionInt and rename it to check_int_precision --- ext/bigdecimal/bigdecimal.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 5ae0b96a..0b424f62 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -928,11 +928,17 @@ GetAddSubPrec(Real *a, Real *b) return mx; } -static SIGNED_VALUE -GetPrecisionInt(VALUE v) +static inline SIGNED_VALUE +check_int_precision(VALUE v) { SIGNED_VALUE n; - n = NUM2INT(v); +#if SIZEOF_VALUE <= SIZEOF_LONG + n = (SIGNED_VALUE)NUM2LONG(v); +#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG + n = (SIGNED_VALUE)NUM2LL(v); +#else +# error SIZEOF_VALUE is too large +#endif if (n < 0) { rb_raise(rb_eArgError, "negative precision"); } @@ -1716,7 +1722,7 @@ BigDecimal_quo(int argc, VALUE *argv, VALUE self) argc = rb_scan_args(argc, argv, "11", &value, &digits); if (argc > 1) { - n = GetPrecisionInt(digits); + n = check_int_precision(digits); } if (n > 0) { @@ -1981,7 +1987,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) } /* div in BigDecimal sense */ - ix = GetPrecisionInt(n); + ix = check_int_precision(n); if (ix == 0) { return BigDecimal_div(self, b); } @@ -2086,7 +2092,7 @@ BigDecimal_add2(VALUE self, VALUE b, VALUE n) { ENTER(2); Real *cv; - SIGNED_VALUE mx = GetPrecisionInt(n); + SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_add(self, b); else { size_t pl = VpSetPrecLimit(0); @@ -2116,7 +2122,7 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) { ENTER(2); Real *cv; - SIGNED_VALUE mx = GetPrecisionInt(n); + SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_sub(self, b); else { size_t pl = VpSetPrecLimit(0); @@ -2159,7 +2165,7 @@ BigDecimal_mult2(VALUE self, VALUE b, VALUE n) { ENTER(2); Real *cv; - SIGNED_VALUE mx = GetPrecisionInt(n); + SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_mult(self, b); else { size_t pl = VpSetPrecLimit(0); @@ -2214,7 +2220,8 @@ BigDecimal_sqrt(VALUE self, VALUE nFig) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - n = GetPrecisionInt(nFig) + VpDblFig() + BASE_FIG; + n = check_int_precision(nFig); + n += VpDblFig() + VpBaseFig(); if (mx <= n) mx = n; GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSqrt(c, a); From a5ab34a1159a53555381a7869791efc03e41851c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 12:46:22 +0900 Subject: [PATCH 291/546] Rewrite allocation functions * Rename them * Make allocation count operations atomic --- ext/bigdecimal/bigdecimal.c | 159 ++++++++++++++++++++---------------- ext/bigdecimal/bigdecimal.h | 3 - 2 files changed, 90 insertions(+), 72 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0b424f62..fcb4f460 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -112,6 +112,77 @@ static struct { #define BIGDECIMAL_POSITIVE_P(bd) ((bd)->sign > 0) #define BIGDECIMAL_NEGATIVE_P(bd) ((bd)->sign < 0) +/* + * ================== Memory allocation ============================ + */ + +#ifdef BIGDECIMAL_DEBUG +static size_t rbd_allocation_count = 0; /* Memory allocation counter */ +static inline void +atomic_allocation_count_inc(void) +{ + RUBY_ATOMIC_SIZE_INC(rbd_allocation_count); +} +static inline void +atomic_allocation_count_dec_nounderflow(void) +{ + if (rbd_allocation_count == 0) return; + RUBY_ATOMIC_SIZE_DEC(rbd_allocation_count); +} +static void +check_allocation_count_nonzero(void) +{ + if (rbd_allocation_count != 0) return; + rb_bug("[bigdecimal][rbd_free_struct] Too many memory free calls"); +} +#else +# define atomic_allocation_count_inc() /* nothing */ +# define atomic_allocation_count_dec_nounderflow() /* nothing */ +# define check_allocation_count_nonzero() /* nothing */ +#endif /* BIGDECIMAL_DEBUG */ + +PUREFUNC(static inline size_t rbd_struct_size(size_t const)); + +static inline size_t +rbd_struct_size(size_t const internal_digits) +{ + return offsetof(Real, frac) + sizeof(DECDIG) * internal_digits; +} + +static inline Real * +rbd_allocate_struct(size_t const internal_digits) +{ + size_t const size = rbd_struct_size(internal_digits); + Real *real = ruby_xcalloc(1, size); + atomic_allocation_count_inc(); + return real; +} + +static VALUE BigDecimal_wrap_struct(VALUE obj, Real *vp); + +static inline Real * +rbd_reallocate_struct(Real *real, size_t const internal_digits) +{ + size_t const size = rbd_struct_size(internal_digits); + VALUE obj = real ? real->obj : 0; + Real *new_real = (Real *)ruby_xrealloc(real, size); + if (obj) { + new_real->obj = 0; + BigDecimal_wrap_struct(obj, new_real); + } + return new_real; +} + +static void +rbd_free_struct(Real *real) +{ + if (real != NULL) { + check_allocation_count_nonzero(); + ruby_xfree(real); + atomic_allocation_count_dec_nounderflow(); + } +} + /* * ================== Ruby Interface part ========================== */ @@ -145,7 +216,7 @@ static VALUE BigDecimal_negative_zero(void); static void BigDecimal_delete(void *pv) { - VpFree(pv); + rbd_free_struct(pv); } static size_t @@ -980,26 +1051,12 @@ VpCreateRbObject(size_t mx, const char *str, bool raise_exception) return VpNewRbClass(mx, str, rb_cBigDecimal, true, raise_exception); } -#define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(DECDIG)) - -static Real * -VpReallocReal(Real *pv, size_t prec) -{ - VALUE obj = pv ? pv->obj : 0; - Real *new_pv = (Real *)VpMemRealloc(pv, offsetof(Real, frac) + prec * sizeof(DECDIG)); - if (obj) { - new_pv->obj = 0; - BigDecimal_wrap_struct(obj, new_pv); - } - return new_pv; -} - static Real * VpCopy(Real *pv, Real const* const x) { assert(x != NULL); - pv = VpReallocReal(pv, x->MaxPrec); + pv = rbd_reallocate_struct(pv, x->MaxPrec); pv->MaxPrec = x->MaxPrec; pv->Prec = x->Prec; pv->exponent = x->exponent; @@ -1825,7 +1882,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { /* result adjustment for negative case */ - res = VpReallocReal(res, d->MaxPrec); + res = rbd_reallocate_struct(res, d->MaxPrec); res->MaxPrec = d->MaxPrec; VpAddSub(res, d, VpOne(), -1); GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b) * 2*BASE_FIG, "0", true)); @@ -3116,7 +3173,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r Real *vp; if (uval == 0) { - vp = VpAllocReal(1); + vp = rbd_allocate_struct(1); vp->MaxPrec = 1; vp->Prec = 1; vp->exponent = 1; @@ -3124,7 +3181,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->frac[0] = 0; } else if (uval < BASE) { - vp = VpAllocReal(1); + vp = rbd_allocate_struct(1); vp->MaxPrec = 1; vp->Prec = 1; vp->exponent = 1; @@ -3150,7 +3207,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r } const size_t exp = len + ntz; - vp = VpAllocReal(len); + vp = rbd_allocate_struct(len); vp->MaxPrec = len; vp->Prec = len; vp->exponent = exp; @@ -4494,42 +4551,6 @@ static int VpRdup(Real *m, size_t ind_m); static int gnAlloc = 0; /* Memory allocation counter */ #endif /* BIGDECIMAL_DEBUG */ -VP_EXPORT void * -VpMemAlloc(size_t mb) -{ - void *p = xmalloc(mb); - memset(p, 0, mb); -#ifdef BIGDECIMAL_DEBUG - gnAlloc++; /* Count allocation call */ -#endif /* BIGDECIMAL_DEBUG */ - return p; -} - -VP_EXPORT void * -VpMemRealloc(void *ptr, size_t mb) -{ - return xrealloc(ptr, mb); -} - -VP_EXPORT void -VpFree(Real *pv) -{ - if (pv != NULL) { - xfree(pv); -#ifdef BIGDECIMAL_DEBUG - gnAlloc--; /* Decrement allocation count */ - if (gnAlloc == 0) { - printf(" *************** All memories allocated freed ****************\n"); - /*getchar();*/ - } - if (gnAlloc < 0) { - printf(" ??????????? Too many memory free calls(%d) ?????????????\n", gnAlloc); - /*getchar();*/ - } -#endif /* BIGDECIMAL_DEBUG */ - } -} - /* * EXCEPTION Handling. */ @@ -5009,7 +5030,7 @@ bigdecimal_parse_special_string(const char *str) p = str + table[i].len; while (*p && ISSPACE(*p)) ++p; if (*p == '\0') { - Real *vp = VpAllocReal(1); + Real *vp = rbd_allocate_struct(1); vp->MaxPrec = 1; switch (table[i].sign) { default: @@ -5079,7 +5100,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) /* necessary to be able to store */ /* at least mx digits. */ /* szVal==NULL ==> allocate zero value. */ - vp = VpAllocReal(mx); + vp = rbd_allocate_struct(mx); vp->MaxPrec = mx; /* set max precision */ VpSetZero(vp, 1); /* initialize vp to zero. */ return vp; @@ -5254,7 +5275,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) if (mx == 0) mx = 1; nalloc = Max(nalloc, mx); mx = nalloc; - vp = VpAllocReal(mx); + vp = rbd_allocate_struct(mx); vp->MaxPrec = mx; /* set max precision */ VpSetZero(vp, sign); VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne); @@ -5828,8 +5849,8 @@ VpMult(Real *c, Real *a, Real *b) c->exponent = a->exponent; /* set exponent */ if (!AddExponent(c, b->exponent)) { - if (w) VpFree(c); - return 0; + if (w) rbd_free_struct(c); + return 0; } VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ carry = 0; @@ -5879,10 +5900,10 @@ VpMult(Real *c, Real *a, Real *b) } } if (w != NULL) { /* free work variable */ - VpNmlz(c); - VpAsgn(w, c, 1); - VpFree(c); - c = w; + VpNmlz(c); + VpAsgn(w, c, 1); + rbd_free_struct(c); + c = w; } else { VpLimitRound(c,0); @@ -7047,8 +7068,8 @@ VpSqrt(Real *y, Real *x) y->MaxPrec = y_prec; Exit: - VpFree(f); - VpFree(r); + rbd_free_struct(f); + rbd_free_struct(r); return 1; } @@ -7470,8 +7491,8 @@ VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n) printf(" n=%"PRIdVALUE"\n", n); } #endif /* BIGDECIMAL_DEBUG */ - VpFree(w2); - VpFree(w1); + rbd_free_struct(w2); + rbd_free_struct(w1); return 1; } diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 38dee51e..46fafde1 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -224,9 +224,6 @@ VP_EXPORT int VpIsNegDoubleZero(double v); #endif VP_EXPORT size_t VpNumOfChars(Real *vp,const char *pszFmt); VP_EXPORT size_t VpInit(DECDIG BaseVal); -VP_EXPORT void *VpMemAlloc(size_t mb); -VP_EXPORT void *VpMemRealloc(void *ptr, size_t mb); -VP_EXPORT void VpFree(Real *pv); VP_EXPORT Real *VpAlloc(size_t mx, const char *szVal, int strict_p, int exc); VP_EXPORT size_t VpAsgn(Real *c, Real *a, int isw); VP_EXPORT size_t VpAddSub(Real *c,Real *a,Real *b,int operation); From 14f3d965f8dfe1c92d8ea7704f177b38747a90bf Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 13:26:50 +0900 Subject: [PATCH 292/546] Tweak VpAlloc * Stop reusing mx and mf * Check szVal == NULL first * Treat special values before checking the leading `#` --- ext/bigdecimal/bigdecimal.c | 64 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index fcb4f460..4e4ac991 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -5054,11 +5054,11 @@ bigdecimal_parse_special_string(const char *str) /* * Allocates variable. * [Input] - * mx ... allocation unit, if zero then mx is determined by szVal. - * The mx is the number of effective digits can to be stored. - * szVal ... value assigned(char). If szVal==NULL,then zero is assumed. - * If szVal[0]=='#' then Max. Prec. will not be considered(1.1.7), - * full precision specified by szVal is allocated. + * mx ... The number of decimal digits to be allocated, if zero then mx is determined by szVal. + * The mx will be the number of significant digits can to be stored. + * szVal ... The value assigned(char). If szVal==NULL, then zero is assumed. + * If szVal[0]=='#' then MaxPrec is not affected by the precision limit + * so that the full precision specified by szVal is allocated. * * [Returns] * Pointer to the newly allocated variable, or @@ -5069,48 +5069,48 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) { const char *orig_szVal = szVal; size_t i, j, ni, ipf, nf, ipe, ne, dot_seen, exp_seen, nalloc; + size_t len; char v, *psz; int sign=1; Real *vp = NULL; - size_t mf = VpGetPrecLimit(); + size_t prec_limit = VpGetPrecLimit(); VALUE buf; - mx = (mx + BASE_FIG - 1) / BASE_FIG; /* Determine allocation unit. */ - if (mx == 0) ++mx; + len = (mx + BASE_FIG - 1) / BASE_FIG; /* Determine allocation unit. */ + if (len == 0) ++len; - if (szVal) { - /* Skipping leading spaces */ - while (ISSPACE(*szVal)) szVal++; - - /* Processing the leading one `#` */ - if (*szVal != '#') { - if (mf) { - mf = (mf + BASE_FIG - 1) / BASE_FIG + 2; /* Needs 1 more for div */ - if (mx > mf) { - mx = mf; - } - } - } - else { - ++szVal; - } - } - else { + if (szVal == NULL) { return_zero: /* necessary to be able to store */ /* at least mx digits. */ /* szVal==NULL ==> allocate zero value. */ vp = rbd_allocate_struct(mx); - vp->MaxPrec = mx; /* set max precision */ + vp->MaxPrec = len; /* set max precision */ VpSetZero(vp, 1); /* initialize vp to zero. */ return vp; } + /* Skipping leading spaces */ + while (ISSPACE(*szVal)) szVal++; + /* Check on Inf & NaN */ if ((vp = bigdecimal_parse_special_string(szVal)) != NULL) { return vp; } + /* Processing the leading one `#` */ + if (*szVal != '#') { + if (prec_limit) { + size_t const max_len = (prec_limit + BASE_FIG - 1) / BASE_FIG + 2; /* Needs 1 more for div */ + if (len > max_len) { + len = max_len; + } + } + } + else { + ++szVal; + } + /* Scanning digits */ /* A buffer for keeping scanned digits */ @@ -5272,11 +5272,11 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) nalloc = (ni + nf + BASE_FIG - 1) / BASE_FIG + 1; /* set effective allocation */ /* units for szVal[] */ - if (mx == 0) mx = 1; - nalloc = Max(nalloc, mx); - mx = nalloc; - vp = rbd_allocate_struct(mx); - vp->MaxPrec = mx; /* set max precision */ + if (len == 0) len = 1; + nalloc = Max(nalloc, len); + len = nalloc; + vp = rbd_allocate_struct(len); + vp->MaxPrec = len; /* set max precision */ VpSetZero(vp, sign); VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne); rb_str_resize(buf, 0); From 5391f7e92c0a0f0dc93db0ac910d8ca47f67800c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 14:59:21 +0900 Subject: [PATCH 293/546] Make VPrint function always available --- ext/bigdecimal/bigdecimal.c | 177 ++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 91 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4e4ac991..bad10ae8 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -7,9 +7,7 @@ */ /* #define BIGDECIMAL_DEBUG 1 */ -#ifdef BIGDECIMAL_DEBUG -# define BIGDECIMAL_ENABLE_VPRINT 1 -#endif + #include "bigdecimal.h" #include "ruby/util.h" @@ -198,10 +196,7 @@ static VALUE VpCheckGetValue(Real *p); static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); static Real *VpCopy(Real *pv, Real const* const x); - -#ifdef BIGDECIMAL_ENABLE_VPRINT static int VPrint(FILE *fp,const char *cntl_chr,Real *a); -#endif /* * **** BigDecimal part **** @@ -4501,6 +4496,8 @@ Init_bigdecimal(void) id_to_r = rb_intern_const("to_r"); id_eq = rb_intern_const("=="); id_half = rb_intern_const("half"); + + (void)VPrint; /* suppress unused warning */ } /* @@ -6272,7 +6269,6 @@ VpComp(Real *a, Real *b) * Note: % must not appear more than once * a ... VP variable to be printed */ -#ifdef BIGDECIMAL_ENABLE_VPRINT static int VPrint(FILE *fp, const char *cntl_chr, Real *a) { @@ -6285,95 +6281,94 @@ VPrint(FILE *fp, const char *cntl_chr, Real *a) /* nc : number of characters printed */ ZeroSup = 1; /* Flag not to print the leading zeros as 0.00xxxxEnn */ while (*(cntl_chr + j)) { - if (*(cntl_chr + j) == '%' && *(cntl_chr + j + 1) != '%') { - nc = 0; - if (VpIsNaN(a)) { - fprintf(fp, SZ_NaN); - nc += 8; - } - else if (VpIsPosInf(a)) { - fprintf(fp, SZ_INF); - nc += 8; - } - else if (VpIsNegInf(a)) { - fprintf(fp, SZ_NINF); - nc += 9; - } - else if (!VpIsZero(a)) { - if (BIGDECIMAL_NEGATIVE_P(a)) { - fprintf(fp, "-"); - ++nc; - } - nc += fprintf(fp, "0."); - switch (*(cntl_chr + j + 1)) { - default: - break; + if (*(cntl_chr + j) == '%' && *(cntl_chr + j + 1) != '%') { + nc = 0; + if (VpIsNaN(a)) { + fprintf(fp, SZ_NaN); + nc += 8; + } + else if (VpIsPosInf(a)) { + fprintf(fp, SZ_INF); + nc += 8; + } + else if (VpIsNegInf(a)) { + fprintf(fp, SZ_NINF); + nc += 9; + } + else if (!VpIsZero(a)) { + if (BIGDECIMAL_NEGATIVE_P(a)) { + fprintf(fp, "-"); + ++nc; + } + nc += fprintf(fp, "0."); + switch (*(cntl_chr + j + 1)) { + default: + break; - case '0': case 'z': - ZeroSup = 0; - ++j; - sep = cntl_chr[j] == 'z' ? BIGDECIMAL_COMPONENT_FIGURES : 10; - break; - } - for (i = 0; i < a->Prec; ++i) { - m = BASE1; - e = a->frac[i]; - while (m) { - nn = e / m; - if (!ZeroSup || nn) { - nc += fprintf(fp, "%lu", (unsigned long)nn); /* The leading zero(s) */ - /* as 0.00xx will not */ - /* be printed. */ - ++nd; - ZeroSup = 0; /* Set to print succeeding zeros */ - } - if (nd >= sep) { /* print ' ' after every 10 digits */ - nd = 0; - nc += fprintf(fp, " "); - } - e = e - nn * m; - m /= 10; - } - } - nc += fprintf(fp, "E%"PRIdSIZE, VpExponent10(a)); - nc += fprintf(fp, " (%"PRIdVALUE", %lu, %lu)", a->exponent, a->Prec, a->MaxPrec); - } - else { - nc += fprintf(fp, "0.0"); - } - } - else { - ++nc; - if (*(cntl_chr + j) == '\\') { - switch (*(cntl_chr + j + 1)) { - case 'n': - fprintf(fp, "\n"); - ++j; - break; - case 't': - fprintf(fp, "\t"); - ++j; - break; - case 'b': - fprintf(fp, "\n"); - ++j; - break; - default: - fprintf(fp, "%c", *(cntl_chr + j)); - break; - } - } - else { - fprintf(fp, "%c", *(cntl_chr + j)); - if (*(cntl_chr + j) == '%') ++j; - } - } - j++; + case '0': case 'z': + ZeroSup = 0; + ++j; + sep = cntl_chr[j] == 'z' ? BIGDECIMAL_COMPONENT_FIGURES : 10; + break; + } + for (i = 0; i < a->Prec; ++i) { + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + if (!ZeroSup || nn) { + nc += fprintf(fp, "%lu", (unsigned long)nn); /* The leading zero(s) */ + /* as 0.00xx will not */ + /* be printed. */ + ++nd; + ZeroSup = 0; /* Set to print succeeding zeros */ + } + if (nd >= sep) { /* print ' ' after every 10 digits */ + nd = 0; + nc += fprintf(fp, " "); + } + e = e - nn * m; + m /= 10; + } + } + nc += fprintf(fp, "E%"PRIdSIZE, VpExponent10(a)); + nc += fprintf(fp, " (%"PRIdVALUE", %lu, %lu)", a->exponent, a->Prec, a->MaxPrec); + } + else { + nc += fprintf(fp, "0.0"); + } + } + else { + ++nc; + if (*(cntl_chr + j) == '\\') { + switch (*(cntl_chr + j + 1)) { + case 'n': + fprintf(fp, "\n"); + ++j; + break; + case 't': + fprintf(fp, "\t"); + ++j; + break; + case 'b': + fprintf(fp, "\n"); + ++j; + break; + default: + fprintf(fp, "%c", *(cntl_chr + j)); + break; + } + } + else { + fprintf(fp, "%c", *(cntl_chr + j)); + if (*(cntl_chr + j) == '%') ++j; + } + } + j++; } return (int)nc; } -#endif static void VpFormatSt(char *psz, size_t fFmt) From 40c826f5e6b692458153b4addb41be34dd3f3e27 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 14:59:55 +0900 Subject: [PATCH 294/546] Add and use specific value allocators * Add rbd_allocate_struct_zero for making 0.0 * Add rbd_allocate_struct_one for making 1.0 * Use them to replace VpAlloc calls * Renmae VpPt5 to VpConstPt5 --- ext/bigdecimal/bigdecimal.c | 69 +++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index bad10ae8..a914d995 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -144,7 +144,8 @@ PUREFUNC(static inline size_t rbd_struct_size(size_t const)); static inline size_t rbd_struct_size(size_t const internal_digits) { - return offsetof(Real, frac) + sizeof(DECDIG) * internal_digits; + size_t const frac_len = (internal_digits == 0) ? 1 : internal_digits; + return offsetof(Real, frac) + frac_len * sizeof(DECDIG); } static inline Real * @@ -153,6 +154,7 @@ rbd_allocate_struct(size_t const internal_digits) size_t const size = rbd_struct_size(internal_digits); Real *real = ruby_xcalloc(1, size); atomic_allocation_count_inc(); + real->MaxPrec = internal_digits; return real; } @@ -164,6 +166,7 @@ rbd_reallocate_struct(Real *real, size_t const internal_digits) size_t const size = rbd_struct_size(internal_digits); VALUE obj = real ? real->obj : 0; Real *new_real = (Real *)ruby_xrealloc(real, size); + new_real->MaxPrec = internal_digits; if (obj) { new_real->obj = 0; BigDecimal_wrap_struct(obj, new_real); @@ -181,6 +184,23 @@ rbd_free_struct(Real *real) } } +static Real * +rbd_allocate_struct_zero(size_t const digits, int sign) +{ + size_t const len = roomof(digits, BASE_FIG); + Real *real = rbd_allocate_struct(len); + VpSetZero(real, sign); + return real; +} + +static Real * +rbd_allocate_struct_one(size_t const digits, int sign) +{ + Real *real = rbd_allocate_struct_zero(digits, sign); + VpSetOne(real); + return real; +} + /* * ================== Ruby Interface part ========================== */ @@ -4517,7 +4537,7 @@ static int gfCheckVal = 1; /* Value checking flag in VpNmlz() */ #endif /* BIGDECIMAL_DEBUG */ static Real *VpConstOne; /* constant 1.0 */ -static Real *VpPt5; /* constant 0.5 */ +static Real *VpConstPt5; /* constant 0.5 */ #define maxnr 100UL /* Maximum iterations for calculating sqrt. */ /* used in VpSqrt() */ @@ -4936,9 +4956,13 @@ VpInit(DECDIG BaseVal) /* Setup +/- Inf NaN -0 */ VpGetDoubleNegZero(); - /* Allocates Vp constants. */ - VpConstOne = VpAlloc(1UL, "1", 1, 1); - VpPt5 = VpAlloc(1UL, ".5", 1, 1); + /* Const 1.0 */ + VpConstOne = rbd_allocate_struct_one(1, 1); + + /* Const 0.5 */ + VpConstPt5 = rbd_allocate_struct_one(1, 1); + VpConstPt5->exponent = 0; + VpConstPt5->frac[0] = 5*BASE1; #ifdef BIGDECIMAL_DEBUG gnAlloc = 0; @@ -5838,7 +5862,7 @@ VpMult(Real *c, Real *a, Real *b) if (MxIndC < MxIndAB) { /* The Max. prec. of c < Prec(a)+Prec(b) */ w = c; - c = VpAlloc((size_t)((MxIndAB + 1) * BASE_FIG), "#0", 1, 1); + c = rbd_allocate_struct_zero((size_t)((MxIndAB + 1) * BASE_FIG), 1); MxIndC = MxIndAB; } @@ -7003,8 +7027,9 @@ VpSqrt(Real *y, Real *x) if (x->MaxPrec > (size_t)n) n = (ssize_t)x->MaxPrec; /* allocate temporally variables */ - f = VpAlloc(y->MaxPrec * (BASE_FIG + 2), "#1", 1, 1); - r = VpAlloc((n + n) * (BASE_FIG + 2), "#1", 1, 1); + /* TODO: reconsider MaxPrec of f and r */ + f = rbd_allocate_struct_one(y->MaxPrec * (BASE_FIG + 2), 1); + r = rbd_allocate_struct_one((n + n) * (BASE_FIG + 2), 1); nr = 0; y_prec = y->MaxPrec; @@ -7029,16 +7054,21 @@ VpSqrt(Real *y, Real *x) f->MaxPrec = y->MaxPrec + 1; n = (SIGNED_VALUE)(y_prec * BASE_FIG); if (n < (SIGNED_VALUE)maxnr) n = (SIGNED_VALUE)maxnr; + + /* + * Perform: y_{n+1} = (y_n - x/y_n) / 2 + */ do { - y->MaxPrec *= 2; - if (y->MaxPrec > y_prec) y->MaxPrec = y_prec; - f->MaxPrec = y->MaxPrec; - VpDivd(f, r, x, y); /* f = x/y */ - VpAddSub(r, f, y, -1); /* r = f - y */ - VpMult(f, VpPt5, r); /* f = 0.5*r */ - if (VpIsZero(f)) goto converge; - VpAddSub(r, f, y, 1); /* r = y + f */ - VpAsgn(y, r, 1); /* y = r */ + y->MaxPrec *= 2; + if (y->MaxPrec > y_prec) y->MaxPrec = y_prec; + f->MaxPrec = y->MaxPrec; + VpDivd(f, r, x, y); /* f = x/y */ + VpAddSub(r, f, y, -1); /* r = f - y */ + VpMult(f, VpConstPt5, r); /* f = 0.5*r */ + if (VpIsZero(f)) + goto converge; + VpAddSub(r, f, y, 1); /* r = y + f */ + VpAsgn(y, r, 1); /* y = r */ } while (++nr < n); #ifdef BIGDECIMAL_DEBUG @@ -7455,9 +7485,10 @@ VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n) } /* Allocate working variables */ + /* TODO: reconsider MaxPrec of w1 and w2 */ + w1 = rbd_allocate_struct_zero((y->MaxPrec + 2) * BASE_FIG, 1); + w2 = rbd_allocate_struct_zero((w1->MaxPrec * 2 + 1) * BASE_FIG, 1); - w1 = VpAlloc((y->MaxPrec + 2) * BASE_FIG, "#0", 1, 1); - w2 = VpAlloc((w1->MaxPrec * 2 + 1) * BASE_FIG, "#0", 1, 1); /* calculation start */ VpAsgn(y, x, 1); From 9276a94ac79ccb4ad6899bc65a3c2527c642ece6 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 13 Nov 2022 21:05:46 +0900 Subject: [PATCH 295/546] Add specific value allocators * Add NewZero* and NewOne* function families * Use them instead of VpAlloc for allocating 0 and 1 --- ext/bigdecimal/bigdecimal.c | 481 +++++++++++++++++++++--------------- 1 file changed, 282 insertions(+), 199 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a914d995..a5b80464 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -158,9 +158,33 @@ rbd_allocate_struct(size_t const internal_digits) return real; } -static VALUE BigDecimal_wrap_struct(VALUE obj, Real *vp); +static size_t +rbd_calculate_internal_digits(size_t const digits, bool limit_precision) +{ + size_t const len = roomof(digits, BASE_FIG); + if (limit_precision) { + size_t const prec_limit = VpGetPrecLimit(); + if (prec_limit > 0) { + /* NOTE: 2 more digits for rounding and division */ + size_t const max_len = roomof(prec_limit, BASE_FIG) + 2; + if (len > max_len) + return max_len; + } + } + + return len; +} static inline Real * +rbd_allocate_struct_decimal_digits(size_t const decimal_digits, bool limit_precision) +{ + size_t const internal_digits = rbd_calculate_internal_digits(decimal_digits, limit_precision); + return rbd_allocate_struct(internal_digits); +} + +static VALUE BigDecimal_wrap_struct(VALUE obj, Real *vp); + +static Real * rbd_reallocate_struct(Real *real, size_t const internal_digits) { size_t const size = rbd_struct_size(internal_digits); @@ -184,23 +208,54 @@ rbd_free_struct(Real *real) } } +#define NewZero rbd_allocate_struct_zero static Real * -rbd_allocate_struct_zero(size_t const digits, int sign) +rbd_allocate_struct_zero(int sign, size_t const digits, bool limit_precision) { - size_t const len = roomof(digits, BASE_FIG); - Real *real = rbd_allocate_struct(len); + Real *real = rbd_allocate_struct_decimal_digits(digits, limit_precision); VpSetZero(real, sign); return real; } +#define NewZeroLimited rbd_allocate_struct_zero_limited +static inline Real * +rbd_allocate_struct_zero_limited(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero(sign, digits, true); +} + +#define NewZeroNolimit rbd_allocate_struct_zero_nolimit +static inline Real * +rbd_allocate_struct_zero_nolimit(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero(sign, digits, false); +} + +#define NewOne rbd_allocate_struct_one static Real * -rbd_allocate_struct_one(size_t const digits, int sign) +rbd_allocate_struct_one(int sign, size_t const digits, bool limit_precision) { - Real *real = rbd_allocate_struct_zero(digits, sign); + Real *real = rbd_allocate_struct_decimal_digits(digits, limit_precision); VpSetOne(real); + if (sign < 0) + VpSetSign(real, VP_SIGN_NEGATIVE_FINITE); return real; } +#define NewOneLimited rbd_allocate_struct_one_limited +static inline Real * +rbd_allocate_struct_one_limited(int sign, size_t const digits) +{ + return rbd_allocate_struct_one(sign, digits, true); +} + +#define NewOneNolimit rbd_allocate_struct_one_nolimit +static inline Real * +rbd_allocate_struct_one_nolimit(int sign, size_t const digits) +{ + return rbd_allocate_struct_one(sign, digits, false); +} + /* * ================== Ruby Interface part ========================== */ @@ -254,6 +309,56 @@ static const rb_data_type_t BigDecimal_data_type = { #endif }; +static Real * +rbd_allocate_struct_zero_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_zero(sign, digits, limit_precision); + if (real != NULL) { + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + BigDecimal_wrap_struct(obj, real); + } + return real; +} + +#define NewZeroWrapLimited rbd_allocate_struct_zero_limited_wrap +static inline Real * +rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, true); +} + +#define NewZeroWrapNolimit rbd_allocate_struct_zero_nolimit_wrap +static inline Real * +rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, false); +} + +static Real * +rbd_allocate_struct_one_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_one(sign, digits, limit_precision); + if (real != NULL) { + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + BigDecimal_wrap_struct(obj, real); + } + return real; +} + +#define NewOneWrapLimited rbd_allocate_struct_one_limited_wrap +static inline Real * +rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, true); +} + +#define NewOneWrapNolimit rbd_allocate_struct_one_nolimit_wrap +static inline Real * +rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, false); +} + static inline int is_kind_of_BigDecimal(VALUE const v) { @@ -1349,17 +1454,17 @@ BigDecimal_add(VALUE self, VALUE r) mx = GetAddSubPrec(a, b); if (mx == (size_t)-1L) { - GUARD_OBJ(c, VpCreateRbObject(VpBaseFig() + 1, "0", true)); - VpAddSub(c, a, b, 1); + GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); + VpAddSub(c, a, b, 1); } else { - GUARD_OBJ(c, VpCreateRbObject(mx * (VpBaseFig() + 1), "0", true)); - if(!mx) { - VpSetInf(c, VpGetSign(a)); - } - else { - VpAddSub(c, a, b, 1); - } + GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); + if (!mx) { + VpSetInf(c, VpGetSign(a)); + } + else { + VpAddSub(c, a, b, 1); + } } return VpCheckGetValue(c); } @@ -1404,17 +1509,17 @@ BigDecimal_sub(VALUE self, VALUE r) mx = GetAddSubPrec(a,b); if (mx == (size_t)-1L) { - GUARD_OBJ(c, VpCreateRbObject(VpBaseFig() + 1, "0", true)); - VpAddSub(c, a, b, -1); + GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); + VpAddSub(c, a, b, -1); } else { - GUARD_OBJ(c,VpCreateRbObject(mx *(VpBaseFig() + 1), "0", true)); - if (!mx) { - VpSetInf(c,VpGetSign(a)); - } - else { - VpAddSub(c, a, b, -1); - } + GUARD_OBJ(c, NewZeroWrapLimited(1, mx *(VpBaseFig() + 1))); + if (!mx) { + VpSetInf(c,VpGetSign(a)); + } + else { + VpAddSub(c, a, b, -1); + } } return VpCheckGetValue(c); } @@ -1654,7 +1759,7 @@ BigDecimal_neg(VALUE self) ENTER(5); Real *c, *a; GUARD_OBJ(a, GetVpValue(self, 1)); - GUARD_OBJ(c, VpCreateRbObject(a->Prec *(VpBaseFig() + 1), "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, a->Prec *(VpBaseFig() + 1))); VpAsgn(c, a, -1); return VpCheckGetValue(c); } @@ -1681,7 +1786,7 @@ BigDecimal_mult(VALUE self, VALUE r) SAVE(b); mx = a->Prec + b->Prec; - GUARD_OBJ(c, VpCreateRbObject(mx *(VpBaseFig() + 1), "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); VpMult(c, a, b); return VpCheckGetValue(c); } @@ -1728,8 +1833,8 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; - GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); - GUARD_OBJ((*res), VpCreateRbObject((mx + 1)*2 + 2*BASE_FIG, "#0", true)); + GUARD_OBJ((*c), NewZeroWrapNolimit(1, mx + 2*BASE_FIG)); + GUARD_OBJ((*res), NewZeroWrapNolimit(1, (mx + 1)*2 + 2*BASE_FIG)); VpDivd(*c, *res, a, b); return Qnil; @@ -1884,12 +1989,12 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; - GUARD_OBJ(c, VpCreateRbObject(mx + 2*BASE_FIG, "0", true)); - GUARD_OBJ(res, VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx + 2*BASE_FIG)); + GUARD_OBJ(res, NewZeroWrapNolimit(1, mx*2 + 2*BASE_FIG)); VpDivd(c, res, a, b); mx = c->Prec * BASE_FIG; - GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(d, NewZeroWrapLimited(1, mx)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); VpMult(res, d, b); @@ -1900,7 +2005,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) res = rbd_reallocate_struct(res, d->MaxPrec); res->MaxPrec = d->MaxPrec; VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b) * 2*BASE_FIG, "0", true)); + GUARD_OBJ(d, NewZeroWrapLimited(1, GetAddSubPrec(c, b) * 2*BASE_FIG)); VpAddSub(d, c, b, 1); *div = res; *mod = d; @@ -1964,17 +2069,17 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) SAVE(b); mx = (a->MaxPrec + b->MaxPrec) *VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); - GUARD_OBJ(rr, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); - GUARD_OBJ(ff, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + GUARD_OBJ(res, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); + GUARD_OBJ(rr, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); + GUARD_OBJ(ff, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); VpDivd(c, res, a, b); mx = c->Prec *(VpBaseFig() + 1); - GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); - GUARD_OBJ(f, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(d, NewZeroWrapLimited(1, mx)); + GUARD_OBJ(f, NewZeroWrapLimited(1, mx)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); /* 0: round off */ @@ -2070,7 +2175,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) size_t b_prec = ix; size_t pl = VpSetPrecLimit(0); - GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0", true)); + GUARD_OBJ(cv, NewZeroWrapLimited(1, mx + VpBaseFig())); GUARD_OBJ(av, GetVpValue(self, 1)); /* TODO: I want to refactor this precision control for a float value later * by introducing an implicit conversion function instead of @@ -2081,7 +2186,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) GUARD_OBJ(bv, GetVpValueWithPrec(b, b_prec, 1)); mx = av->Prec + bv->Prec + 2; if (mx <= cv->MaxPrec) mx = cv->MaxPrec + 1; - GUARD_OBJ(res, VpCreateRbObject((mx * 2 + 2)*VpBaseFig(), "#0", true)); + GUARD_OBJ(res, NewZeroWrapNolimit(1, (mx * 2 + 2)*VpBaseFig())); VpDivd(cv, res, av, bv); VpSetPrecLimit(pl); VpLeftRound(cv, VpGetRoundMode(), ix); @@ -2269,7 +2374,7 @@ BigDecimal_abs(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec *(VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpAsgn(c, a, 1); VpChangeSign(c, 1); return VpCheckGetValue(c); @@ -2295,7 +2400,7 @@ BigDecimal_sqrt(VALUE self, VALUE nFig) n = check_int_precision(nFig); n += VpDblFig() + VpBaseFig(); if (mx <= n) mx = n; - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSqrt(c, a); return VpCheckGetValue(c); } @@ -2311,7 +2416,7 @@ BigDecimal_fix(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec *(VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpActiveRound(c, a, VP_ROUND_DOWN, 0); /* 0: round off */ return VpCheckGetValue(c); } @@ -2384,7 +2489,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) pl = VpSetPrecLimit(0); GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c, a, sw, iLoc); if (round_to_int) { @@ -2430,7 +2535,7 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_DOWN, iLoc); /* 0: truncate */ if (argc == 0) { @@ -2450,7 +2555,7 @@ BigDecimal_frac(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpFrac(c, a); return VpCheckGetValue(c); } @@ -2490,7 +2595,7 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_FLOOR, iLoc); #ifdef BIGDECIMAL_DEBUG @@ -2536,7 +2641,7 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_CEIL, iLoc); if (argc == 0) { @@ -2844,7 +2949,7 @@ bigdecimal_power_by_bigdecimal(Real const* x, Real const* exp, ssize_t const n) volatile VALUE obj = exp->obj; if (VpIsZero(exp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1", true)); + return VpCheckGetValue(NewOneWrapLimited(1, n)); } log_x = BigMath_log(x->obj, SSIZET2NUM(n+1)); @@ -2882,9 +2987,9 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) n = NIL_P(prec) ? (ssize_t)(x->Prec*VpBaseFig()) : NUM2SSIZET(prec); if (VpIsNaN(x)) { - y = VpCreateRbObject(n, "0", true); - RB_GC_GUARD(y->obj); - VpSetNaN(y); + y = NewZeroWrapLimited(1, n); + VpSetNaN(y); + RB_GC_GUARD(y->obj); return VpCheckGetValue(y); } @@ -2953,136 +3058,126 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) } if (VpIsZero(x)) { - if (is_negative(vexp)) { - y = VpCreateRbObject(n, "#0", true); - RB_GC_GUARD(y->obj); - if (BIGDECIMAL_NEGATIVE_P(x)) { - if (is_integer(vexp)) { - if (is_even(vexp)) { - /* (-0) ** (-even_integer) -> Infinity */ - VpSetPosInf(y); - } - else { - /* (-0) ** (-odd_integer) -> -Infinity */ - VpSetNegInf(y); - } - } - else { - /* (-0) ** (-non_integer) -> Infinity */ - VpSetPosInf(y); - } - } - else { - /* (+0) ** (-num) -> Infinity */ - VpSetPosInf(y); - } + if (is_negative(vexp)) { + y = NewZeroWrapNolimit(1, n); + if (BIGDECIMAL_NEGATIVE_P(x)) { + if (is_integer(vexp)) { + if (is_even(vexp)) { + /* (-0) ** (-even_integer) -> Infinity */ + VpSetPosInf(y); + } + else { + /* (-0) ** (-odd_integer) -> -Infinity */ + VpSetNegInf(y); + } + } + else { + /* (-0) ** (-non_integer) -> Infinity */ + VpSetPosInf(y); + } + } + else { + /* (+0) ** (-num) -> Infinity */ + VpSetPosInf(y); + } + RB_GC_GUARD(y->obj); return VpCheckGetValue(y); - } - else if (is_zero(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1", true)); - } - else { - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } + } + else if (is_zero(vexp)) { + return VpCheckGetValue(NewOneWrapLimited(1, n)); + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } } if (is_zero(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1", true)); + return VpCheckGetValue(NewOneWrapLimited(1, n)); } else if (is_one(vexp)) { - return self; + return self; } if (VpIsInf(x)) { - if (is_negative(vexp)) { - if (BIGDECIMAL_NEGATIVE_P(x)) { - if (is_integer(vexp)) { - if (is_even(vexp)) { - /* (-Infinity) ** (-even_integer) -> +0 */ - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } - else { - /* (-Infinity) ** (-odd_integer) -> -0 */ - return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); - } - } - else { - /* (-Infinity) ** (-non_integer) -> -0 */ - return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); - } - } - else { - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } - } - else { - y = VpCreateRbObject(n, "0", true); - if (BIGDECIMAL_NEGATIVE_P(x)) { - if (is_integer(vexp)) { - if (is_even(vexp)) { - VpSetPosInf(y); - } - else { - VpSetNegInf(y); - } - } - else { - /* TODO: support complex */ - rb_raise(rb_eMathDomainError, - "a non-integral exponent for a negative base"); - } - } - else { - VpSetPosInf(y); - } + if (is_negative(vexp)) { + if (BIGDECIMAL_NEGATIVE_P(x)) { + if (is_integer(vexp)) { + if (is_even(vexp)) { + /* (-Infinity) ** (-even_integer) -> +0 */ + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + else { + /* (-Infinity) ** (-odd_integer) -> -0 */ + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + } + else { + /* (-Infinity) ** (-non_integer) -> -0 */ + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } + else { + y = NewZeroWrapLimited(1, n); + if (BIGDECIMAL_NEGATIVE_P(x)) { + if (is_integer(vexp)) { + if (is_even(vexp)) { + VpSetPosInf(y); + } + else { + VpSetNegInf(y); + } + } + else { + /* TODO: support complex */ + rb_raise(rb_eMathDomainError, + "a non-integral exponent for a negative base"); + } + } + else { + VpSetPosInf(y); + } return VpCheckGetValue(y); - } + } } if (exp != NULL) { - return bigdecimal_power_by_bigdecimal(x, exp, n); + return bigdecimal_power_by_bigdecimal(x, exp, n); } else if (RB_TYPE_P(vexp, T_BIGNUM)) { - VALUE abs_value = BigDecimal_abs(self); - if (is_one(abs_value)) { - return VpCheckGetValue(VpCreateRbObject(n, "1", true)); - } - else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { - if (is_negative(vexp)) { - y = VpCreateRbObject(n, "0", true); - if (is_even(vexp)) { - VpSetInf(y, VpGetSign(x)); - } - else { - VpSetInf(y, -VpGetSign(x)); - } + VALUE abs_value = BigDecimal_abs(self); + if (is_one(abs_value)) { + return VpCheckGetValue(NewOneWrapLimited(1, n)); + } + else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { + if (is_negative(vexp)) { + y = NewZeroWrapLimited(1, n); + VpSetInf(y, (is_even(vexp) ? 1 : -1) * VpGetSign(x)); return VpCheckGetValue(y); - } - else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); - } - else { - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } - } - else { - if (is_positive(vexp)) { - y = VpCreateRbObject(n, "0", true); - if (is_even(vexp)) { - VpSetInf(y, VpGetSign(x)); - } - else { - VpSetInf(y, -VpGetSign(x)); - } + } + else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } + else { + if (is_positive(vexp)) { + y = NewZeroWrapLimited(1, n); + VpSetInf(y, (is_even(vexp) ? 1 : -1) * VpGetSign(x)); return VpCheckGetValue(y); - } - else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); - } - else { - return VpCheckGetValue(VpCreateRbObject(n, "0", true)); - } - } + } + else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } } int_exp = FIX2LONG(vexp); @@ -3091,15 +3186,15 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (ma == 0) ma = 1; if (VpIsDef(x)) { - mp = x->Prec * (VpBaseFig() + 1); - GUARD_OBJ(y, VpCreateRbObject(mp * (ma + 1), "0", true)); + mp = x->Prec * (VpBaseFig() + 1); + GUARD_OBJ(y, NewZeroWrapLimited(1, mp * (ma + 1))); } else { - GUARD_OBJ(y, VpCreateRbObject(1, "0", true)); + GUARD_OBJ(y, NewZeroWrapLimited(1, 1)); } VpPowerByInt(y, x, int_exp); if (!NIL_P(prec) && VpIsDef(y)) { - VpMidRound(y, VpGetRoundMode(), n); + VpMidRound(y, VpGetRoundMode(), n); } return VpCheckGetValue(y); } @@ -3850,18 +3945,16 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) return VpCheckGetValue(GetVpValueWithPrec(INT2FIX(0), prec, 1)); } else { - Real* vy; - vy = VpCreateRbObject(prec, "#0", true); + Real* vy = NewZeroWrapNolimit(1, prec); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); RB_GC_GUARD(vy->obj); return VpCheckGetValue(vy); } } else if (nan) { - Real* vy; - vy = VpCreateRbObject(prec, "#0", true); - VpSetNaN(vy); - RB_GC_GUARD(vy->obj); + Real* vy = NewZeroWrapNolimit(1, prec); + VpSetNaN(vy); + RB_GC_GUARD(vy->obj); return VpCheckGetValue(vy); } else if (vx == NULL) { @@ -3879,7 +3972,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) VpSetSign(vx, 1); } - one = VpCheckGetValue(VpCreateRbObject(1, "1", true)); + one = VpCheckGetValue(NewOneWrapLimited(1, 1)); y = one; d = y; i = 1; @@ -4006,15 +4099,13 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) break; } if (infinite && !negative) { - Real* vy; - vy = VpCreateRbObject(prec, "#0", true); + Real *vy = NewZeroWrapNolimit(1, prec); RB_GC_GUARD(vy->obj); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); return VpCheckGetValue(vy); } else if (nan) { - Real* vy; - vy = VpCreateRbObject(prec, "#0", true); + Real* vy = NewZeroWrapNolimit(1, prec); RB_GC_GUARD(vy->obj); VpSetNaN(vy); return VpCheckGetValue(vy); @@ -4028,7 +4119,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) } x = VpCheckGetValue(vx); - RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1", true)); + RB_GC_GUARD(one) = VpCheckGetValue(NewOneWrapLimited(1, 1)); RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2", true)); n = prec + BIGDECIMAL_DOUBLE_FIGURES; @@ -4957,10 +5048,10 @@ VpInit(DECDIG BaseVal) VpGetDoubleNegZero(); /* Const 1.0 */ - VpConstOne = rbd_allocate_struct_one(1, 1); + VpConstOne = NewOneNolimit(1, 1); /* Const 0.5 */ - VpConstPt5 = rbd_allocate_struct_one(1, 1); + VpConstPt5 = NewOneNolimit(1, 1); VpConstPt5->exponent = 0; VpConstPt5->frac[0] = 5*BASE1; @@ -5094,19 +5185,15 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) char v, *psz; int sign=1; Real *vp = NULL; - size_t prec_limit = VpGetPrecLimit(); VALUE buf; - len = (mx + BASE_FIG - 1) / BASE_FIG; /* Determine allocation unit. */ - if (len == 0) ++len; - if (szVal == NULL) { return_zero: /* necessary to be able to store */ /* at least mx digits. */ /* szVal==NULL ==> allocate zero value. */ vp = rbd_allocate_struct(mx); - vp->MaxPrec = len; /* set max precision */ + vp->MaxPrec = rbd_calculate_internal_digits(mx, false); /* Must false */ VpSetZero(vp, 1); /* initialize vp to zero. */ return vp; } @@ -5121,14 +5208,10 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) /* Processing the leading one `#` */ if (*szVal != '#') { - if (prec_limit) { - size_t const max_len = (prec_limit + BASE_FIG - 1) / BASE_FIG + 2; /* Needs 1 more for div */ - if (len > max_len) { - len = max_len; - } - } + len = rbd_calculate_internal_digits(mx, true); } else { + len = rbd_calculate_internal_digits(mx, false); ++szVal; } @@ -5862,7 +5945,7 @@ VpMult(Real *c, Real *a, Real *b) if (MxIndC < MxIndAB) { /* The Max. prec. of c < Prec(a)+Prec(b) */ w = c; - c = rbd_allocate_struct_zero((size_t)((MxIndAB + 1) * BASE_FIG), 1); + c = NewZeroNolimit(1, (size_t)((MxIndAB + 1) * BASE_FIG)); MxIndC = MxIndAB; } @@ -7028,8 +7111,8 @@ VpSqrt(Real *y, Real *x) /* allocate temporally variables */ /* TODO: reconsider MaxPrec of f and r */ - f = rbd_allocate_struct_one(y->MaxPrec * (BASE_FIG + 2), 1); - r = rbd_allocate_struct_one((n + n) * (BASE_FIG + 2), 1); + f = NewOneNolimit(1, y->MaxPrec * (BASE_FIG + 2)); + r = NewOneNolimit(1, (n + n) * (BASE_FIG + 2)); nr = 0; y_prec = y->MaxPrec; @@ -7486,8 +7569,8 @@ VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n) /* Allocate working variables */ /* TODO: reconsider MaxPrec of w1 and w2 */ - w1 = rbd_allocate_struct_zero((y->MaxPrec + 2) * BASE_FIG, 1); - w2 = rbd_allocate_struct_zero((w1->MaxPrec * 2 + 1) * BASE_FIG, 1); + w1 = NewZeroNolimit(1, (y->MaxPrec + 2) * BASE_FIG); + w2 = NewZeroNolimit(1, (w1->MaxPrec * 2 + 1) * BASE_FIG); /* calculation start */ From d70a4d53e5b63d4512830ecf0e04dbfae8b02130 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 15 Nov 2022 09:32:53 +0900 Subject: [PATCH 296/546] Mark some functions MAYBE_UNUSED --- ext/bigdecimal/bigdecimal.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a5b80464..c85ef159 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -217,6 +217,7 @@ rbd_allocate_struct_zero(int sign, size_t const digits, bool limit_precision) return real; } +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_limited(int sign, size_t const digits)); #define NewZeroLimited rbd_allocate_struct_zero_limited static inline Real * rbd_allocate_struct_zero_limited(int sign, size_t const digits) @@ -224,6 +225,7 @@ rbd_allocate_struct_zero_limited(int sign, size_t const digits) return rbd_allocate_struct_zero(sign, digits, true); } +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_nolimit(int sign, size_t const digits)); #define NewZeroNolimit rbd_allocate_struct_zero_nolimit static inline Real * rbd_allocate_struct_zero_nolimit(int sign, size_t const digits) @@ -242,6 +244,7 @@ rbd_allocate_struct_one(int sign, size_t const digits, bool limit_precision) return real; } +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_limited(int sign, size_t const digits)); #define NewOneLimited rbd_allocate_struct_one_limited static inline Real * rbd_allocate_struct_one_limited(int sign, size_t const digits) @@ -249,6 +252,7 @@ rbd_allocate_struct_one_limited(int sign, size_t const digits) return rbd_allocate_struct_one(sign, digits, true); } +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit(int sign, size_t const digits)); #define NewOneNolimit rbd_allocate_struct_one_nolimit static inline Real * rbd_allocate_struct_one_nolimit(int sign, size_t const digits) @@ -320,6 +324,7 @@ rbd_allocate_struct_zero_wrap_klass(VALUE klass, int sign, size_t const digits, return real; } +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits)); #define NewZeroWrapLimited rbd_allocate_struct_zero_limited_wrap static inline Real * rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits) @@ -327,6 +332,7 @@ rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits) return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, true); } +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits)); #define NewZeroWrapNolimit rbd_allocate_struct_zero_nolimit_wrap static inline Real * rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits) @@ -345,6 +351,7 @@ rbd_allocate_struct_one_wrap_klass(VALUE klass, int sign, size_t const digits, b return real; } +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits)); #define NewOneWrapLimited rbd_allocate_struct_one_limited_wrap static inline Real * rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits) @@ -352,6 +359,7 @@ rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits) return rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, true); } +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits)); #define NewOneWrapNolimit rbd_allocate_struct_one_nolimit_wrap static inline Real * rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits) From d6f5bb40c7598a487b468460294db66d8fc955f5 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 15 Nov 2022 13:01:50 +0900 Subject: [PATCH 297/546] Replace sprintf by snprintf --- ext/bigdecimal/bigdecimal.c | 302 ++++++++++++++++++++++-------------- ext/bigdecimal/bigdecimal.h | 8 +- 2 files changed, 189 insertions(+), 121 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c85ef159..9b1d33ed 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -420,7 +420,7 @@ GetVpValueWithPrec(VALUE v, long prec, int must) case T_FIXNUM: { char szD[128]; - sprintf(szD, "%ld", FIX2LONG(v)); + snprintf(szD, 128, "%ld", FIX2LONG(v)); v = rb_cstr_convert_to_BigDecimal(szD, VpBaseFig() * 2 + 1, must); break; } @@ -779,13 +779,15 @@ BigDecimal_dump(int argc, VALUE *argv, VALUE self) char *psz; VALUE dummy; volatile VALUE dump; + size_t len; rb_scan_args(argc, argv, "01", &dummy); GUARD_OBJ(vp,GetVpValue(self, 1)); dump = rb_str_new(0, VpNumOfChars(vp, "E")+50); psz = RSTRING_PTR(dump); - sprintf(psz, "%"PRIuSIZE":", VpMaxPrec(vp)*VpBaseFig()); - VpToString(vp, psz+strlen(psz), 0, 0); + snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", VpMaxPrec(vp)*VpBaseFig()); + len = strlen(psz); + VpToString(vp, psz+len, RSTRING_LEN(dump)-len, 0, 0); rb_str_resize(dump, strlen(psz)); return dump; } @@ -1305,7 +1307,7 @@ BigDecimal_to_f(VALUE self) str = rb_str_new(0, VpNumOfChars(p, "E")); buf = RSTRING_PTR(str); - VpToString(p, buf, 0, 0); + VpToString(p, buf, RSTRING_LEN(str), 0, 0); errno = 0; d = strtod(buf, 0); if (errno == ERANGE) { @@ -2753,10 +2755,10 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) psz = RSTRING_PTR(str); if (fmt) { - VpToFString(vp, psz, mc, fPlus); + VpToFString(vp, psz, RSTRING_LEN(str), mc, fPlus); } else { - VpToString (vp, psz, mc, fPlus); + VpToString (vp, psz, RSTRING_LEN(str), mc, fPlus); } rb_str_resize(str, strlen(psz)); return str; @@ -2798,7 +2800,7 @@ BigDecimal_split(VALUE self) GUARD_OBJ(vp, GetVpValue(self, 1)); str = rb_str_new(0, VpNumOfChars(vp, "E")); psz1 = RSTRING_PTR(str); - VpSzMantissa(vp, psz1); + VpSzMantissa(vp, psz1, RSTRING_LEN(str)); s = 1; if(psz1[0] == '-') { size_t len = strlen(psz1 + 1); @@ -2847,7 +2849,7 @@ BigDecimal_inspect(VALUE self) nc = VpNumOfChars(vp, "E"); str = rb_str_new(0, nc); - VpToString(vp, RSTRING_PTR(str), 0, 0); + VpToString(vp, RSTRING_PTR(str), RSTRING_LEN(str), 0, 0); rb_str_resize(str, strlen(RSTRING_PTR(str))); return str; } @@ -6528,188 +6530,254 @@ VpExponent10(Real *a) } VP_EXPORT void -VpSzMantissa(Real *a,char *psz) +VpSzMantissa(Real *a, char *buf, size_t buflen) { size_t i, n, ZeroSup; DECDIG_DBL m, e, nn; if (VpIsNaN(a)) { - sprintf(psz, SZ_NaN); - return; + snprintf(buf, buflen, SZ_NaN); + return; } if (VpIsPosInf(a)) { - sprintf(psz, SZ_INF); + snprintf(buf, buflen, SZ_INF); return; } if (VpIsNegInf(a)) { - sprintf(psz, SZ_NINF); + snprintf(buf, buflen, SZ_NINF); return; } ZeroSup = 1; /* Flag not to print the leading zeros as 0.00xxxxEnn */ if (!VpIsZero(a)) { - if (BIGDECIMAL_NEGATIVE_P(a)) *psz++ = '-'; - n = a->Prec; - for (i = 0; i < n; ++i) { - m = BASE1; - e = a->frac[i]; - while (m) { - nn = e / m; - if (!ZeroSup || nn) { - sprintf(psz, "%lu", (unsigned long)nn); /* The leading zero(s) */ - psz += strlen(psz); - /* as 0.00xx will be ignored. */ - ZeroSup = 0; /* Set to print succeeding zeros */ - } - e = e - nn * m; - m /= 10; - } - } - *psz = 0; - while (psz[-1] == '0') *(--psz) = 0; + if (BIGDECIMAL_NEGATIVE_P(a)) *buf++ = '-'; + n = a->Prec; + for (i = 0; i < n; ++i) { + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + if (!ZeroSup || nn) { + snprintf(buf, buflen, "%lu", (unsigned long)nn); /* The leading zero(s) */ + buf += strlen(buf); + /* as 0.00xx will be ignored. */ + ZeroSup = 0; /* Set to print succeeding zeros */ + } + e = e - nn * m; + m /= 10; + } + } + *buf = 0; + while (buf[-1] == '0') *(--buf) = 0; } else { - if (VpIsPosZero(a)) sprintf(psz, "0"); - else sprintf(psz, "-0"); + if (VpIsPosZero(a)) snprintf(buf, buflen, "0"); + else snprintf(buf, buflen, "-0"); } } VP_EXPORT int -VpToSpecialString(Real *a,char *psz,int fPlus) +VpToSpecialString(Real *a, char *buf, size_t buflen, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { if (VpIsNaN(a)) { - sprintf(psz,SZ_NaN); - return 1; + snprintf(buf, buflen, SZ_NaN); + return 1; } if (VpIsPosInf(a)) { - if (fPlus == 1) { - *psz++ = ' '; - } - else if (fPlus == 2) { - *psz++ = '+'; - } - sprintf(psz, SZ_INF); - return 1; + if (fPlus == 1) { + *buf++ = ' '; + } + else if (fPlus == 2) { + *buf++ = '+'; + } + snprintf(buf, buflen, SZ_INF); + return 1; } if (VpIsNegInf(a)) { - sprintf(psz, SZ_NINF); - return 1; + snprintf(buf, buflen, SZ_NINF); + return 1; } if (VpIsZero(a)) { - if (VpIsPosZero(a)) { - if (fPlus == 1) sprintf(psz, " 0.0"); - else if (fPlus == 2) sprintf(psz, "+0.0"); - else sprintf(psz, "0.0"); - } - else sprintf(psz, "-0.0"); - return 1; + if (VpIsPosZero(a)) { + if (fPlus == 1) snprintf(buf, buflen, " 0.0"); + else if (fPlus == 2) snprintf(buf, buflen, "+0.0"); + else snprintf(buf, buflen, "0.0"); + } + else snprintf(buf, buflen, "-0.0"); + return 1; } return 0; } VP_EXPORT void -VpToString(Real *a, char *psz, size_t fFmt, int fPlus) +VpToString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { size_t i, n, ZeroSup; DECDIG shift, m, e, nn; - char *pszSav = psz; + char *p = buf; + size_t plen = buflen; ssize_t ex; - if (VpToSpecialString(a, psz, fPlus)) return; + if (VpToSpecialString(a, buf, buflen, fPlus)) return; ZeroSup = 1; /* Flag not to print the leading zeros as 0.00xxxxEnn */ - if (BIGDECIMAL_NEGATIVE_P(a)) *psz++ = '-'; - else if (fPlus == 1) *psz++ = ' '; - else if (fPlus == 2) *psz++ = '+'; +#define ADVANCE(n) do { \ + if (plen < n) goto overflow; \ + p += n; \ + plen -= n; \ +} while (0) + + if (BIGDECIMAL_NEGATIVE_P(a)) { + *p = '-'; + ADVANCE(1); + } + else if (fPlus == 1) { + *p = ' '; + ADVANCE(1); + } + else if (fPlus == 2) { + *p = '+'; + ADVANCE(1); + } + + *p = '0'; ADVANCE(1); + *p = '.'; ADVANCE(1); - *psz++ = '0'; - *psz++ = '.'; n = a->Prec; for (i = 0; i < n; ++i) { - m = BASE1; - e = a->frac[i]; - while (m) { - nn = e / m; - if (!ZeroSup || nn) { - sprintf(psz, "%lu", (unsigned long)nn); /* The reading zero(s) */ - psz += strlen(psz); - /* as 0.00xx will be ignored. */ - ZeroSup = 0; /* Set to print succeeding zeros */ - } - e = e - nn * m; - m /= 10; - } + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + if (!ZeroSup || nn) { + /* The reading zero(s) */ + size_t n = (size_t)snprintf(p, plen, "%lu", (unsigned long)nn); + if (n > plen) goto overflow; + ADVANCE(n); + /* as 0.00xx will be ignored. */ + ZeroSup = 0; /* Set to print succeeding zeros */ + } + e = e - nn * m; + m /= 10; + } } + ex = a->exponent * (ssize_t)BASE_FIG; shift = BASE1; while (a->frac[0] / shift == 0) { - --ex; - shift /= 10; + --ex; + shift /= 10; } - while (psz[-1] == '0') { - *(--psz) = 0; + while (p - 1 > buf && p[-1] == '0') { + *(--p) = '\0'; + ++plen; } - sprintf(psz, "e%"PRIdSIZE, ex); - if (fFmt) VpFormatSt(pszSav, fFmt); + snprintf(p, plen, "e%"PRIdSIZE, ex); + if (fFmt) VpFormatSt(buf, fFmt); + + overflow: + return; +#undef ADVANCE } VP_EXPORT void -VpToFString(Real *a, char *psz, size_t fFmt, int fPlus) +VpToFString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { size_t i, n; DECDIG m, e, nn; - char *pszSav = psz; + char *p = buf; + size_t plen = buflen; ssize_t ex; - if (VpToSpecialString(a, psz, fPlus)) return; + if (VpToSpecialString(a, buf, buflen, fPlus)) return; + +#define ADVANCE(n) do { \ + if (plen < n) goto overflow; \ + p += n; \ + plen -= n; \ +} while (0) - if (BIGDECIMAL_NEGATIVE_P(a)) *psz++ = '-'; - else if (fPlus == 1) *psz++ = ' '; - else if (fPlus == 2) *psz++ = '+'; + + if (BIGDECIMAL_NEGATIVE_P(a)) { + *p = '-'; + ADVANCE(1); + } + else if (fPlus == 1) { + *p = ' '; + ADVANCE(1); + } + else if (fPlus == 2) { + *p = '+'; + ADVANCE(1); + } n = a->Prec; ex = a->exponent; if (ex <= 0) { - *psz++ = '0';*psz++ = '.'; - while (ex < 0) { - for (i=0; i < BASE_FIG; ++i) *psz++ = '0'; - ++ex; - } - ex = -1; + *p = '0'; ADVANCE(1); + *p = '.'; ADVANCE(1); + while (ex < 0) { + for (i=0; i < BASE_FIG; ++i) { + *p = '0'; ADVANCE(1); + } + ++ex; + } + ex = -1; } for (i = 0; i < n; ++i) { - --ex; - if (i == 0 && ex >= 0) { - sprintf(psz, "%lu", (unsigned long)a->frac[i]); - psz += strlen(psz); - } - else { - m = BASE1; - e = a->frac[i]; - while (m) { - nn = e / m; - *psz++ = (char)(nn + '0'); - e = e - nn * m; - m /= 10; - } - } - if (ex == 0) *psz++ = '.'; + --ex; + if (i == 0 && ex >= 0) { + size_t n = snprintf(p, plen, "%lu", (unsigned long)a->frac[i]); + if (n > plen) goto overflow; + ADVANCE(n); + } + else { + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + *p = (char)(nn + '0'); + ADVANCE(1); + e = e - nn * m; + m /= 10; + } + } + if (ex == 0) { + *p = '.'; + ADVANCE(1); + } } while (--ex>=0) { - m = BASE; - while (m /= 10) *psz++ = '0'; - if (ex == 0) *psz++ = '.'; - } - *psz = 0; - while (psz[-1] == '0') *(--psz) = 0; - if (psz[-1] == '.') sprintf(psz, "0"); - if (fFmt) VpFormatSt(pszSav, fFmt); + m = BASE; + while (m /= 10) { + *p = '0'; + ADVANCE(1); + } + if (ex == 0) { + *p = '.'; + ADVANCE(1); + } + } + + *p = '\0'; + while (p - 1 > buf && p[-1] == '0') { + *(--p) = '\0'; + ++plen; + } + if (p - 1 > buf && p[-1] == '.') { + snprintf(p, plen, "0"); + } + if (fFmt) VpFormatSt(buf, fFmt); + + overflow: + return; +#undef ADVANCE } /* diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 46fafde1..54fed811 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -231,10 +231,10 @@ VP_EXPORT size_t VpMult(Real *c,Real *a,Real *b); VP_EXPORT size_t VpDivd(Real *c,Real *r,Real *a,Real *b); VP_EXPORT int VpComp(Real *a,Real *b); VP_EXPORT ssize_t VpExponent10(Real *a); -VP_EXPORT void VpSzMantissa(Real *a,char *psz); -VP_EXPORT int VpToSpecialString(Real *a,char *psz,int fPlus); -VP_EXPORT void VpToString(Real *a, char *psz, size_t fFmt, int fPlus); -VP_EXPORT void VpToFString(Real *a, char *psz, size_t fFmt, int fPlus); +VP_EXPORT void VpSzMantissa(Real *a, char *buf, size_t bufsize); +VP_EXPORT int VpToSpecialString(Real *a, char *buf, size_t bufsize, int fPlus); +VP_EXPORT void VpToString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus); +VP_EXPORT void VpToFString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus); VP_EXPORT int VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne); VP_EXPORT int VpVtoD(double *d, SIGNED_VALUE *e, Real *m); VP_EXPORT void VpDtoV(Real *m,double d); From b2123faa5206a1c4cb85cd37de7354125011fd1f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 15 Nov 2022 15:58:56 +0900 Subject: [PATCH 298/546] Add fallback definition of MAYBE_UNUSED --- ext/bigdecimal/bigdecimal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 9b1d33ed..d6ea35c6 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -107,6 +107,10 @@ static struct { # define RB_OBJ_STRING(obj) StringValueCStr(obj) #endif +#ifndef MAYBE_UNUSED +# define MAYBE_UNUSED(x) x +#endif + #define BIGDECIMAL_POSITIVE_P(bd) ((bd)->sign > 0) #define BIGDECIMAL_NEGATIVE_P(bd) ((bd)->sign < 0) From 20eb3e4ddbce06280a8b1491e66a8e5d8a004629 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 29 Nov 2022 12:04:40 +0900 Subject: [PATCH 299/546] Added dependebot for github actions --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b18fd293 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' From d484f25a03433bed24ab98662fc640a979eb9156 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Nov 2022 03:15:41 +0000 Subject: [PATCH 300/546] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 5ce8fde8..552299a0 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -33,7 +33,7 @@ jobs: - { os: windows-latest , ruby: debug } steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87658190..bb0fca1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: - { os: windows-latest , ruby: debug } steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 From 25a75c2033512d1b9b47ede3a26da68de06ff342 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 5 Dec 2022 19:44:05 +0900 Subject: [PATCH 301/546] Bump version to 3.1.3 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 1feed332..d2157571 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "bigdecimal" - s.version = "3.1.2" + s.version = "3.1.3" s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"] From 75bf4c3369f7f36ea935d98d88e467453797ffad Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 6 Dec 2022 09:56:51 +0900 Subject: [PATCH 302/546] Note v3.1.3 changes --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f94d6602..3ef0f2c1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.1.3 + +* Adjust a local variable type to exponent. [GH-223] +* Remove checks for `struct RRational` and `struct RComplex` . [GH-233] +* Suppress macro redefinition warnings. [GH-239] + ## 3.1.2 * Fix the maximum precision of the quotient. [GH-220] From 99db3c97544b9df70a2d93daa3e74d17ec4603d9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 7 Jan 2023 12:09:02 +0900 Subject: [PATCH 303/546] Fix format specifiers for `size_t` --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d6ea35c6..aa3abb54 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -6453,7 +6453,7 @@ VPrint(FILE *fp, const char *cntl_chr, Real *a) } } nc += fprintf(fp, "E%"PRIdSIZE, VpExponent10(a)); - nc += fprintf(fp, " (%"PRIdVALUE", %lu, %lu)", a->exponent, a->Prec, a->MaxPrec); + nc += fprintf(fp, " (%"PRIdVALUE", %"PRIuSIZE", %"PRIuSIZE")", a->exponent, a->Prec, a->MaxPrec); } else { nc += fprintf(fp, "0.0"); From 5a25e26e08725bf9d33d9a10ce23f14f90fdfe0f Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 25 Jan 2023 14:01:10 +0100 Subject: [PATCH 304/546] Add truffleruby in CI --- .github/workflows/ci.yml | 2 ++ test/bigdecimal/test_bigdecimal.rb | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb0fca1d..b5e18b44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,12 +28,14 @@ jobs: - 2.6 - 2.5 - debug + - truffleruby include: - { os: windows-latest , ruby: mingw } - { os: windows-latest , ruby: mswin } exclude: - { os: windows-latest , ruby: 3.0 } - { os: windows-latest , ruby: debug } + - { os: windows-latest , ruby: truffleruby } steps: - uses: actions/checkout@v3 diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 02282405..9c3d72ec 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -294,7 +294,11 @@ def test_s_ver end def test_s_allocate - assert_raise_with_message(TypeError, /allocator undefined for BigDecimal/) { BigDecimal.allocate } + if RUBY_ENGINE == "truffleruby" + assert_raise_with_message(NoMethodError, /undefined.+allocate.+for BigDecimal/) { BigDecimal.allocate } + else + assert_raise_with_message(TypeError, /allocator undefined for BigDecimal/) { BigDecimal.allocate } + end end def test_s_new From 29c61c90e8ee93e287d9b9feadebf25cc636e4e9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 1 Feb 2023 10:33:15 -0500 Subject: [PATCH 305/546] Make BigDecimal WB protected BigDecimal has no references, so it is WB protected. --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index aa3abb54..ce50e780 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -313,7 +313,7 @@ static const rb_data_type_t BigDecimal_data_type = { "BigDecimal", { 0, BigDecimal_delete, BigDecimal_memsize, }, #ifdef RUBY_TYPED_FREE_IMMEDIATELY - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_WB_PROTECTED #endif }; From 5fb36baa1987daf7c5b94c3f827993f64ebaabfa Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 16 Feb 2023 12:42:09 +0900 Subject: [PATCH 306/546] Use ruby/actions/.github/workflows/ruby_versions.yml@master --- .github/workflows/ci.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5e18b44..a9b34cac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,15 @@ on: - reopened jobs: + ruby-versions: + uses: ruby/actions/.github/workflows/ruby_versions.yml@master + with: + engine: cruby-truffleruby + min_version: 2.5 + versions: '["debug"]' + host: + needs: ruby-versions name: ${{ matrix.os }} ${{ matrix.ruby }} runs-on: ${{ matrix.os }} strategy: @@ -22,20 +30,14 @@ jobs: - ubuntu-18.04 - macos-10.15 - windows-latest - ruby: - - "3.0" # Quoted to avoid 3.0 becoming "3" as a String. - - 2.7 - - 2.6 - - 2.5 - - debug - - truffleruby + ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} include: - { os: windows-latest , ruby: mingw } - { os: windows-latest , ruby: mswin } exclude: - - { os: windows-latest , ruby: 3.0 } - { os: windows-latest , ruby: debug } - { os: windows-latest , ruby: truffleruby } + - { os: windows-latest , ruby: truffleruby-head } steps: - uses: actions/checkout@v3 From 24a9060b8476317de9ce420cccb5ee5b7c47133e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 16 Feb 2023 12:56:25 +0900 Subject: [PATCH 307/546] Also use ruby/actions/.github/workflows/ruby_versions.yml@master for benchmark --- .github/workflows/benchmark.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 552299a0..1d61d7e6 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -11,7 +11,15 @@ on: - reopened jobs: + ruby-versions: + uses: ruby/actions/.github/workflows/ruby_versions.yml@master + with: + engine: cruby + min_version: 2.7 + versions: '["debug"]' + host: + needs: ruby-versions name: ${{ matrix.os }} ${{ matrix.ruby }} runs-on: ${{ matrix.os }} strategy: @@ -21,15 +29,11 @@ jobs: - ubuntu-latest - macos-10.15 - windows-latest - ruby: - - 3.0 - - 2.7 - - head + ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} include: - { os: windows-latest , ruby: mingw } - { os: windows-latest , ruby: mswin } exclude: - - { os: windows-latest , ruby: 3.0 } - { os: windows-latest , ruby: debug } steps: From 4b8572d4526abb8b87577ceac4f66119f0cfedf7 Mon Sep 17 00:00:00 2001 From: Maciej Rzasa Date: Wed, 30 Nov 2022 22:38:25 +0100 Subject: [PATCH 308/546] Handle correctly #remainder with infinity. Fixes #187 --- ext/bigdecimal/bigdecimal.c | 7 +++++++ test/bigdecimal/test_bigdecimal.rb | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index ce50e780..637e824a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2082,6 +2082,13 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) if (!b) return DoSomeOne(self, r, rb_intern("remainder")); SAVE(b); + if (VpIsPosInf(b) || VpIsNegInf(b)) { + GUARD_OBJ(*dv, NewZeroWrapLimited(1, 1)); + VpSetZero(*dv, 1); + *rv = a; + return Qnil; + } + mx = (a->MaxPrec + b->MaxPrec) *VpBaseFig(); GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); GUARD_OBJ(res, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 9c3d72ec..2fc22d22 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2260,6 +2260,17 @@ def test_llong_min_gh_200 assert_equal(BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max), "[GH-200]") end + def test_reminder_infinity_gh_187 + # https://github.com/ruby/bigdecimal/issues/187 + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + bd = BigDecimal("4.2") + assert_equal(bd.remainder(BigDecimal("+Infinity")), bd) + assert_equal(bd.remainder(BigDecimal("-Infinity")), bd) + end + end + def assert_no_memory_leak(code, *rest, **opt) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" super(["-rbigdecimal"], From 829956c643730995349fe10cab00b8ca7b20747c Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 8 Feb 2023 21:01:46 +0100 Subject: [PATCH 309/546] Stub out extension build on JRuby JRuby currently ships its own internal bigdecimal extension as part of the core libraries. In order for users to be able to add bigdecimal to their Gemfile or gem dependencies, we need to stub out the C extension and just load the extension shipped with JRuby. In the future we will try to move our BigDecimal implementation into the gem, but for now this is the simplest way to make it installable on JRuby. See #169 --- Gemfile | 2 +- Rakefile | 7 ++++++- bigdecimal.gemspec | 24 +++++++++++++++--------- lib/bigdecimal.rb | 6 +++++- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 06413ee9..288db6ec 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' gemspec gem "benchmark_driver" -gem "fiddle" +gem "fiddle", platform: :ruby gem "rake", ">= 12.3.3" gem "rake-compiler", ">= 0.9" gem "minitest", "< 5.0.0" diff --git a/Rakefile b/Rakefile index 8fd7c14b..706b7fa9 100644 --- a/Rakefile +++ b/Rakefile @@ -6,7 +6,12 @@ require "rake/extensiontask" require "rake/testtask" spec = eval(File.read('bigdecimal.gemspec')) -Rake::ExtensionTask.new('bigdecimal', spec) +if RUBY_ENGINE == 'jruby' + # JRuby's extension is included with JRuby currently + task :compile do; end +else + Rake::ExtensionTask.new('bigdecimal', spec) +end Rake::TestTask.new do |t| t.libs << 'test/lib' diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index d2157571..b05b2b08 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -12,17 +12,8 @@ Gem::Specification.new do |s| s.licenses = ["Ruby", "bsd-2-clause"] s.require_paths = %w[lib] - s.extensions = %w[ext/bigdecimal/extconf.rb] s.files = %w[ bigdecimal.gemspec - ext/bigdecimal/bigdecimal.c - ext/bigdecimal/bigdecimal.h - ext/bigdecimal/bits.h - ext/bigdecimal/feature.h - ext/bigdecimal/missing.c - ext/bigdecimal/missing.h - ext/bigdecimal/missing/dtoa.c - ext/bigdecimal/static_assert.h lib/bigdecimal.rb lib/bigdecimal/jacobian.rb lib/bigdecimal/ludcmp.rb @@ -33,6 +24,21 @@ Gem::Specification.new do |s| sample/nlsolve.rb sample/pi.rb ] + if Gem::Platform === s.platform and s.platform =~ 'java' or RUBY_ENGINE == 'jruby' + s.platform = 'java' + else + s.extensions = %w[ext/bigdecimal/extconf.rb] + s.files += %w[ + ext/bigdecimal/bigdecimal.c + ext/bigdecimal/bigdecimal.h + ext/bigdecimal/bits.h + ext/bigdecimal/feature.h + ext/bigdecimal/missing.c + ext/bigdecimal/missing.h + ext/bigdecimal/missing/dtoa.c + ext/bigdecimal/static_assert.h + ] + end s.required_ruby_version = Gem::Requirement.new(">= 2.5.0") end diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 8fd2587c..82b3e1b7 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -1 +1,5 @@ -require 'bigdecimal.so' +if RUBY_ENGINE == 'jruby' + JRuby::Util.load_ext("org.jruby.ext.bigdecimal.BigDecimalLibrary") +else + require 'bigdecimal.so' +end From 7d312d7650a1ed12104c8f09341d6ef2216d523c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 19 Feb 2023 18:05:57 +0900 Subject: [PATCH 310/546] Reuse the gemspec read by `Bundler::GemHelper` --- Rakefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 706b7fa9..060fc3f2 100644 --- a/Rakefile +++ b/Rakefile @@ -5,12 +5,11 @@ require "rake" require "rake/extensiontask" require "rake/testtask" -spec = eval(File.read('bigdecimal.gemspec')) if RUBY_ENGINE == 'jruby' # JRuby's extension is included with JRuby currently task :compile do; end else - Rake::ExtensionTask.new('bigdecimal', spec) + Rake::ExtensionTask.new('bigdecimal', Bundler::GemHelper.gemspec) end Rake::TestTask.new do |t| From 36b77a2d2fbd7685bd72989ba21c4bcf0a6b45d3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 19 Feb 2023 18:54:43 +0900 Subject: [PATCH 311/546] Fix the license name [ci skip] ``` $ gem build bigdecimal.gemspec WARNING: license value 'BSD-2-clause' is invalid. Use a license identifier from http://spdx.org/licenses or 'Nonstandard' for a nonstandard license. Did you mean 'BSD-2-Clause'? WARNING: See https://guides.rubygems.org/specification-reference/ for help ``` --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index b05b2b08..a72205f5 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Arbitrary-precision decimal floating-point number library." s.description = "This library provides arbitrary-precision decimal floating-point number class." s.homepage = "https://github.com/ruby/bigdecimal" - s.licenses = ["Ruby", "bsd-2-clause"] + s.licenses = ["Ruby", "BSD-2-Clause"] s.require_paths = %w[lib] s.files = %w[ From 7f99b2855225a645b442d20c62da1b240db4cff3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 19 Feb 2023 18:45:06 +0900 Subject: [PATCH 312/546] Read version from bigdecimal.c The dependency of extconf.h on bigdecimal.gemspec does not make sense as far as no rule is defined for it. Also, the relationship between extension library and gemspec file is various in default gems, and does not work well. --- bigdecimal.gemspec | 14 ++++++++++++-- ext/bigdecimal/bigdecimal.c | 7 +++---- ext/bigdecimal/depend | 1 - ext/bigdecimal/extconf.rb | 25 ------------------------- 4 files changed, 15 insertions(+), 32 deletions(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index a72205f5..f9f3b456 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,8 +1,18 @@ # coding: utf-8 +name = File.basename(__FILE__, '.*') +source_version = ["", "ext/#{name}/"].find do |dir| + begin + break File.foreach(File.join(__dir__, "#{dir}#{name}.c")) {|line| + break $1.sub("-", ".") if /^#define\s+#{name.upcase}_VERSION\s+"(.+)"/o =~ line + } + rescue Errno::ENOENT + end +end or raise "can't find #{name.upcase}_VERSION" + Gem::Specification.new do |s| - s.name = "bigdecimal" - s.version = "3.1.3" + s.name = name + s.version = source_version s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"] diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 637e824a..24f299d2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,6 +31,8 @@ #include "bits.h" #include "static_assert.h" +#define BIGDECIMAL_VERSION "3.1.3" + /* #define ENABLE_NUMERIC_STRING */ #define SIGNED_VALUE_MAX INTPTR_MAX @@ -4402,13 +4404,10 @@ Init_bigdecimal(void) /* Constants definition */ -#ifndef RUBY_BIGDECIMAL_VERSION -# error RUBY_BIGDECIMAL_VERSION is not defined -#endif /* * The version of bigdecimal library */ - rb_define_const(rb_cBigDecimal, "VERSION", rb_str_new2(RUBY_BIGDECIMAL_VERSION)); + rb_define_const(rb_cBigDecimal, "VERSION", rb_str_new2(BIGDECIMAL_VERSION)); /* * Base value used in internal calculations. On a 32 bit system, BASE diff --git a/ext/bigdecimal/depend b/ext/bigdecimal/depend index 943bd6c3..33cf28d1 100644 --- a/ext/bigdecimal/depend +++ b/ext/bigdecimal/depend @@ -1,4 +1,3 @@ -extconf.h: $(srcdir)/$(GEMSPEC) Makefile: $(BIGDECIMAL_RB) # AUTOGENERATED DEPENDENCIES START diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 17e7905d..23904ed6 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -1,18 +1,6 @@ # frozen_string_literal: false require 'mkmf' -def check_bigdecimal_version(gemspec_path) - message "checking RUBY_BIGDECIMAL_VERSION... " - bigdecimal_version = File.read(gemspec_path).match(/^\s*s\.version\s+=\s+['"]([^'"]+)['"]\s*$/)[1] - - version_components = bigdecimal_version.split('.') - bigdecimal_version = version_components[0, 3].join('.') - bigdecimal_version << "-#{version_components[3]}" if version_components[3] - $defs << %Q[-DRUBY_BIGDECIMAL_VERSION=\\"#{bigdecimal_version}\\"] - - message "#{bigdecimal_version}\n" -end - def have_builtin_func(name, check_expr, opt = "", &b) checking_for checking_message(name.funcall_style, nil, opt) do if try_compile(< Date: Tue, 21 Feb 2023 14:12:51 +0100 Subject: [PATCH 313/546] Avoid RB_GC_GUARD(a) = b in bigdecimal * This is not supported on TruffleRuby, which requires the value to be set before RB_GC_GUARD() is called. * See https://github.com/oracle/truffleruby/pull/2879 --- ext/bigdecimal/bigdecimal.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 24f299d2..b4e32c70 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4142,11 +4142,14 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) } x = VpCheckGetValue(vx); - RB_GC_GUARD(one) = VpCheckGetValue(NewOneWrapLimited(1, 1)); - RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2", true)); + one = VpCheckGetValue(NewOneWrapLimited(1, 1)); + two = VpCheckGetValue(VpCreateRbObject(1, "2", true)); + RB_GC_GUARD(one); + RB_GC_GUARD(two); n = prec + BIGDECIMAL_DOUBLE_FIGURES; - RB_GC_GUARD(vn) = SSIZET2NUM(n); + vn = SSIZET2NUM(n); + RB_GC_GUARD(vn); expo = VpExponent10(vx); if (expo < 0 || expo >= 3) { char buf[DECIMAL_SIZE_OF_BITS(SIZEOF_VALUE * CHAR_BIT) + 4]; @@ -4158,9 +4161,12 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) } w = BigDecimal_sub(x, one); x = BigDecimal_div2(w, BigDecimal_add(x, one), vn); - RB_GC_GUARD(x2) = BigDecimal_mult2(x, x, vn); - RB_GC_GUARD(y) = x; - RB_GC_GUARD(d) = y; + x2 = BigDecimal_mult2(x, x, vn); + y = x; + d = y; + RB_GC_GUARD(x2); + RB_GC_GUARD(y); + RB_GC_GUARD(d); i = 1; while (!VpIsZero((Real*)DATA_PTR(d))) { SIGNED_VALUE const ey = VpExponent10(DATA_PTR(y)); From b66ef9fbb5f5fbd88ed4ba7cffca700f2d809689 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 21 Feb 2023 14:16:45 +0100 Subject: [PATCH 314/546] Move RB_GC_GUARD() at the end, like in BigMath_s_exp() --- ext/bigdecimal/bigdecimal.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b4e32c70..ae37c65f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4144,12 +4144,9 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) one = VpCheckGetValue(NewOneWrapLimited(1, 1)); two = VpCheckGetValue(VpCreateRbObject(1, "2", true)); - RB_GC_GUARD(one); - RB_GC_GUARD(two); n = prec + BIGDECIMAL_DOUBLE_FIGURES; vn = SSIZET2NUM(n); - RB_GC_GUARD(vn); expo = VpExponent10(vx); if (expo < 0 || expo >= 3) { char buf[DECIMAL_SIZE_OF_BITS(SIZEOF_VALUE * CHAR_BIT) + 4]; @@ -4164,9 +4161,6 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) x2 = BigDecimal_mult2(x, x, vn); y = x; d = y; - RB_GC_GUARD(x2); - RB_GC_GUARD(y); - RB_GC_GUARD(d); i = 1; while (!VpIsZero((Real*)DATA_PTR(d))) { SIGNED_VALUE const ey = VpExponent10(DATA_PTR(y)); @@ -4194,6 +4188,13 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) y = BigDecimal_add(y, dy); } + RB_GC_GUARD(one); + RB_GC_GUARD(two); + RB_GC_GUARD(vn); + RB_GC_GUARD(x2); + RB_GC_GUARD(y); + RB_GC_GUARD(d); + return y; } From 36de91172cad339d9a19094b68152615c095ba53 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 21 Feb 2023 14:27:51 +0100 Subject: [PATCH 315/546] ubuntu-18.04 is being removed * https://github.com/actions/runner-images/issues/6002 --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9b34cac..2a039575 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,6 @@ jobs: matrix: os: - ubuntu-20.04 - - ubuntu-18.04 - macos-10.15 - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} From 3a2a7a93537c4e47592d2b18025b9a8e7529e72b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 21 Feb 2023 22:36:01 +0900 Subject: [PATCH 316/546] Bump up to 3.1.4 ruby/bigdecimal#187 has changed a behavior and ruby/spec also needed a follow up at ruby/ruby@0d8ef62fc293dc04110f36382a7e8bddec6aee15. However, because bigdecimal is a separate gem and can be updated in older versions of ruby, `RUBY_VERSION` is not appropriate for this guard. That means it needs bumped up `BigDecimal::VERSION`. --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index ae37c65f..6eb14bcd 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.1.3" +#define BIGDECIMAL_VERSION "3.1.4" /* #define ENABLE_NUMERIC_STRING */ From 32b2ebe2f40d5095349f776dca19199f7b63f51f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 22 Feb 2023 09:03:04 +0900 Subject: [PATCH 317/546] macos-10.05 will be removed by 2023-03-31 --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 1d61d7e6..975270a5 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -27,7 +27,7 @@ jobs: matrix: os: - ubuntu-latest - - macos-10.15 + - macos-11 - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} include: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a039575..4182c81a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: matrix: os: - ubuntu-20.04 - - macos-10.15 + - macos-11 - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} include: From 779ae7651040b2fd0d0869beaf1d9cc5740a6d2a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 21 Feb 2023 15:14:10 +0900 Subject: [PATCH 318/546] Update Ubuntu versions and fix installation tests --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4182c81a..4bb8c20a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,8 +26,8 @@ jobs: fail-fast: false matrix: os: - - ubuntu-20.04 - - macos-11 + - ubuntu-latest + - macos-latest - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} include: @@ -55,4 +55,4 @@ jobs: - run: rake build - run: gem install pkg/*.gem - if: ${{ ( matrix.ruby != 'debug' && matrix.os == 'linux-latest' ) || ( matrix.ruby != 'debug' && matrix.os == 'macos-latest' ) }} + if: ${{ matrix.ruby != 'debug' && ( matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' ) }} From 2cb856efed5da96792b15a1268538aa59aab886e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 24 Mar 2023 12:59:39 +0900 Subject: [PATCH 319/546] Update test libraries from https://github.com/ruby/ruby/commit/b4e438d8aabaf4bba2b27f374c787543fae07c58 --- test/lib/core_assertions.rb | 49 +++++++++++++++++++++++++++++++++---- test/lib/envutil.rb | 18 ++++++++++---- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index 7cd598b1..1fdc0a34 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -111,7 +111,9 @@ def syntax_check(code, fname, line) end def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) - # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail + # TODO: consider choosing some appropriate limit for RJIT and stop skipping this once it does not randomly fail + pend 'assert_no_memory_leak may consider RJIT memory usage as leak' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? + # For previous versions which implemented MJIT pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? require_relative 'memory_status' @@ -248,7 +250,11 @@ def separated_runner(token, out = nil) at_exit { out.puts "#{token}", [Marshal.dump($!)].pack('m'), "#{token}", "#{token}assertions=#{self._assertions}" } - Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner) + if defined?(Test::Unit::Runner) + Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) + elsif defined?(Test::Unit::AutoRunner) + Test::Unit::AutoRunner.need_auto_run = false + end end def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) @@ -535,11 +541,11 @@ def assert_not_respond_to(obj, (meth, *priv), msg = nil) refute_respond_to(obj, meth, msg) end - # pattern_list is an array which contains regexp and :*. + # pattern_list is an array which contains regexp, string and :*. # :* means any sequence. # # pattern_list is anchored. - # Use [:*, regexp, :*] for non-anchored match. + # Use [:*, regexp/string, :*] for non-anchored match. def assert_pattern_list(pattern_list, actual, message=nil) rest = actual anchored = true @@ -697,7 +703,7 @@ def assert_join_threads(threads, message = nil) msg = "exceptions on #{errs.length} threads:\n" + errs.map {|t, err| "#{t.inspect}:\n" + - err.full_message(highlight: false, order: :top) + (err.respond_to?(:full_message) ? err.full_message(highlight: false, order: :top) : err.message) }.join("\n---\n") if message msg = "#{message}\n#{msg}" @@ -732,6 +738,39 @@ def assert_all_assertions_foreach(msg = nil, *keys, &block) end alias all_assertions_foreach assert_all_assertions_foreach + # Expect +seq+ to respond to +first+ and +each+ methods, e.g., + # Array, Range, Enumerator::ArithmeticSequence and other + # Enumerable-s, and each elements should be size factors. + # + # :yield: each elements of +seq+. + def assert_linear_performance(seq, rehearsal: nil, pre: ->(n) {n}) + first = seq.first + *arg = pre.call(first) + times = (0..(rehearsal || (2 * first))).map do + st = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield(*arg) + t = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st) + assert_operator 0, :<=, t + t.nonzero? + end + times.compact! + tmin, tmax = times.minmax + tmax *= tmax / tmin + tmax = 10**Math.log10(tmax).ceil + + seq.each do |i| + next if i == first + t = tmax * i.fdiv(first) + *arg = pre.call(i) + message = "[#{i}]: in #{t}s" + Timeout.timeout(t, Timeout::Error, message) do + st = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield(*arg) + assert_operator (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st), :<=, t, message + end + end + end + def diff(exp, act) require 'pp' q = PP.new(+"") diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb index e21305c9..728ca705 100644 --- a/test/lib/envutil.rb +++ b/test/lib/envutil.rb @@ -297,16 +297,24 @@ def self.diagnostic_reports(signame, pid, now) cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd path = DIAGNOSTIC_REPORTS_PATH timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT - pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" + pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.{crash,ips}" first = true 30.times do first ? (first = false) : sleep(0.1) Dir.glob(pat) do |name| log = File.read(name) rescue next - if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log - File.unlink(name) - File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil - return log + case name + when /\.crash\z/ + if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log + File.unlink(name) + File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil + return log + end + when /\.ips\z/ + if /^ *"pid" *: *#{pid},/ =~ log + File.unlink(name) + return log + end end end end From 08a0ad563d3904d08349a73da8ea48e7331fea0c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 31 Mar 2023 14:35:10 +0900 Subject: [PATCH 320/546] Remove set but unused variable This `prec` has not been used since 1f5c46dbdd1c53542ab7d37febc1a6f19b245b25. --- ext/bigdecimal/bigdecimal.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6eb14bcd..07fc4235 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -7171,7 +7171,6 @@ VpSqrt(Real *y, Real *x) Real *r = NULL; size_t y_prec; SIGNED_VALUE n, e; - SIGNED_VALUE prec; ssize_t nr; double val; @@ -7210,12 +7209,6 @@ VpSqrt(Real *y, Real *x) nr = 0; y_prec = y->MaxPrec; - prec = x->exponent - (ssize_t)y_prec; - if (x->exponent > 0) - ++prec; - else - --prec; - VpVtoD(&val, &e, x); /* val <- x */ e /= (SIGNED_VALUE)BASE_FIG; n = e / 2; From 13abe1fd78978beefefcaa39acaabc537fb81400 Mon Sep 17 00:00:00 2001 From: HoNooD Date: Sat, 18 Mar 2023 01:04:28 +0800 Subject: [PATCH 321/546] fix: typo in document comments of `f_BigDecimal` function --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 07fc4235..8cbe686e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3722,7 +3722,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) * - Other type: * * - Raises an exception if keyword argument +exception+ is +true+. - * - Returns +nil+ if keyword argument +exception+ is +true+. + * - Returns +nil+ if keyword argument +exception+ is +false+. * * Raises an exception if +value+ evaluates to a Float * and +digits+ is larger than Float::DIG + 1. From f63544d4652a415b41fdb5b42ec63a2337580a52 Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Sat, 1 Jul 2023 16:24:53 +0200 Subject: [PATCH 322/546] Add .to_s('F') digit grouping for integer part --- ext/bigdecimal/bigdecimal.c | 99 ++++++++++++++---------------- test/bigdecimal/test_bigdecimal.rb | 10 ++- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 8cbe686e..6239808a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2689,7 +2689,7 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) * A space at the start of s returns positive values with a leading space. * * If s contains a number, a space is inserted after each group of that many - * fractional digits. + * digits, starting from '.' and counting outwards. * * If s ends with an 'E', engineering notation (0.xxxxEnn) is used. * @@ -6706,95 +6706,90 @@ VpToFString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { size_t i, n; - DECDIG m, e, nn; + DECDIG m, e; char *p = buf; - size_t plen = buflen; + size_t plen = buflen, delim = fFmt; ssize_t ex; if (VpToSpecialString(a, buf, buflen, fPlus)) return; -#define ADVANCE(n) do { \ - if (plen < n) goto overflow; \ - p += n; \ - plen -= n; \ +#define APPEND(c, group) do { \ + if (plen < 1) goto overflow; \ + if (group && delim == 0) { \ + *p = ' '; \ + p += 1; \ + plen -= 1; \ + } \ + if (plen < 1) goto overflow; \ + *p = c; \ + p += 1; \ + plen -= 1; \ + if (group) delim = (delim + 1) % fFmt; \ } while (0) if (BIGDECIMAL_NEGATIVE_P(a)) { - *p = '-'; - ADVANCE(1); + APPEND('-', false); } else if (fPlus == 1) { - *p = ' '; - ADVANCE(1); + APPEND(' ', false); } else if (fPlus == 2) { - *p = '+'; - ADVANCE(1); + APPEND('+', false); } n = a->Prec; ex = a->exponent; if (ex <= 0) { - *p = '0'; ADVANCE(1); - *p = '.'; ADVANCE(1); - while (ex < 0) { - for (i=0; i < BASE_FIG; ++i) { - *p = '0'; ADVANCE(1); - } - ++ex; + APPEND('0', false); + APPEND('.', false); + } + while (ex < 0) { + for (i=0; i < BASE_FIG; ++i) { + APPEND('0', fFmt > 0); } - ex = -1; + ++ex; } for (i = 0; i < n; ++i) { - --ex; - if (i == 0 && ex >= 0) { - size_t n = snprintf(p, plen, "%lu", (unsigned long)a->frac[i]); - if (n > plen) goto overflow; - ADVANCE(n); - } - else { - m = BASE1; - e = a->frac[i]; - while (m) { - nn = e / m; - *p = (char)(nn + '0'); - ADVANCE(1); - e = e - nn * m; + m = BASE1; + e = a->frac[i]; + if (i == 0 && ex > 0) { + for (delim = 0; e / m == 0; delim++) { m /= 10; } + if (fFmt > 0) { + delim = 2*fFmt - (ex * BASE_FIG - delim) % fFmt; + } } - if (ex == 0) { - *p = '.'; - ADVANCE(1); + while (m && (e || (i < n - 1) || ex > 0)) { + APPEND((char)(e / m + '0'), fFmt > 0); + e %= m; + m /= 10; + } + if (--ex == 0) { + APPEND('.', false); + delim = fFmt; } } - while (--ex>=0) { - m = BASE; - while (m /= 10) { - *p = '0'; - ADVANCE(1); + + while (ex > 0) { + for (i=0; i < BASE_FIG; ++i) { + APPEND('0', fFmt > 0); } - if (ex == 0) { - *p = '.'; - ADVANCE(1); + if (--ex == 0) { + APPEND('.', false); } } *p = '\0'; - while (p - 1 > buf && p[-1] == '0') { - *(--p) = '\0'; - ++plen; - } if (p - 1 > buf && p[-1] == '.') { snprintf(p, plen, "0"); } - if (fFmt) VpFormatSt(buf, fFmt); overflow: return; -#undef ADVANCE +#undef APPEND } /* diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 2fc22d22..b1557619 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1415,9 +1415,13 @@ def test_ceil end def test_to_s - assert_equal('-123.45678 90123 45678 9', BigDecimal('-123.45678901234567890').to_s('5F')) - assert_equal('+123.45678901 23456789', BigDecimal('123.45678901234567890').to_s('+8F')) - assert_equal(' 123.4567890123456789', BigDecimal('123.45678901234567890').to_s(' F')) + assert_equal('0.0', BigDecimal('0').to_s) + assert_equal('-123 45678 90123.45678 90123 45678 9', BigDecimal('-1234567890123.45678901234567890').to_s('5F')) + assert_equal('+12345 67890123.45678901 23456789', BigDecimal('1234567890123.45678901234567890').to_s('+8F')) + assert_equal(' 1234567890123.4567890123456789', BigDecimal('1234567890123.45678901234567890').to_s(' F')) + assert_equal('100 000 000 000.000 000 000 01', BigDecimal('100000000000.00000000001').to_s('3F')) + assert_equal('0.0 0 0 0 0 0 0 0 0 0 0 0 1', BigDecimal('0.0000000000001').to_s('1F')) + assert_equal('+1000000 0000000.0', BigDecimal('10000000000000').to_s('+7F')) assert_equal('0.1234567890123456789e3', BigDecimal('123.45678901234567890').to_s) assert_equal('0.12345 67890 12345 6789e3', BigDecimal('123.45678901234567890').to_s(5)) end From 8a94a29cf18c7f223b78f4071f32e1fe3e9c1c6c Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Sun, 2 Jul 2023 13:44:03 +0200 Subject: [PATCH 323/546] Update to_s doc examples --- ext/bigdecimal/bigdecimal.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6239808a..bcfa4005 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2697,14 +2697,14 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) * * Examples: * - * BigDecimal('-123.45678901234567890').to_s('5F') - * #=> '-123.45678 90123 45678 9' + * BigDecimal('-1234567890123.45678901234567890').to_s('5F') + * #=> '-123 45678 90123.45678 90123 45678 9' * - * BigDecimal('123.45678901234567890').to_s('+8F') - * #=> '+123.45678901 23456789' + * BigDecimal('1234567890123.45678901234567890').to_s('+8F') + * #=> '+12345 67890123.45678901 23456789' * - * BigDecimal('123.45678901234567890').to_s(' F') - * #=> ' 123.4567890123456789' + * BigDecimal('1234567890123.45678901234567890').to_s(' F') + * #=> ' 1234567890123.4567890123456789' */ static VALUE BigDecimal_to_s(int argc, VALUE *argv, VALUE self) From c47802e813a79378c48a09edd938ea83849535f5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 8 Jul 2023 18:29:03 +0900 Subject: [PATCH 324/546] Bump up to 3.1.5 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index bcfa4005..0a60f36c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.1.4" +#define BIGDECIMAL_VERSION "3.1.5" /* #define ENABLE_NUMERIC_STRING */ From c269b4dbb75100086f00b0c09581b0c6075f47ed Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 13 Jul 2023 12:51:35 +0900 Subject: [PATCH 325/546] Move bsearch test with Bigdecimal under the test_bigdecimal.rb When we extract bigdecimal as bundled gems, this test will be failed with `make test-all`. --- test/bigdecimal/test_bigdecimal.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index b1557619..b28242b7 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2275,6 +2275,12 @@ def test_reminder_infinity_gh_187 end end + def test_bsearch_for_bigdecimal + assert_raise(TypeError) { + (BigDecimal('0.5')..BigDecimal('2.25')).bsearch + } + end + def assert_no_memory_leak(code, *rest, **opt) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" super(["-rbigdecimal"], From 8316e5bd8daf2fb6ac55c5a3a09c34011d83e474 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 23 Aug 2023 20:39:57 +0900 Subject: [PATCH 326/546] Remove TruffleRuby because it's broken with the current test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4bb8c20a..3d9610c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: ruby-versions: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: - engine: cruby-truffleruby + engine: cruby min_version: 2.5 versions: '["debug"]' From 97aeb65ad6922e71d69e1c8e116453836d458d45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 12:53:43 +0000 Subject: [PATCH 327/546] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 975270a5..3b67d1da 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -37,7 +37,7 @@ jobs: - { os: windows-latest , ruby: debug } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d9610c4..ef6ba7cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: - { os: windows-latest , ruby: truffleruby-head } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 From e21b72bf6e24977802c2d88391bab4a88e6577a6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 11 Sep 2023 17:48:14 +0900 Subject: [PATCH 328/546] Use test-unit-ruby-core gem --- Gemfile | 1 + Rakefile | 19 - test/lib/core_assertions.rb | 796 ------------------------------------ test/lib/envutil.rb | 380 ----------------- test/lib/find_executable.rb | 22 - test/lib/helper.rb | 2 +- 6 files changed, 2 insertions(+), 1218 deletions(-) delete mode 100644 test/lib/core_assertions.rb delete mode 100644 test/lib/envutil.rb delete mode 100644 test/lib/find_executable.rb diff --git a/Gemfile b/Gemfile index 288db6ec..b0c551f7 100644 --- a/Gemfile +++ b/Gemfile @@ -9,3 +9,4 @@ gem "rake-compiler", ">= 0.9" gem "minitest", "< 5.0.0" gem "irb" gem "test-unit" +gem "test-unit-ruby-core" diff --git a/Rakefile b/Rakefile index 060fc3f2..65384de2 100644 --- a/Rakefile +++ b/Rakefile @@ -46,22 +46,3 @@ end desc "Run all benchmarks" task benchmark: benchmark_tasks - -task :sync_tool do - require 'fileutils' - - sync_files = [ - "../ruby/tool/lib/core_assertions.rb", - "../ruby/tool/lib/find_executable.rb", - "../ruby/tool/lib/envutil.rb" - ] - - unless sync_files.all? {|fn| File.file?(fn) } - $stderr.puts "ruby/ruby must be git-cloned at ../ruby before running `rake sync_tool` here." - abort - end - - sync_files.each do |file| - FileUtils.cp file, "./test/lib" - end -end diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb deleted file mode 100644 index 1fdc0a34..00000000 --- a/test/lib/core_assertions.rb +++ /dev/null @@ -1,796 +0,0 @@ -# frozen_string_literal: true - -module Test - module Unit - module Assertions - def assert_raises(*exp, &b) - raise NoMethodError, "use assert_raise", caller - end - - def _assertions= n # :nodoc: - @_assertions = n - end - - def _assertions # :nodoc: - @_assertions ||= 0 - end - - ## - # Returns a proc that will output +msg+ along with the default message. - - def message msg = nil, ending = nil, &default - proc { - ending ||= (ending_pattern = /(? 0 and b > 0 - assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) - end - rescue LoadError - pend - end - - # :call-seq: - # assert_nothing_raised( *args, &block ) - # - #If any exceptions are given as arguments, the assertion will - #fail if one of those exceptions are raised. Otherwise, the test fails - #if any exceptions are raised. - # - #The final argument may be a failure message. - # - # assert_nothing_raised RuntimeError do - # raise Exception #Assertion passes, Exception is not a RuntimeError - # end - # - # assert_nothing_raised do - # raise Exception #Assertion fails - # end - def assert_nothing_raised(*args) - self._assertions += 1 - if Module === args.last - msg = nil - else - msg = args.pop - end - begin - yield - rescue Test::Unit::PendedError, *(Test::Unit::AssertionFailedError if args.empty?) - raise - rescue *(args.empty? ? Exception : args) => e - msg = message(msg) { - "Exception raised:\n<#{mu_pp(e)}>\n""Backtrace:\n" << - Test.filter_backtrace(e.backtrace).map{|frame| " #{frame}"}.join("\n") - } - raise Test::Unit::AssertionFailedError, msg.call, e.backtrace - end - end - - def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil) - fname ||= caller_locations(2, 1)[0] - mesg ||= fname.to_s - verbose, $VERBOSE = $VERBOSE, verbose - case - when Array === fname - fname, line = *fname - when defined?(fname.path) && defined?(fname.lineno) - fname, line = fname.path, fname.lineno - else - line = 1 - end - yield(code, fname, line, message(mesg) { - if code.end_with?("\n") - "```\n#{code}```\n" - else - "```\n#{code}\n```\n""no-newline" - end - }) - ensure - $VERBOSE = verbose - end - - def assert_valid_syntax(code, *args, **opt) - prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg| - yield if defined?(yield) - assert_nothing_raised(SyntaxError, mesg) do - assert_equal(:ok, syntax_check(src, fname, line), mesg) - end - end - end - - def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) - assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) - if child_env - child_env = [child_env] - else - child_env = [] - end - out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) - assert !status.signaled?, FailDesc[status, message, out] - end - - def assert_ruby_status(args, test_stdin="", message=nil, **opt) - out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt) - desc = FailDesc[status, message, out] - assert(!status.signaled?, desc) - message ||= "ruby exit status is not success:" - assert(status.success?, desc) - end - - ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") - - def separated_runner(token, out = nil) - include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) - out = out ? IO.new(out, 'w') : STDOUT - at_exit { - out.puts "#{token}", [Marshal.dump($!)].pack('m'), "#{token}", "#{token}assertions=#{self._assertions}" - } - if defined?(Test::Unit::Runner) - Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) - elsif defined?(Test::Unit::AutoRunner) - Test::Unit::AutoRunner.need_auto_run = false - end - end - - def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) - unless file and line - loc, = caller_locations(1,1) - file ||= loc.path - line ||= loc.lineno - end - capture_stdout = true - unless /mswin|mingw/ =~ RbConfig::CONFIG['host_os'] - capture_stdout = false - opt[:out] = Test::Unit::Runner.output if defined?(Test::Unit::Runner) - res_p, res_c = IO.pipe - opt[:ios] = [res_c] - end - token_dump, token_re = new_test_token - src = <\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m")) - rescue => marshal_error - ignore_stderr = nil - res = nil - end - if res and !(SystemExit === res) - if bt = res.backtrace - bt.each do |l| - l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} - end - bt.concat(caller) - else - res.set_backtrace(caller) - end - raise res - end - - # really is it succeed? - unless ignore_stderr - # the body of assert_separately must not output anything to detect error - assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) - end - assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) - raise marshal_error if marshal_error - end - - # Run Ractor-related test without influencing the main test suite - def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt) - return unless defined?(Ractor) - - require = "require #{require.inspect}" if require - if require_relative - dir = File.dirname(caller_locations[0,1][0].absolute_path) - full_path = File.expand_path(require_relative, dir) - require = "#{require}; require #{full_path.inspect}" - end - - assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt) - #{require} - previous_verbose = $VERBOSE - $VERBOSE = nil - Ractor.new {} # trigger initial warning - $VERBOSE = previous_verbose - #{src} - RUBY - end - - # :call-seq: - # assert_throw( tag, failure_message = nil, &block ) - # - #Fails unless the given block throws +tag+, returns the caught - #value otherwise. - # - #An optional failure message may be provided as the final argument. - # - # tag = Object.new - # assert_throw(tag, "#{tag} was not thrown!") do - # throw tag - # end - def assert_throw(tag, msg = nil) - ret = catch(tag) do - begin - yield(tag) - rescue UncaughtThrowError => e - thrown = e.tag - end - msg = message(msg) { - "Expected #{mu_pp(tag)} to have been thrown"\ - "#{%Q[, not #{thrown}] if thrown}" - } - assert(false, msg) - end - assert(true) - ret - end - - # :call-seq: - # assert_raise( *args, &block ) - # - #Tests if the given block raises an exception. Acceptable exception - #types may be given as optional arguments. If the last argument is a - #String, it will be used as the error message. - # - # assert_raise do #Fails, no Exceptions are raised - # end - # - # assert_raise NameError do - # puts x #Raises NameError, so assertion succeeds - # end - def assert_raise(*exp, &b) - case exp.last - when String, Proc - msg = exp.pop - end - - begin - yield - rescue Test::Unit::PendedError => e - return e if exp.include? Test::Unit::PendedError - raise e - rescue Exception => e - expected = exp.any? { |ex| - if ex.instance_of? Module then - e.kind_of? ex - else - e.instance_of? ex - end - } - - assert expected, proc { - flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"}) - } - - return e - ensure - unless e - exp = exp.first if exp.size == 1 - - flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) - end - end - end - - # :call-seq: - # assert_raise_with_message(exception, expected, msg = nil, &block) - # - #Tests if the given block raises an exception with the expected - #message. - # - # assert_raise_with_message(RuntimeError, "foo") do - # nil #Fails, no Exceptions are raised - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise ArgumentError, "foo" #Fails, different Exception is raised - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise "bar" #Fails, RuntimeError is raised but the message differs - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise "foo" #Raises RuntimeError with the message, so assertion succeeds - # end - def assert_raise_with_message(exception, expected, msg = nil, &block) - case expected - when String - assert = :assert_equal - when Regexp - assert = :assert_match - else - raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" - end - - ex = m = nil - EnvUtil.with_default_internal(expected.encoding) do - ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do - yield - end - m = ex.message - end - msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} - - if assert == :assert_equal - assert_equal(expected, m, msg) - else - msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } - assert expected =~ m, msg - block.binding.eval("proc{|_|$~=_}").call($~) - end - ex - end - - TEST_DIR = File.join(__dir__, "test/unit") #:nodoc: - - # :call-seq: - # assert(test, [failure_message]) - # - #Tests if +test+ is true. - # - #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used - #as the failure message. Otherwise, the result of calling +msg+ will be - #used as the message if the assertion fails. - # - #If no +msg+ is given, a default message will be used. - # - # assert(false, "This was expected to be true") - def assert(test, *msgs) - case msg = msgs.first - when String, Proc - when nil - msgs.shift - else - bt = caller.reject { |s| s.start_with?(TEST_DIR) } - raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt - end unless msgs.empty? - super - end - - # :call-seq: - # assert_respond_to( object, method, failure_message = nil ) - # - #Tests if the given Object responds to +method+. - # - #An optional failure message may be provided as the final argument. - # - # assert_respond_to("hello", :reverse) #Succeeds - # assert_respond_to("hello", :does_not_exist) #Fails - def assert_respond_to(obj, (meth, *priv), msg = nil) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" - } - return assert obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) - return if obj.respond_to?(meth) - end - super(obj, meth, msg) - end - - # :call-seq: - # assert_not_respond_to( object, method, failure_message = nil ) - # - #Tests if the given Object does not respond to +method+. - # - #An optional failure message may be provided as the final argument. - # - # assert_not_respond_to("hello", :reverse) #Fails - # assert_not_respond_to("hello", :does_not_exist) #Succeeds - def assert_not_respond_to(obj, (meth, *priv), msg = nil) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" - } - return assert !obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) - return unless obj.respond_to?(meth) - end - refute_respond_to(obj, meth, msg) - end - - # pattern_list is an array which contains regexp, string and :*. - # :* means any sequence. - # - # pattern_list is anchored. - # Use [:*, regexp/string, :*] for non-anchored match. - def assert_pattern_list(pattern_list, actual, message=nil) - rest = actual - anchored = true - pattern_list.each_with_index {|pattern, i| - if pattern == :* - anchored = false - else - if anchored - match = rest.rindex(pattern, 0) - else - match = rest.index(pattern) - end - if match - post_match = $~ ? $~.post_match : rest[match+pattern.size..-1] - else - msg = message(msg) { - expect_msg = "Expected #{mu_pp pattern}\n" - if /\n[^\n]/ =~ rest - actual_mesg = +"to match\n" - rest.scan(/.*\n+/) { - actual_mesg << ' ' << $&.inspect << "+\n" - } - actual_mesg.sub!(/\+\n\z/, '') - else - actual_mesg = "to match " + mu_pp(rest) - end - actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" - expect_msg + actual_mesg - } - assert false, msg - end - rest = post_match - anchored = true - end - } - if anchored - assert_equal("", rest) - end - end - - def assert_warning(pat, msg = nil) - result = nil - stderr = EnvUtil.with_default_internal(pat.encoding) { - EnvUtil.verbose_warning { - result = yield - } - } - msg = message(msg) {diff pat, stderr} - assert(pat === stderr, msg) - result - end - - def assert_warn(*args) - assert_warning(*args) {$VERBOSE = false; yield} - end - - def assert_deprecated_warning(mesg = /deprecated/) - assert_warning(mesg) do - Warning[:deprecated] = true if Warning.respond_to?(:[]=) - yield - end - end - - def assert_deprecated_warn(mesg = /deprecated/) - assert_warn(mesg) do - Warning[:deprecated] = true if Warning.respond_to?(:[]=) - yield - end - end - - class << (AssertFile = Struct.new(:failure_message).new) - include Assertions - include CoreAssertions - def assert_file_predicate(predicate, *args) - if /\Anot_/ =~ predicate - predicate = $' - neg = " not" - end - result = File.__send__(predicate, *args) - result = !result if neg - mesg = "Expected file ".dup << args.shift.inspect - mesg << "#{neg} to be #{predicate}" - mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? - mesg << " #{failure_message}" if failure_message - assert(result, mesg) - end - alias method_missing assert_file_predicate - - def for(message) - clone.tap {|a| a.failure_message = message} - end - end - - class AllFailures - attr_reader :failures - - def initialize - @count = 0 - @failures = {} - end - - def for(key) - @count += 1 - yield key - rescue Exception => e - @failures[key] = [@count, e] - end - - def foreach(*keys) - keys.each do |key| - @count += 1 - begin - yield key - rescue Exception => e - @failures[key] = [@count, e] - end - end - end - - def message - i = 0 - total = @count.to_s - fmt = "%#{total.size}d" - @failures.map {|k, (n, v)| - v = v.message - "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}" - }.join("\n") - end - - def pass? - @failures.empty? - end - end - - # threads should respond to shift method. - # Array can be used. - def assert_join_threads(threads, message = nil) - errs = [] - values = [] - while th = threads.shift - begin - values << th.value - rescue Exception - errs << [th, $!] - th = nil - end - end - values - ensure - if th&.alive? - th.raise(Timeout::Error.new) - th.join rescue errs << [th, $!] - end - if !errs.empty? - msg = "exceptions on #{errs.length} threads:\n" + - errs.map {|t, err| - "#{t.inspect}:\n" + - (err.respond_to?(:full_message) ? err.full_message(highlight: false, order: :top) : err.message) - }.join("\n---\n") - if message - msg = "#{message}\n#{msg}" - end - raise Test::Unit::AssertionFailedError, msg - end - end - - def assert_all?(obj, m = nil, &blk) - failed = [] - obj.each do |*a, &b| - unless blk.call(*a, &b) - failed << (a.size > 1 ? a : a[0]) - end - end - assert(failed.empty?, message(m) {failed.pretty_inspect}) - end - - def assert_all_assertions(msg = nil) - all = AllFailures.new - yield all - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions assert_all_assertions - - def assert_all_assertions_foreach(msg = nil, *keys, &block) - all = AllFailures.new - all.foreach(*keys, &block) - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions_foreach assert_all_assertions_foreach - - # Expect +seq+ to respond to +first+ and +each+ methods, e.g., - # Array, Range, Enumerator::ArithmeticSequence and other - # Enumerable-s, and each elements should be size factors. - # - # :yield: each elements of +seq+. - def assert_linear_performance(seq, rehearsal: nil, pre: ->(n) {n}) - first = seq.first - *arg = pre.call(first) - times = (0..(rehearsal || (2 * first))).map do - st = Process.clock_gettime(Process::CLOCK_MONOTONIC) - yield(*arg) - t = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st) - assert_operator 0, :<=, t - t.nonzero? - end - times.compact! - tmin, tmax = times.minmax - tmax *= tmax / tmin - tmax = 10**Math.log10(tmax).ceil - - seq.each do |i| - next if i == first - t = tmax * i.fdiv(first) - *arg = pre.call(i) - message = "[#{i}]: in #{t}s" - Timeout.timeout(t, Timeout::Error, message) do - st = Process.clock_gettime(Process::CLOCK_MONOTONIC) - yield(*arg) - assert_operator (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st), :<=, t, message - end - end - end - - def diff(exp, act) - require 'pp' - q = PP.new(+"") - q.guard_inspect_key do - q.group(2, "expected: ") do - q.pp exp - end - q.text q.newline - q.group(2, "actual: ") do - q.pp act - end - q.flush - end - q.output - end - - def new_test_token - token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" - return token.dump, Regexp.quote(token) - end - end - end -end diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb deleted file mode 100644 index 728ca705..00000000 --- a/test/lib/envutil.rb +++ /dev/null @@ -1,380 +0,0 @@ -# -*- coding: us-ascii -*- -# frozen_string_literal: true -require "open3" -require "timeout" -require_relative "find_executable" -begin - require 'rbconfig' -rescue LoadError -end -begin - require "rbconfig/sizeof" -rescue LoadError -end - -module EnvUtil - def rubybin - if ruby = ENV["RUBY"] - return ruby - end - ruby = "ruby" - exeext = RbConfig::CONFIG["EXEEXT"] - rubyexe = (ruby + exeext if exeext and !exeext.empty?) - 3.times do - if File.exist? ruby and File.executable? ruby and !File.directory? ruby - return File.expand_path(ruby) - end - if rubyexe and File.exist? rubyexe and File.executable? rubyexe - return File.expand_path(rubyexe) - end - ruby = File.join("..", ruby) - end - if defined?(RbConfig.ruby) - RbConfig.ruby - else - "ruby" - end - end - module_function :rubybin - - LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" - - DEFAULT_SIGNALS = Signal.list - DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM - - RUBYLIB = ENV["RUBYLIB"] - - class << self - attr_accessor :timeout_scale - attr_reader :original_internal_encoding, :original_external_encoding, - :original_verbose, :original_warning - - def capture_global_values - @original_internal_encoding = Encoding.default_internal - @original_external_encoding = Encoding.default_external - @original_verbose = $VERBOSE - @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil - end - end - - def apply_timeout_scale(t) - if scale = EnvUtil.timeout_scale - t * scale - else - t - end - end - module_function :apply_timeout_scale - - def timeout(sec, klass = nil, message = nil, &blk) - return yield(sec) if sec == nil or sec.zero? - sec = apply_timeout_scale(sec) - Timeout.timeout(sec, klass, message, &blk) - end - module_function :timeout - - def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) - reprieve = apply_timeout_scale(reprieve) if reprieve - - signals = Array(signal).select do |sig| - DEFAULT_SIGNALS[sig.to_s] or - DEFAULT_SIGNALS[Signal.signame(sig)] rescue false - end - signals |= [:ABRT, :KILL] - case pgroup - when 0, true - pgroup = -pid - when nil, false - pgroup = pid - end - - lldb = true if /darwin/ =~ RUBY_PLATFORM - - while signal = signals.shift - - if lldb and [:ABRT, :KILL].include?(signal) - lldb = false - # sudo -n: --non-interactive - # lldb -p: attach - # -o: run command - system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) - true - end - - begin - Process.kill signal, pgroup - rescue Errno::EINVAL - next - rescue Errno::ESRCH - break - end - if signals.empty? or !reprieve - Process.wait(pid) - else - begin - Timeout.timeout(reprieve) {Process.wait(pid)} - rescue Timeout::Error - else - break - end - end - end - $? - end - module_function :terminate - - def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, - encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, - stdout_filter: nil, stderr_filter: nil, ios: nil, - signal: :TERM, - rubybin: EnvUtil.rubybin, precommand: nil, - **opt) - timeout = apply_timeout_scale(timeout) - - in_c, in_p = IO.pipe - out_p, out_c = IO.pipe if capture_stdout - err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout - opt[:in] = in_c - opt[:out] = out_c if capture_stdout - opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr - if encoding - out_p.set_encoding(encoding) if out_p - err_p.set_encoding(encoding) if err_p - end - ios.each {|i, o = i|opt[i] = o} if ios - - c = "C" - child_env = {} - LANG_ENVS.each {|lc| child_env[lc] = c} - if Array === args and Hash === args.first - child_env.update(args.shift) - end - if RUBYLIB and lib = child_env["RUBYLIB"] - child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) - end - - # remain env - %w(ASAN_OPTIONS RUBY_ON_BUG).each{|name| - child_env[name] = ENV[name] if ENV[name] - } - - args = [args] if args.kind_of?(String) - pid = spawn(child_env, *precommand, rubybin, *args, opt) - in_c.close - out_c&.close - out_c = nil - err_c&.close - err_c = nil - if block_given? - return yield in_p, out_p, err_p, pid - else - th_stdout = Thread.new { out_p.read } if capture_stdout - th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout - in_p.write stdin_data.to_str unless stdin_data.empty? - in_p.close - if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) - timeout_error = nil - else - status = terminate(pid, signal, opt[:pgroup], reprieve) - terminated = Time.now - end - stdout = th_stdout.value if capture_stdout - stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout - out_p.close if capture_stdout - err_p.close if capture_stderr && capture_stderr != :merge_to_stdout - status ||= Process.wait2(pid)[1] - stdout = stdout_filter.call(stdout) if stdout_filter - stderr = stderr_filter.call(stderr) if stderr_filter - if timeout_error - bt = caller_locations - msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" - msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n")) - raise timeout_error, msg, bt.map(&:to_s) - end - return stdout, stderr, status - end - ensure - [th_stdout, th_stderr].each do |th| - th.kill if th - end - [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| - io&.close - end - [th_stdout, th_stderr].each do |th| - th.join if th - end - end - module_function :invoke_ruby - - def verbose_warning - class << (stderr = "".dup) - alias write concat - def flush; end - end - stderr, $stderr = $stderr, stderr - $VERBOSE = true - yield stderr - return $stderr - ensure - stderr, $stderr = $stderr, stderr - $VERBOSE = EnvUtil.original_verbose - EnvUtil.original_warning&.each {|i, v| Warning[i] = v} - end - module_function :verbose_warning - - def default_warning - $VERBOSE = false - yield - ensure - $VERBOSE = EnvUtil.original_verbose - end - module_function :default_warning - - def suppress_warning - $VERBOSE = nil - yield - ensure - $VERBOSE = EnvUtil.original_verbose - end - module_function :suppress_warning - - def under_gc_stress(stress = true) - stress, GC.stress = GC.stress, stress - yield - ensure - GC.stress = stress - end - module_function :under_gc_stress - - def with_default_external(enc) - suppress_warning { Encoding.default_external = enc } - yield - ensure - suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding } - end - module_function :with_default_external - - def with_default_internal(enc) - suppress_warning { Encoding.default_internal = enc } - yield - ensure - suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding } - end - module_function :with_default_internal - - def labeled_module(name, &block) - Module.new do - singleton_class.class_eval { - define_method(:to_s) {name} - alias inspect to_s - alias name to_s - } - class_eval(&block) if block - end - end - module_function :labeled_module - - def labeled_class(name, superclass = Object, &block) - Class.new(superclass) do - singleton_class.class_eval { - define_method(:to_s) {name} - alias inspect to_s - alias name to_s - } - class_eval(&block) if block - end - end - module_function :labeled_class - - if /darwin/ =~ RUBY_PLATFORM - DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") - DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' - @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] - - def self.diagnostic_reports(signame, pid, now) - return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) - cmd = File.basename(rubybin) - cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd - path = DIAGNOSTIC_REPORTS_PATH - timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT - pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.{crash,ips}" - first = true - 30.times do - first ? (first = false) : sleep(0.1) - Dir.glob(pat) do |name| - log = File.read(name) rescue next - case name - when /\.crash\z/ - if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log - File.unlink(name) - File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil - return log - end - when /\.ips\z/ - if /^ *"pid" *: *#{pid},/ =~ log - File.unlink(name) - return log - end - end - end - end - nil - end - else - def self.diagnostic_reports(signame, pid, now) - end - end - - def self.failure_description(status, now, message = "", out = "") - pid = status.pid - if signo = status.termsig - signame = Signal.signame(signo) - sigdesc = "signal #{signo}" - end - log = diagnostic_reports(signame, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc = "#{sigdesc} (core dumped)" - end - full_message = ''.dup - message = message.call if Proc === message - if message and !message.empty? - full_message << message << "\n" - end - full_message << "pid #{pid}" - full_message << " exit #{status.exitstatus}" if status.exited? - full_message << " killed by #{sigdesc}" if sigdesc - if out and !out.empty? - full_message << "\n" << out.b.gsub(/^/, '| ') - full_message.sub!(/(? Date: Wed, 1 Nov 2023 03:34:08 +0100 Subject: [PATCH 329/546] fixed docs for .scale the scale of `1` is actually 0 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0a60f36c..07c2bcf0 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -657,7 +657,7 @@ BigDecimal_precision(VALUE self) * Returns the number of decimal digits following the decimal digits in +self+. * * BigDecimal("0").scale # => 0 - * BigDecimal("1").scale # => 1 + * BigDecimal("1").scale # => 0 * BigDecimal("1.1").scale # => 1 * BigDecimal("3.1415").scale # => 4 * BigDecimal("-1e20").precision # => 0 From dd52adf3b2036ae1adbe9cdc14f326e1bb4c20f7 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Mon, 6 Nov 2023 22:31:31 +0900 Subject: [PATCH 330/546] Update doc for bigdecimal/util Follow up https://github.com/ruby/bigdecimal/issues/89. `BigDecimal.new` has already been removed. This PR replaces `BigDecimal.new` with `Kernel.BigDecimal` in the documentation, following the message below: > BigDecimal.new is deprecated; use Kernel.BigDecimal method instead. https://github.com/ruby/bigdecimal/commit/26d84ba766e971da8eaaf2ce41e7b89935fa68da --- lib/bigdecimal/util.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index ad92f7cf..8bfc0ed8 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -18,7 +18,7 @@ class Integer < Numeric # # 42.to_d # => 0.42e2 # - # See also BigDecimal::new. + # See also Kernel.BigDecimal. # def to_d BigDecimal(self) @@ -45,7 +45,7 @@ class Float < Numeric # 1.234.to_d # => 0.1234e1 # 1.234.to_d(2) # => 0.12e1 # - # See also BigDecimal::new. + # See also Kernel.BigDecimal. # def to_d(precision=0) BigDecimal(self, precision) @@ -67,7 +67,7 @@ class String # "123.45e1".to_d # => 0.12345e4 # "45.67 degrees".to_d # => 0.4567e2 # - # See also BigDecimal::new. + # See also Kernel.BigDecimal. # def to_d BigDecimal.interpret_loosely(self) @@ -127,7 +127,7 @@ class Rational < Numeric # # Rational(22, 7).to_d(3) # => 0.314e1 # - # See also BigDecimal::new. + # See also Kernel.BigDecimal. # def to_d(precision) BigDecimal(self, precision) @@ -152,7 +152,7 @@ class Complex < Numeric # Complex(0.1234567, 0).to_d(4) # => 0.1235e0 # Complex(Rational(22, 7), 0).to_d(3) # => 0.314e1 # - # See also BigDecimal::new. + # See also Kernel.BigDecimal. # def to_d(*args) BigDecimal(self) unless self.imag.zero? # to raise eerror From 2edd8d0a2360477387d603dfab80acdda5372f78 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 16 Nov 2023 15:46:01 -0600 Subject: [PATCH 331/546] [DOC] Add section Methods for Working with JSON --- ext/bigdecimal/bigdecimal.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 07c2bcf0..d73e0714 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4363,7 +4363,20 @@ BigDecimal_negative_zero(void) * (2/3r).to_d(3) # => 0.667e0 * "0.5".to_d # => 0.5e0 * - * == License + * == Methods for Working with \JSON + * + * - {::json_create}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-c-json_create]: + * Returns a new \BigDecimal object constructed from the given object. + * - {#as_json}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-i-as_json]: + * Returns a 2-element hash representing +self+. + * - {#to_json}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-i-to_json]: + * Returns a \JSON string representing +self+. + * + * To make these methods available: + * + * require 'json/add/bigdecimal' + * + * * == License * * Copyright (C) 2002 by Shigeo Kobayashi . * From 581725d4e5f220618bbfeb7c09f7ca5b45921150 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 6 Dec 2023 11:02:17 -0500 Subject: [PATCH 332/546] Clarify that JSON methods come from the JSON gem --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d73e0714..0595726c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4372,7 +4372,7 @@ BigDecimal_negative_zero(void) * - {#to_json}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-i-to_json]: * Returns a \JSON string representing +self+. * - * To make these methods available: + * These methods are provided by the {JSON gem}[https://github.com/flori/json]. To make these methods available: * * require 'json/add/bigdecimal' * From 2841dc84d5d5c2a042601a3c6c528222aa29e0dc Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Dec 2023 15:52:50 +0900 Subject: [PATCH 333/546] CHANGES: Add 3.1.4 entries --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 3ef0f2c1..5f86615c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.1.4 + +* Handle correctly #reminder with infinity. Fixes [GH-187] [GH-243] + + **@mrzasa** + ## 3.1.3 * Adjust a local variable type to exponent. [GH-223] From 4f4510ba0a187a939bb96954b7e7f09e065f35c7 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Dec 2023 16:00:05 +0900 Subject: [PATCH 334/546] CHANGES: Add 3.1.5 entries --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5f86615c..3806ec41 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.1.5 + +* Add .to_s('F') digit grouping for integer part [GH-264] + + **@cryptogopher** + ## 3.1.4 * Handle correctly #reminder with infinity. Fixes [GH-187] [GH-243] From 5843c89da40b743225de2a822a6d4030848d972a Mon Sep 17 00:00:00 2001 From: z2 <88509734+z2-2z@users.noreply.github.com> Date: Tue, 19 Dec 2023 17:31:52 +0100 Subject: [PATCH 335/546] Correctly computing loop iterations in `BigDecimal#sqrt` (#280) --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0595726c..0550e6e3 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -7230,7 +7230,7 @@ VpSqrt(Real *y, Real *x) y->MaxPrec = Min((size_t)n , y_prec); f->MaxPrec = y->MaxPrec + 1; n = (SIGNED_VALUE)(y_prec * BASE_FIG); - if (n < (SIGNED_VALUE)maxnr) n = (SIGNED_VALUE)maxnr; + if (n > (SIGNED_VALUE)maxnr) n = (SIGNED_VALUE)maxnr; /* * Perform: y_{n+1} = (y_n - x/y_n) / 2 From 55a9f83d49235c0690bde632f85fa0b8dedd83b2 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Jan 2024 13:24:18 +0900 Subject: [PATCH 336/546] Added current directory to LOAD_PATH of assert_in_out_err for test-bundled-gems at ruby/ruby --- test/bigdecimal/test_bigdecimal.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index b28242b7..f4b54901 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1768,7 +1768,9 @@ def test_ctov def test_split_under_gc_stress bug3258 = '[ruby-dev:41213]' expect = 10.upto(20).map{|i|[1, "1", 10, i+1].inspect} - assert_in_out_err(%w[-rbigdecimal --disable-gems], <<-EOS, expect, [], bug3258) + # for test-bundled-gems in ruby/ruby repository + extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") + assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, expect, [], bug3258) GC.stress = true 10.upto(20) do |i| p BigDecimal("1"+"0"*i).split @@ -1777,7 +1779,9 @@ def test_split_under_gc_stress end def test_coerce_under_gc_stress - assert_in_out_err(%w[-rbigdecimal --disable-gems], <<-EOS, [], []) + # for test-bundled-gems in ruby/ruby repository + extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") + assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" b = BigDecimal("1") GC.stress = true @@ -1884,7 +1888,9 @@ def test_BigMath_exp_with_rational end def test_BigMath_exp_under_gc_stress - assert_in_out_err(%w[-rbigdecimal --disable-gems], <<-EOS, [], []) + # for test-bundled-gems in ruby/ruby repository + extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") + assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" 10.times do begin @@ -2026,7 +2032,9 @@ def test_BigMath_log_with_reciprocal_of_42 end def test_BigMath_log_under_gc_stress - assert_in_out_err(%w[-rbigdecimal --disable-gems], <<-EOS, [], []) + # for test-bundled-gems in ruby/ruby repository + extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") + assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" 10.times do begin @@ -2073,7 +2081,9 @@ def test_to_d end if RUBY_VERSION < '2.5' # mathn was removed from Ruby 2.5 def test_bug6406 - assert_in_out_err(%w[-rbigdecimal --disable-gems], <<-EOS, [], []) + # for test-bundled-gems in ruby/ruby repository + extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") + assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) Thread.current.keys.to_s EOS end From 1bbca814f9650ee3de351a4f3eb126cdc7ad36ae Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Jan 2024 17:10:43 +0900 Subject: [PATCH 337/546] Support ruby/bigdecimal repository cases --- test/bigdecimal/test_bigdecimal.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f4b54901..e97fd48c 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1770,7 +1770,9 @@ def test_split_under_gc_stress expect = 10.upto(20).map{|i|[1, "1", 10, i+1].inspect} # for test-bundled-gems in ruby/ruby repository extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, expect, [], bug3258) + opts = ["-rbigdecimal", "--disable-gems"] + opts.unshift "-I#{extension_paths}" unless extension_paths.empty? + assert_in_out_err(opts, <<-EOS, expect, [], bug3258) GC.stress = true 10.upto(20) do |i| p BigDecimal("1"+"0"*i).split @@ -1781,7 +1783,9 @@ def test_split_under_gc_stress def test_coerce_under_gc_stress # for test-bundled-gems in ruby/ruby repository extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) + opts = ["-rbigdecimal", "--disable-gems"] + opts.unshift "-I#{extension_paths}" unless extension_paths.empty? + assert_in_out_err(opts, <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" b = BigDecimal("1") GC.stress = true @@ -1890,7 +1894,9 @@ def test_BigMath_exp_with_rational def test_BigMath_exp_under_gc_stress # for test-bundled-gems in ruby/ruby repository extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) + opts = ["-rbigdecimal", "--disable-gems"] + opts.unshift "-I#{extension_paths}" unless extension_paths.empty? + assert_in_out_err(opts, <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" 10.times do begin @@ -2034,7 +2040,9 @@ def test_BigMath_log_with_reciprocal_of_42 def test_BigMath_log_under_gc_stress # for test-bundled-gems in ruby/ruby repository extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) + opts = ["-rbigdecimal", "--disable-gems"] + opts.unshift "-I#{extension_paths}" unless extension_paths.empty? + assert_in_out_err(opts, <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" 10.times do begin @@ -2083,7 +2091,9 @@ def test_to_d def test_bug6406 # for test-bundled-gems in ruby/ruby repository extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - assert_in_out_err(["-I#{extension_paths}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) + opts = ["-rbigdecimal", "--disable-gems"] + opts.unshift "-I#{extension_paths}" unless extension_paths.empty? + assert_in_out_err(opts, <<-EOS, [], []) Thread.current.keys.to_s EOS end From bf5ebd0be03a8cba3e7a1569ee0ccde8ebc16cf6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Jan 2024 19:34:01 +0900 Subject: [PATCH 338/546] Simplified LOAD_PATH delegation for assert_in_out_err --- test/bigdecimal/test_bigdecimal.rb | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e97fd48c..078b059c 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1768,11 +1768,7 @@ def test_ctov def test_split_under_gc_stress bug3258 = '[ruby-dev:41213]' expect = 10.upto(20).map{|i|[1, "1", 10, i+1].inspect} - # for test-bundled-gems in ruby/ruby repository - extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - opts = ["-rbigdecimal", "--disable-gems"] - opts.unshift "-I#{extension_paths}" unless extension_paths.empty? - assert_in_out_err(opts, <<-EOS, expect, [], bug3258) + assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, expect, [], bug3258) GC.stress = true 10.upto(20) do |i| p BigDecimal("1"+"0"*i).split @@ -1781,11 +1777,7 @@ def test_split_under_gc_stress end def test_coerce_under_gc_stress - # for test-bundled-gems in ruby/ruby repository - extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - opts = ["-rbigdecimal", "--disable-gems"] - opts.unshift "-I#{extension_paths}" unless extension_paths.empty? - assert_in_out_err(opts, <<-EOS, [], []) + assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" b = BigDecimal("1") GC.stress = true @@ -1892,11 +1884,7 @@ def test_BigMath_exp_with_rational end def test_BigMath_exp_under_gc_stress - # for test-bundled-gems in ruby/ruby repository - extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - opts = ["-rbigdecimal", "--disable-gems"] - opts.unshift "-I#{extension_paths}" unless extension_paths.empty? - assert_in_out_err(opts, <<-EOS, [], []) + assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" 10.times do begin @@ -2038,11 +2026,7 @@ def test_BigMath_log_with_reciprocal_of_42 end def test_BigMath_log_under_gc_stress - # for test-bundled-gems in ruby/ruby repository - extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - opts = ["-rbigdecimal", "--disable-gems"] - opts.unshift "-I#{extension_paths}" unless extension_paths.empty? - assert_in_out_err(opts, <<-EOS, [], []) + assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" 10.times do begin @@ -2089,11 +2073,7 @@ def test_to_d end if RUBY_VERSION < '2.5' # mathn was removed from Ruby 2.5 def test_bug6406 - # for test-bundled-gems in ruby/ruby repository - extension_paths = Dir.glob(File.expand_path('../../../../../.bundle/extensions', __dir__) + '/**/*/bigdecimal-*').join(":") - opts = ["-rbigdecimal", "--disable-gems"] - opts.unshift "-I#{extension_paths}" unless extension_paths.empty? - assert_in_out_err(opts, <<-EOS, [], []) + assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) Thread.current.keys.to_s EOS end From 758fc62a52cbf382788c7aa9eca170d5ba9d7648 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Jan 2024 19:34:25 +0900 Subject: [PATCH 339/546] We need to care assert_no_memory_leak too --- test/bigdecimal/test_bigdecimal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 078b059c..bab23a47 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2283,7 +2283,7 @@ def test_bsearch_for_bigdecimal def assert_no_memory_leak(code, *rest, **opt) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" - super(["-rbigdecimal"], + super(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal"], "b = BigDecimal('10'); b.nil?; " \ "GC.add_stress_to_class(BigDecimal); "\ "#{code}", code, *rest, rss: true, limit: 1.1, **opt) From 2aab78bee1d24f2c29d89ef905d16539bcd036be Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Jan 2024 21:12:31 +0900 Subject: [PATCH 340/546] BigDecimal() is obsoleted today. We shouldn't test it --- test/bigdecimal/test_bigdecimal.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index bab23a47..866cd962 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2294,10 +2294,6 @@ def test_no_memory_leak_allocate assert_no_memory_leak("BigDecimal.allocate") end - def test_no_memory_leak_initialize - assert_no_memory_leak("BigDecimal()") - end - def test_no_memory_leak_BigDecimal assert_no_memory_leak("BigDecimal('10')") assert_no_memory_leak("BigDecimal(b)") From c25d93e0e465b50dd3f197838f7ac0311a91ee00 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Jan 2024 21:12:53 +0900 Subject: [PATCH 341/546] BigDecimal.allocate is obsoleted too --- test/bigdecimal/test_bigdecimal.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 866cd962..898eb7ec 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2290,10 +2290,6 @@ def assert_no_memory_leak(code, *rest, **opt) end if EnvUtil.gc_stress_to_class? - def test_no_memory_leak_allocate - assert_no_memory_leak("BigDecimal.allocate") - end - def test_no_memory_leak_BigDecimal assert_no_memory_leak("BigDecimal('10')") assert_no_memory_leak("BigDecimal(b)") From a77d7751382f4947bf1f5fa98a380f3c2eb7bde9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Jan 2024 11:54:24 +0900 Subject: [PATCH 342/546] Don't use : for Windows platforms --- test/bigdecimal/test_bigdecimal.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 898eb7ec..41d8bad7 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1768,7 +1768,8 @@ def test_ctov def test_split_under_gc_stress bug3258 = '[ruby-dev:41213]' expect = 10.upto(20).map{|i|[1, "1", 10, i+1].inspect} - assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, expect, [], bug3258) + paths = $LOAD_PATH.map{|path| "-I#{path}" } + assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, expect, [], bug3258) GC.stress = true 10.upto(20) do |i| p BigDecimal("1"+"0"*i).split @@ -1777,7 +1778,8 @@ def test_split_under_gc_stress end def test_coerce_under_gc_stress - assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) + paths = $LOAD_PATH.map{|path| "-I#{path}" } + assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" b = BigDecimal("1") GC.stress = true @@ -1884,7 +1886,8 @@ def test_BigMath_exp_with_rational end def test_BigMath_exp_under_gc_stress - assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) + paths = $LOAD_PATH.map{|path| "-I#{path}" } + assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" 10.times do begin @@ -2026,7 +2029,8 @@ def test_BigMath_log_with_reciprocal_of_42 end def test_BigMath_log_under_gc_stress - assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) + paths = $LOAD_PATH.map{|path| "-I#{path}" } + assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) expect = ":too_long_to_embed_as_string can't be coerced into BigDecimal" 10.times do begin @@ -2073,7 +2077,8 @@ def test_to_d end if RUBY_VERSION < '2.5' # mathn was removed from Ruby 2.5 def test_bug6406 - assert_in_out_err(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) + paths = $LOAD_PATH.map{|path| "-I#{path}" } + assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) Thread.current.keys.to_s EOS end @@ -2283,7 +2288,8 @@ def test_bsearch_for_bigdecimal def assert_no_memory_leak(code, *rest, **opt) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" - super(["-I#{$LOAD_PATH.join(":")}", "-rbigdecimal"], + paths = $LOAD_PATH.map{|path| "-I#{path}" } + super([*paths, "-rbigdecimal"], "b = BigDecimal('10'); b.nil?; " \ "GC.add_stress_to_class(BigDecimal); "\ "#{code}", code, *rest, rss: true, limit: 1.1, **opt) From 992c2c9433858ccfe509f9e550885309a14fe326 Mon Sep 17 00:00:00 2001 From: Oleksii Leonov Date: Sat, 13 Jan 2024 20:02:28 +0000 Subject: [PATCH 343/546] Add LICENSE file to gem files Adding a `LICENSE` file to the bundled gem package is common practice. This way, automated OSS license compliance tools will be able to collect the full text of the license. --- bigdecimal.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index f9f3b456..69423838 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |s| s.require_paths = %w[lib] s.files = %w[ + LICENSE bigdecimal.gemspec lib/bigdecimal.rb lib/bigdecimal/jacobian.rb From ed0dd9f22c1cdea9f207d1f1d601f228453d9ddb Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Jan 2024 12:07:41 +0900 Subject: [PATCH 344/546] Refine test code related unsupported Ruby version --- test/bigdecimal/test_bigdecimal.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 41d8bad7..d44bcdaf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2070,12 +2070,6 @@ def test_new_subclass assert_raise_with_message(NoMethodError, /undefined method `new'/) { c.new(1) } end - def test_to_d - bug6093 = '[ruby-core:42969]' - code = "exit(BigDecimal('10.0') == 10.0.to_d)" - assert_ruby_status(%w[-rbigdecimal -rbigdecimal/util -rmathn -], code, bug6093) - end if RUBY_VERSION < '2.5' # mathn was removed from Ruby 2.5 - def test_bug6406 paths = $LOAD_PATH.map{|path| "-I#{path}" } assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) @@ -2254,10 +2248,9 @@ def test_n_significant_digits_special def test_initialize_copy_dup_clone_frozen_error bd = BigDecimal(1) bd2 = BigDecimal(2) - err = RUBY_VERSION >= '2.5' ? FrozenError : TypeError - assert_raise(err) { bd.send(:initialize_copy, bd2) } - assert_raise(err) { bd.send(:initialize_clone, bd2) } - assert_raise(err) { bd.send(:initialize_dup, bd2) } + assert_raise(FrozenError) { bd.send(:initialize_copy, bd2) } + assert_raise(FrozenError) { bd.send(:initialize_clone, bd2) } + assert_raise(FrozenError) { bd.send(:initialize_dup, bd2) } end def test_llong_min_gh_200 From 080f9b2ce67c2d82bd67018568babe39c11fe750 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Jan 2024 15:51:10 +0900 Subject: [PATCH 345/546] Bump up version to 3.1.6 --- CHANGES.md | 6 ++++++ ext/bigdecimal/bigdecimal.c | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 3806ec41..2123976f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.1.6 + +* Add LICENSE file to gem files [GH-282] + + **@oleksii-leonov** + ## 3.1.5 * Add .to_s('F') digit grouping for integer part [GH-264] diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0550e6e3..98d5094f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.1.5" +#define BIGDECIMAL_VERSION "3.1.6" /* #define ENABLE_NUMERIC_STRING */ From 310cb156b924ac655b19a7b9b0e6febf7aa08284 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Jan 2024 11:45:36 +0900 Subject: [PATCH 346/546] Use macos-arm-oss and latest versions for test and benchmark CI --- .github/workflows/benchmark.yml | 3 ++- .github/workflows/ci.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 3b67d1da..5a31c35c 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -27,7 +27,8 @@ jobs: matrix: os: - ubuntu-latest - - macos-11 + - macos-latest + - macos-arm-oss - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} include: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef6ba7cb..9227cb1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,7 @@ jobs: os: - ubuntu-latest - macos-latest + - macos-arm-oss - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} include: From 26bc4990156cd84106a63800e42c02bd700eb9e5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Jan 2024 11:50:25 +0900 Subject: [PATCH 347/546] Exclude Ruby 2.5 with Apple Silicon --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9227cb1d..cd6f20b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ jobs: - { os: windows-latest , ruby: mingw } - { os: windows-latest , ruby: mswin } exclude: + - { os: macos-arm-oss , ruby: "2.5" } - { os: windows-latest , ruby: debug } - { os: windows-latest , ruby: truffleruby } - { os: windows-latest , ruby: truffleruby-head } From b2a948037c16785e27ace99f701dd1e069936b7e Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Thu, 15 Feb 2024 21:30:16 +0900 Subject: [PATCH 348/546] Support Ruby 3.4's new error message format Ruby 3.4 will use a single quote instead of a backtrick as an open quote. https://bugs.ruby-lang.org/issues/16495 --- test/bigdecimal/test_bigdecimal.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index d44bcdaf..cae33751 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -290,7 +290,7 @@ def test_BigDecimal_with_exception_keyword end def test_s_ver - assert_raise_with_message(NoMethodError, /undefined method `ver'/) { BigDecimal.ver } + assert_raise_with_message(NoMethodError, /undefined method [`']ver'/) { BigDecimal.ver } end def test_s_allocate @@ -302,7 +302,7 @@ def test_s_allocate end def test_s_new - assert_raise_with_message(NoMethodError, /undefined method `new'/) { BigDecimal.new("1") } + assert_raise_with_message(NoMethodError, /undefined method [`']new'/) { BigDecimal.new("1") } end def test_s_interpret_loosely @@ -2067,7 +2067,7 @@ def test_dup def test_new_subclass c = Class.new(BigDecimal) - assert_raise_with_message(NoMethodError, /undefined method `new'/) { c.new(1) } + assert_raise_with_message(NoMethodError, /undefined method [`']new'/) { c.new(1) } end def test_bug6406 From c9aa2a5e19a4dcbc76a83720f8f0d101232d0dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Fri, 16 Feb 2024 10:46:58 +0100 Subject: [PATCH 349/546] Rename `rake spec` with `rake test` in documentation `rake spec` is not defined in Rake tasks (`rake -T`). `rake test` runs the tests. This updates the documentation to help newcomers to run the test suite locally. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 927cce11..e9cbf7b1 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ TODO: Write usage instructions here ## Development After checking out the repo, run `bin/setup` to install dependencies. -Then, run `rake spec` to run the tests. +Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. From cbb53692f163babcb8360ed3a1f50068f315886c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 14 Mar 2024 16:21:01 +0900 Subject: [PATCH 350/546] Bump up 3.1.7 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 98d5094f..3e14364a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.1.6" +#define BIGDECIMAL_VERSION "3.1.7" /* #define ENABLE_NUMERIC_STRING */ From 69674e8c0f0bb8abab4da481f2545d2fad48e1af Mon Sep 17 00:00:00 2001 From: Andrew Konchin Date: Tue, 26 Mar 2024 13:00:11 +0200 Subject: [PATCH 351/546] Restore TruffleRuby on CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd6f20b0..4744eb21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: ruby-versions: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: - engine: cruby + engine: cruby-truffleruby min_version: 2.5 versions: '["debug"]' From ccdd8d2b6ae339af786638ac7a33d7c26ca1996f Mon Sep 17 00:00:00 2001 From: Kenta Murata <3959+mrkn@users.noreply.github.com> Date: Sun, 5 May 2024 00:14:56 +0900 Subject: [PATCH 352/546] CI: Remove macos-latest 2.5 Because it's unavailable. --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4744eb21..5bee326f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ jobs: - { os: windows-latest , ruby: mingw } - { os: windows-latest , ruby: mswin } exclude: + - { os: macos-latest , ruby: "2.5" } - { os: macos-arm-oss , ruby: "2.5" } - { os: windows-latest , ruby: debug } - { os: windows-latest , ruby: truffleruby } From be80c3fdbff961be5b86f06f497b275204c9cf0f Mon Sep 17 00:00:00 2001 From: Kenta Murata <3959+mrkn@users.noreply.github.com> Date: Mon, 6 May 2024 11:53:18 +0900 Subject: [PATCH 353/546] Fix memory leak in VpAlloc by exception (#294) --- ext/bigdecimal/bigdecimal.c | 44 ++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 3e14364a..e4068a59 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -5203,6 +5203,48 @@ bigdecimal_parse_special_string(const char *str) return NULL; } +struct VpCtoV_args { + Real *a; + const char *int_chr; + size_t ni; + const char *frac; + size_t nf; + const char *exp_chr; + size_t ne; +}; + +static VALUE +call_VpCtoV(VALUE arg) +{ + struct VpCtoV_args *x = (struct VpCtoV_args *)arg; + return (VALUE)VpCtoV(x->a, x->int_chr, x->ni, x->frac, x->nf, x->exp_chr, x->ne); +} + +static int +protected_VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne, int free_on_error) +{ + struct VpCtoV_args args; + int state = 0; + + args.a = a; + args.int_chr = int_chr; + args.ni = ni; + args.frac = frac; + args.nf = nf; + args.exp_chr = exp_chr; + args.ne = ne; + + VALUE result = rb_protect(call_VpCtoV, (VALUE)&args, &state); + if (state) { + if (free_on_error) { + rbd_free_struct(a); + } + rb_jump_tag(state); + } + + return (int)result; +} + /* * Allocates variable. * [Input] @@ -5422,7 +5464,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) vp = rbd_allocate_struct(len); vp->MaxPrec = len; /* set max precision */ VpSetZero(vp, sign); - VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne); + protected_VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne, true); rb_str_resize(buf, 0); return vp; } From 381340ad7d001a106dcfdb60680e3c579fb2515c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 7 May 2024 10:11:17 +0900 Subject: [PATCH 354/546] Add missing documents (#277) * [DOC] Make constants documented * [DOC] Add missing documents * [DOC] Link Infinity and NaN --- ext/bigdecimal/bigdecimal.c | 62 +++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e4068a59..e011b4a6 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1780,6 +1780,17 @@ BigDecimal_neg(VALUE self) return VpCheckGetValue(c); } +/* + * call-seq: + * a * b -> bigdecimal + * + * Multiply by the specified value. + * + * The result precision will be the precision of the sum of each precision. + * + * See BigDecimal#mult. + */ + static VALUE BigDecimal_mult(VALUE self, VALUE r) { @@ -3257,10 +3268,11 @@ BigDecimal_initialize_copy(VALUE self, VALUE other) return self; } +/* :nodoc: */ static VALUE BigDecimal_clone(VALUE self) { - return self; + return self; } #ifdef HAVE_RB_OPTS_EXCEPTION_P @@ -3758,6 +3770,12 @@ f_BigDecimal(int argc, VALUE *argv, VALUE self) return rb_convert_to_BigDecimal(val, digs, exception); } +/* call-seq: + * BigDecimal.interpret_loosely(string) -> bigdecimal + * + * Returns the +BigDecimal+ converted loosely from +string+. + */ + static VALUE BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) { @@ -4238,6 +4256,17 @@ BigDecimal_negative_zero(void) return BIGDECIMAL_NEGATIVE_ZERO; } +static inline VALUE +BigDecimal_literal(const char *str) +{ + VALUE arg = rb_str_new_cstr(str); + VALUE val = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(val); + return val; +} + +#define BIGDECIMAL_LITERAL(var, val) (BIGDECIMAL_ ## var = BigDecimal_literal(#val)) + /* Document-class: BigDecimal * BigDecimal provides arbitrary-precision floating point decimal arithmetic. * @@ -4394,7 +4423,6 @@ Init_bigdecimal(void) #ifdef HAVE_RB_EXT_RACTOR_SAFE rb_ext_ractor_safe(true); #endif - VALUE arg; id_BigDecimal_exception_mode = rb_intern_const("BigDecimal.exception_mode"); id_BigDecimal_rounding_mode = rb_intern_const("BigDecimal.rounding_mode"); @@ -4532,33 +4560,19 @@ Init_bigdecimal(void) rb_define_const(rb_cBigDecimal, "SIGN_NEGATIVE_INFINITE", INT2FIX(VP_SIGN_NEGATIVE_INFINITE)); /* Positive zero value. */ - arg = rb_str_new2("+0"); - BIGDECIMAL_POSITIVE_ZERO = f_BigDecimal(1, &arg, rb_cBigDecimal); - rb_gc_register_mark_object(BIGDECIMAL_POSITIVE_ZERO); + BIGDECIMAL_LITERAL(POSITIVE_ZERO, +0); /* Negative zero value. */ - arg = rb_str_new2("-0"); - BIGDECIMAL_NEGATIVE_ZERO = f_BigDecimal(1, &arg, rb_cBigDecimal); - rb_gc_register_mark_object(BIGDECIMAL_NEGATIVE_ZERO); + BIGDECIMAL_LITERAL(NEGATIVE_ZERO, -0); - /* Positive infinity value. */ - arg = rb_str_new2("+Infinity"); - BIGDECIMAL_POSITIVE_INFINITY = f_BigDecimal(1, &arg, rb_cBigDecimal); - rb_gc_register_mark_object(BIGDECIMAL_POSITIVE_INFINITY); + /* Positive infinity[rdoc-ref:BigDecimal@Infinity] value. */ + rb_define_const(rb_cBigDecimal, "INFINITY", BIGDECIMAL_LITERAL(POSITIVE_INFINITY, +Infinity)); /* Negative infinity value. */ - arg = rb_str_new2("-Infinity"); - BIGDECIMAL_NEGATIVE_INFINITY = f_BigDecimal(1, &arg, rb_cBigDecimal); - rb_gc_register_mark_object(BIGDECIMAL_NEGATIVE_INFINITY); - - /* 'Not a Number' value. */ - arg = rb_str_new2("NaN"); - BIGDECIMAL_NAN = f_BigDecimal(1, &arg, rb_cBigDecimal); - rb_gc_register_mark_object(BIGDECIMAL_NAN); - - /* Special value constants */ - rb_define_const(rb_cBigDecimal, "INFINITY", BIGDECIMAL_POSITIVE_INFINITY); - rb_define_const(rb_cBigDecimal, "NAN", BIGDECIMAL_NAN); + BIGDECIMAL_LITERAL(NEGATIVE_INFINITY, -Infinity); + + /* '{Not a Number}[rdoc-ref:BigDecimal@Not+a+Number]' value. */ + rb_define_const(rb_cBigDecimal, "NAN", BIGDECIMAL_LITERAL(NAN, NaN)); /* instance methods */ rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); From 4b54be557f685af71d3c66dfe9cde683711a4388 Mon Sep 17 00:00:00 2001 From: mark-young-atg <113439900+mark-young-atg@users.noreply.github.com> Date: Tue, 7 May 2024 02:17:35 +0100 Subject: [PATCH 355/546] Provide a 'Changelog' link on rubygems.org/gems/bigdecimal (#281) By providing a 'changelog_uri' in the metadata of the gemspec a 'Changelog' link will be shown on https://rubygems.org/gems/bigdecimal which makes it quick and easy for someone to check on the changes introduced with a new version. Details of this functionality can be found on https://guides.rubygems.org/specification-reference/ --- bigdecimal.gemspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 69423838..b6ef8fd9 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -52,4 +52,6 @@ Gem::Specification.new do |s| end s.required_ruby_version = Gem::Requirement.new(">= 2.5.0") + + s.metadata["changelog_uri"] = s.homepage + "/blob/master/CHANGES.md" end From a564961e9cc9c1ac70da51b32e991cf522555efa Mon Sep 17 00:00:00 2001 From: Kenta Murata <3959+mrkn@users.noreply.github.com> Date: Tue, 7 May 2024 10:27:23 +0900 Subject: [PATCH 356/546] CHANGES: Add 3.1.7 entry --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 2123976f..ae81e830 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # CHANGES +## 3.1.7 + +* Only consists of CI settings and test changes. + ## 3.1.6 * Add LICENSE file to gem files [GH-282] From 9e16b26682fc83c453e6f37e03bcb42e26f484b9 Mon Sep 17 00:00:00 2001 From: Kenta Murata <3959+mrkn@users.noreply.github.com> Date: Tue, 7 May 2024 10:29:54 +0900 Subject: [PATCH 357/546] CHANGES: Add 3.1.8 entry --- CHANGES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index ae81e830..8d805bd7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # CHANGES +## 3.1.8 + +* Add missing documents [GH-277] + + **@nobu** + +* Fix memory leak in VpAlloc [GH-294] [GH-290] + + Reoprted by **@MaxLap** + ## 3.1.7 * Only consists of CI settings and test changes. From bd49f16d21673bba8eac8db39c803a6366d49e78 Mon Sep 17 00:00:00 2001 From: Kenta Murata <3959+mrkn@users.noreply.github.com> Date: Tue, 7 May 2024 10:31:52 +0900 Subject: [PATCH 358/546] CHANGES: Add note for 3.1.7 --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 8d805bd7..299819c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ ## 3.1.7 * Only consists of CI settings and test changes. + This release is needed for developing Ruby to run `make test-bundled-gems` with the released version of bigdecimal. ## 3.1.6 From ae3915ba8831cb0bbed2bc60a1345b320f2eafb4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 7 May 2024 10:34:08 +0900 Subject: [PATCH 359/546] Bump up to 3.1.8 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e011b4a6..aa2bf210 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.1.7" +#define BIGDECIMAL_VERSION "3.1.8" /* #define ENABLE_NUMERIC_STRING */ From 6c2bb36ff5e21493d50d50587413c16695c38a59 Mon Sep 17 00:00:00 2001 From: Franz Liedke <249125+franzliedke@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:02:53 +0200 Subject: [PATCH 360/546] Extend docs for round(0) Since #170, `big_decimal.round(0)` returns an Integer. This was surprising to me, as the documentation does not specify the behavior for this case. (It could even be seen as misleading / wrong.) --- ext/bigdecimal/bigdecimal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index aa2bf210..e4578791 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2459,7 +2459,7 @@ BigDecimal_fix(VALUE self) * round(n, mode) * * Round to the nearest integer (by default), returning the result as a - * BigDecimal if n is specified, or as an Integer if it isn't. + * BigDecimal if n is specified and positive, or as an Integer if it isn't. * * BigDecimal('3.14159').round #=> 3 * BigDecimal('8.7').round #=> 9 @@ -2467,6 +2467,7 @@ BigDecimal_fix(VALUE self) * * BigDecimal('3.14159').round(2).class.name #=> "BigDecimal" * BigDecimal('3.14159').round.class.name #=> "Integer" + * BigDecimal('3.14159').round(0).class.name #=> "Integer" * * If n is specified and positive, the fractional part of the result has no * more than that many digits. From e68e6cbe04316dea3ad4e3ec19dc0c11fa391880 Mon Sep 17 00:00:00 2001 From: Franz Liedke <249125+franzliedke@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:04:34 +0200 Subject: [PATCH 361/546] Fix indentation [ci skip] --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e4578791..6d48741f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2467,7 +2467,7 @@ BigDecimal_fix(VALUE self) * * BigDecimal('3.14159').round(2).class.name #=> "BigDecimal" * BigDecimal('3.14159').round.class.name #=> "Integer" - * BigDecimal('3.14159').round(0).class.name #=> "Integer" + * BigDecimal('3.14159').round(0).class.name #=> "Integer" * * If n is specified and positive, the fractional part of the result has no * more than that many digits. From 1cbff9f535438fb65e7b0cd099450ef8bff974a4 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Jul 2024 14:15:55 +0900 Subject: [PATCH 362/546] Use macos-14 instead of macos-arm-oss --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 5a31c35c..71c7c685 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -28,7 +28,7 @@ jobs: os: - ubuntu-latest - macos-latest - - macos-arm-oss + - macos-14 - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} include: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bee326f..0ecd1b68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: os: - ubuntu-latest - macos-latest - - macos-arm-oss + - macos-14 - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} include: @@ -36,7 +36,7 @@ jobs: - { os: windows-latest , ruby: mswin } exclude: - { os: macos-latest , ruby: "2.5" } - - { os: macos-arm-oss , ruby: "2.5" } + - { os: macos-14 , ruby: "2.5" } - { os: windows-latest , ruby: debug } - { os: windows-latest , ruby: truffleruby } - { os: windows-latest , ruby: truffleruby-head } From 7f35c41a7df3ba9395e586fcf04a4d2808d59496 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Fri, 26 Jul 2024 13:51:14 +0300 Subject: [PATCH 363/546] Minor typo fix --- lib/bigdecimal/util.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index 8bfc0ed8..cb514435 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -155,7 +155,7 @@ class Complex < Numeric # See also Kernel.BigDecimal. # def to_d(*args) - BigDecimal(self) unless self.imag.zero? # to raise eerror + BigDecimal(self) unless self.imag.zero? # to raise error if args.length == 0 case self.real From 8fa77121fbf77c739287f987962c885b5a63cd98 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 9 Sep 2024 12:16:14 +0900 Subject: [PATCH 364/546] Reduce unnecessary checks If `have_header` returns `false`, `have_func` using that header also return `false` always, because that header cannot be included. --- ext/bigdecimal/extconf.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 23904ed6..cf4290f5 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -24,15 +24,17 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_header("stdbool.h") have_header("stdlib.h") -have_header("x86intrin.h") -have_func("_lzcnt_u32", "x86intrin.h") -have_func("_lzcnt_u64", "x86intrin.h") - -have_header("intrin.h") -have_func("__lzcnt", "intrin.h") -have_func("__lzcnt64", "intrin.h") -have_func("_BitScanReverse", "intrin.h") -have_func("_BitScanReverse64", "intrin.h") +if have_header("x86intrin.h") + have_func("_lzcnt_u32", "x86intrin.h") + have_func("_lzcnt_u64", "x86intrin.h") +end + +if have_header("intrin.h") + have_func("__lzcnt", "intrin.h") + have_func("__lzcnt64", "intrin.h") + have_func("_BitScanReverse", "intrin.h") + have_func("_BitScanReverse64", "intrin.h") +end have_func("labs", "stdlib.h") have_func("llabs", "stdlib.h") From 5f9466a1da79e786df45607b6348599580d2210f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 10 Oct 2024 11:23:47 +0900 Subject: [PATCH 365/546] Fix extra semicolon outside of a function in `NO_SANITIZE` --- ext/bigdecimal/missing.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/missing.c b/ext/bigdecimal/missing.c index 703232d9..1454c281 100644 --- a/ext/bigdecimal/missing.c +++ b/ext/bigdecimal/missing.c @@ -15,7 +15,8 @@ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ __attribute__((__no_sanitize__(x))) y; \ - _Pragma("GCC diagnostic pop") + _Pragma("GCC diagnostic pop") \ + y #endif #undef strtod From 66868abc75df7367298c8973aed1580f7d05703a Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Thu, 10 Oct 2024 17:02:47 +0900 Subject: [PATCH 366/546] Accept no digits in the fractional part (#302) fix #301 For example, "0.E-9" and "0." are accepted. FYI: `Float()`/`.to_f` will accept "0.E-9": https://bugs.ruby-lang.org/issues/20705 The current `Float` doesn't accept "0." but `.to_f` accepts "0.". --- ext/bigdecimal/bigdecimal.c | 9 ++------- test/bigdecimal/test_bigdecimal.rb | 2 ++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6d48741f..34dae9ff 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -5277,7 +5277,7 @@ VP_EXPORT Real * VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) { const char *orig_szVal = szVal; - size_t i, j, ni, ipf, nf, ipe, ne, dot_seen, exp_seen, nalloc; + size_t i, j, ni, ipf, nf, ipe, ne, exp_seen, nalloc; size_t len; char v, *psz; int sign=1; @@ -5363,13 +5363,11 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) ne = 0; /* number of digits in the exponential part */ ipf = 0; /* index of the beginning of the fractional part */ ipe = 0; /* index of the beginning of the exponential part */ - dot_seen = 0; exp_seen = 0; if (v != '\0') { /* Scanning fractional part */ if ((psz[i] = szVal[j]) == '.') { - dot_seen = 1; ++i; ++j; ipf = i; @@ -5385,9 +5383,6 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) } if (!strict_p) { v = psz[i] = '\0'; - if (nf == 0) { - dot_seen = 0; - } break; } goto invalid_value; @@ -5458,7 +5453,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) psz[i] = '\0'; - if (strict_p && (((ni == 0 || dot_seen) && nf == 0) || (exp_seen && ne == 0))) { + if (strict_p && ((ni == 0 && nf == 0) || (exp_seen && ne == 0))) { VALUE str; invalid_value: if (!strict_p) { diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index cae33751..dbc00e56 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -74,6 +74,8 @@ def test_BigDecimal assert_equal(111, BigDecimal("1_1_1_")) assert_equal(10**(-1), BigDecimal("1E-1"), '#4825') assert_equal(1234, BigDecimal(" \t\n\r \r1234 \t\n\r \r")) + assert_equal(0.0, BigDecimal("0.")) + assert_equal(0.0E-9, BigDecimal("0.E-9")) assert_raise(ArgumentError) { BigDecimal("1", -1) } assert_raise_with_message(ArgumentError, /"1__1_1"/) { BigDecimal("1__1_1") } From 480dd7991ad933b6d3e983c5c4edcb0ad89241ab Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 25 Dec 2024 11:19:33 +0900 Subject: [PATCH 367/546] CHANGES: Add v3.1.9 entries --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 299819c4..50ac97e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.1.9 + +* Accept no digits in the fractional part (#302) + + **@kou** + ## 3.1.8 * Add missing documents [GH-277] From 8bdeafc5f23f478350af04c9fe74613afeb58df7 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 25 Dec 2024 11:20:20 +0900 Subject: [PATCH 368/546] Bump up to v3.1.9 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 34dae9ff..a707295e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.1.8" +#define BIGDECIMAL_VERSION "3.1.9" /* #define ENABLE_NUMERIC_STRING */ From a015c8b91575fc6cb25df6be64a69f42abf34399 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 25 Dec 2024 11:43:23 +0900 Subject: [PATCH 369/546] CI: Disable benchmarking of some versions on Ruby --- .github/workflows/benchmark.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 71c7c685..6146cf92 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -37,6 +37,11 @@ jobs: exclude: - { os: windows-latest , ruby: debug } + # These are disabled due to the ambiguity of stringio + - { os: windows-latest , ruby: "3.0" } + - { os: windows-latest , ruby: "3.1" } + - { os: windows-latest , ruby: "3.2" } + steps: - uses: actions/checkout@v4 From 5c9e0428394f1cce2c9d2b873be7ca08ee67087f Mon Sep 17 00:00:00 2001 From: Maciej Rzasa Date: Tue, 4 Feb 2025 22:57:47 +0100 Subject: [PATCH 370/546] Fix spec NoMethodError message for .allocator on truffle Ruby --- test/bigdecimal/test_bigdecimal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index dbc00e56..fab9622a 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -297,7 +297,7 @@ def test_s_ver def test_s_allocate if RUBY_ENGINE == "truffleruby" - assert_raise_with_message(NoMethodError, /undefined.+allocate.+for BigDecimal/) { BigDecimal.allocate } + assert_raise_with_message(NoMethodError, /undefined.+allocate.+for.+BigDecimal/) { BigDecimal.allocate } else assert_raise_with_message(TypeError, /allocator undefined for BigDecimal/) { BigDecimal.allocate } end From dee33ee98b370ace1f02b92b1092af888181e818 Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 10 May 2025 22:39:53 +0900 Subject: [PATCH 371/546] Remove outdated BigMath.atan document that referrs to convergence --- lib/bigdecimal/math.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 0b9d0648..3ab84d84 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -7,13 +7,12 @@ # sqrt(x, prec) # sin (x, prec) # cos (x, prec) -# atan(x, prec) Note: |x|<1, x=0.9999 may not converge. +# atan(x, prec) # PI (prec) # E (prec) == exp(1.0,prec) # # where: # x ... BigDecimal number to be computed. -# |x| must be small enough to get convergence. # prec ... Number of digits to be obtained. #++ # From 07facd4f2c14346ec72755df3eab57ec3a15c28f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 17 May 2025 00:19:14 +0900 Subject: [PATCH 372/546] Add a precision assertion to BigMath test (#316) * Add a precision assertion to BigMath test * Move precision assertion methods to test helper * Assert precision with multiple precision values: [50, 100, 150] --- test/bigdecimal/helper.rb | 27 +++++++++++++++++++++++++++ test/bigdecimal/test_bigmath.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 46721fb9..9bd09fef 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -36,4 +36,31 @@ def under_gc_stress ensure GC.stress = stress end + + # Asserts that the calculation of the given block converges to some value + # with precision specified by block parameter. + + def assert_fixed_point_precision(&block) + _assert_precision(:fixed_point, &block) + end + + def assert_relative_precision(&block) + _assert_precision(:relative, &block) + end + + def _assert_precision(mode) + expected = yield(200) + [50, 100, 150].each do |n| + value = yield(n) + if mode == :fixed_point + precision = -(value - expected).exponent + elsif mode == :relative + precision = -(value.div(expected, expected.precision) - 1).exponent + else + raise ArgumentError, "Unknown mode: #{mode}" + end + assert(value != expected, "Unable to estimate precision for exact value") + assert(precision >= n, "Precision is not enough: #{precision} < #{n}") + end + end end diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 5bf1fbf3..d1c94cc6 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -6,6 +6,9 @@ class TestBigMath < Test::Unit::TestCase include TestBigDecimalBase include BigMath N = 20 + # SQRT in 116 (= 100 + double_fig) digits + SQRT2 = BigDecimal("1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309123") + SQRT3 = BigDecimal("1.7320508075688772935274463415058723669428052538103806280558069794519330169088000370811461867572485756756261414154067") PINF = BigDecimal("+Infinity") MINF = BigDecimal("-Infinity") NAN = BigDecimal("NaN") @@ -23,6 +26,11 @@ def test_sqrt assert_raise(FloatDomainError) {sqrt(BigDecimal("-1.0"), N)} assert_raise(FloatDomainError) {sqrt(NAN, N)} assert_raise(FloatDomainError) {sqrt(PINF, N)} + assert_in_delta(SQRT2, sqrt(BigDecimal("2"), 100), BigDecimal("1e-100")) + assert_in_delta(SQRT3, sqrt(BigDecimal("3"), 100), BigDecimal("1e-100")) + assert_relative_precision {|n| sqrt(BigDecimal("2"), n) } + assert_relative_precision {|n| sqrt(BigDecimal("2e-50"), n) } + assert_relative_precision {|n| sqrt(BigDecimal("2e50"), n) } end def test_sin @@ -37,6 +45,13 @@ def test_sin assert_in_delta(0.0, sin(PI(N) * 21, N)) assert_in_delta(0.0, sin(PI(N) * 30, N)) assert_in_delta(-1.0, sin(PI(N) * BigDecimal("301.5"), N)) + assert_in_delta(BigDecimal('0.5'), sin(PI(100) / 6, 100), BigDecimal("1e-100")) + assert_in_delta(SQRT3 / 2, sin(PI(100) / 3, 100), BigDecimal("1e-100")) + assert_in_delta(SQRT2 / 2, sin(PI(100) / 4, 100), BigDecimal("1e-100")) + assert_fixed_point_precision {|n| sin(BigDecimal("1"), n) } + assert_fixed_point_precision {|n| sin(BigDecimal("1e-30"), n) } + assert_fixed_point_precision {|n| sin(BigDecimal(PI(50)), n) } + assert_fixed_point_precision {|n| sin(BigDecimal(PI(50) * 100), n) } end def test_cos @@ -51,6 +66,12 @@ def test_cos assert_in_delta(-1.0, cos(PI(N) * 21, N)) assert_in_delta(1.0, cos(PI(N) * 30, N)) assert_in_delta(0.0, cos(PI(N) * BigDecimal("301.5"), N)) + assert_in_delta(BigDecimal('0.5'), cos(PI(100) / 3, 100), BigDecimal("1e-100")) + assert_in_delta(SQRT3 / 2, cos(PI(100) / 6, 100), BigDecimal("1e-100")) + assert_in_delta(SQRT2 / 2, cos(PI(100) / 4, 100), BigDecimal("1e-100")) + assert_fixed_point_precision {|n| cos(BigDecimal("1"), n) } + assert_fixed_point_precision {|n| cos(BigDecimal(PI(50) / 2), n) } + assert_fixed_point_precision {|n| cos(BigDecimal(PI(50) * 201 / 2), n) } end def test_atan @@ -58,13 +79,23 @@ def test_atan assert_in_delta(Math::PI/4, atan(BigDecimal("1.0"), N)) assert_in_delta(Math::PI/6, atan(sqrt(BigDecimal("3.0"), N) / 3, N)) assert_in_delta(Math::PI/2, atan(PINF, N)) + assert_in_delta(PI(100) / 3, atan(SQRT3, 100), BigDecimal("1e-100")) assert_equal(BigDecimal("0.823840753418636291769355073102514088959345624027952954058347023122539489"), atan(BigDecimal("1.08"), 72).round(72), '[ruby-dev:41257]') + assert_relative_precision {|n| atan(BigDecimal("2"), n)} + assert_relative_precision {|n| atan(BigDecimal("1e-30"), n)} + assert_relative_precision {|n| atan(BigDecimal("1e30"), n)} end def test_log assert_equal(0, BigMath.log(BigDecimal("1.0"), 10)) assert_in_epsilon(Math.log(10)*1000, BigMath.log(BigDecimal("1e1000"), 10)) + assert_in_epsilon(BigDecimal("2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862"), + BigMath.log(BigDecimal("10"), 100), BigDecimal("1e-100")) + assert_relative_precision {|n| BigMath.log(BigDecimal("2"), n) } + assert_relative_precision {|n| BigMath.log(BigDecimal("1e-30") + 1, n) } + assert_relative_precision {|n| BigMath.log(BigDecimal("1e-30"), n) } + assert_relative_precision {|n| BigMath.log(BigDecimal("1e30"), n) } assert_raise(Math::DomainError) {BigMath.log(BigDecimal("0"), 10)} assert_raise(Math::DomainError) {BigMath.log(BigDecimal("-1"), 10)} assert_separately(%w[-rbigdecimal], <<-SRC) From 8d73360346cb61ab3f43dcc8a7b856b44a87d6a3 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Mon, 26 May 2025 18:14:37 +0900 Subject: [PATCH 373/546] Use `Ractor#value` as `Ractor#take` is removed To keep compatibility with older Rubys, left alias value take. See https://bugs.ruby-lang.org/issues/21262 --- test/bigdecimal/test_ractor.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb index 798cc494..58dc5ccc 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -13,11 +13,15 @@ def test_ractor_shareable assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; $VERBOSE = nil + class Ractor + alias value take unless method_defined? :value + end + require "bigdecimal" r = Ractor.new BigDecimal(Math::PI, Float::DIG+1) do |pi| BigDecimal('2.0')*pi end - assert_equal(2*Math::PI, r.take) + assert_equal(2*Math::PI, r.value) end; end end From bae3d4ba3dc9a33c9d0bcc66a645452ec6e4d21e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 29 May 2025 16:48:22 +0900 Subject: [PATCH 374/546] [DOC] Indent multiline call-seq comment (#311) --- ext/bigdecimal/bigdecimal.c | 38 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a707295e..92c82267 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1485,7 +1485,8 @@ BigDecimal_add(VALUE self, VALUE r) return VpCheckGetValue(c); } - /* call-seq: + /* + * call-seq: * self - value -> bigdecimal * * Returns the \BigDecimal difference of +self+ and +value+: @@ -2053,8 +2054,8 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) } /* call-seq: - * a % b - * a.modulo(b) + * a % b + * a.modulo(b) * * Returns the modulus from dividing by b. * @@ -2127,7 +2128,7 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) } /* call-seq: - * remainder(value) + * remainder(value) * * Returns the remainder from dividing by the value. * @@ -2144,7 +2145,7 @@ BigDecimal_remainder(VALUE self, VALUE r) /* remainder */ } /* call-seq: - * divmod(value) + * divmod(value) * * Divides by the specified value, and returns the quotient and modulus * as BigDecimal numbers. The quotient is rounded towards negative infinity. @@ -2316,7 +2317,7 @@ BigDecimal_add2(VALUE self, VALUE b, VALUE n) } /* call-seq: - * sub(value, digits) -> bigdecimal + * sub(value, digits) -> bigdecimal * * Subtract the specified value. * @@ -2415,7 +2416,7 @@ BigDecimal_abs(VALUE self) } /* call-seq: - * sqrt(n) + * sqrt(n) * * Returns the square root of the value. * @@ -2456,7 +2457,7 @@ BigDecimal_fix(VALUE self) } /* call-seq: - * round(n, mode) + * round(n, mode) * * Round to the nearest integer (by default), returning the result as a * BigDecimal if n is specified and positive, or as an Integer if it isn't. @@ -2534,7 +2535,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) } /* call-seq: - * truncate(n) + * truncate(n) * * Truncate to the nearest integer (by default), returning the result as a * BigDecimal. @@ -2596,7 +2597,7 @@ BigDecimal_frac(VALUE self) } /* call-seq: - * floor(n) + * floor(n) * * Return the largest integer less than or equal to the value, as a BigDecimal. * @@ -2643,7 +2644,7 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) } /* call-seq: - * ceil(n) + * ceil(n) * * Return the smallest integer greater than or equal to the value, as a BigDecimal. * @@ -2686,7 +2687,7 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) } /* call-seq: - * to_s(s) + * to_s(s) * * Converts the value to a string. * @@ -2996,8 +2997,8 @@ bigdecimal_power_by_bigdecimal(Real const* x, Real const* exp, ssize_t const n) } /* call-seq: - * power(n) - * power(n, prec) + * power(n) + * power(n, prec) * * Returns the value raised to the power of n. * @@ -3788,8 +3789,9 @@ BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) return VpCheckGetValue(vp); } - /* call-seq: - * BigDecimal.limit(digits) + /* + * call-seq: + * BigDecimal.limit(digits) * * Limit the number of significant digits in newly created BigDecimal * numbers to the specified value. Rounding is performed as necessary, @@ -3923,7 +3925,7 @@ BigDecimal_save_limit(VALUE self) } /* call-seq: - * BigMath.exp(decimal, numeric) -> BigDecimal + * BigMath.exp(decimal, numeric) -> BigDecimal * * Computes the value of e (the base of natural logarithms) raised to the * power of +decimal+, to the specified number of digits of precision. @@ -4054,7 +4056,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } /* call-seq: - * BigMath.log(decimal, numeric) -> BigDecimal + * BigMath.log(decimal, numeric) -> BigDecimal * * Computes the natural logarithm of +decimal+ to the specified number of * digits of precision, +numeric+. From 1fa3eff1715a49ad0d7baf78029e0b82f8bfc7da Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 29 May 2025 16:48:46 +0900 Subject: [PATCH 375/546] Integrate BigDecimal_div and BigDecimal_div2 (#329) * Use the same division/rounding logic in both BigDecimal_div and BigDecimal_div2 * Reduce unnecessarily high division precision when precision is not specified Fixes exponential precision grouth. `max(divisor_prec, dividend_prec) + extra_prec` is enough for division precision. --- ext/bigdecimal/bigdecimal.c | 127 ++++++++++------------------- test/bigdecimal/test_bigdecimal.rb | 27 ++++-- 2 files changed, 63 insertions(+), 91 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 92c82267..90bf9542 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1819,55 +1819,6 @@ BigDecimal_mult(VALUE self, VALUE r) return VpCheckGetValue(c); } -static VALUE -BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) -/* For c = self.div(r): with round operation */ -{ - ENTER(5); - Real *a, *b; - ssize_t a_prec, b_prec; - size_t mx; - - TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); - SAVE(a); - - VALUE rr = r; - if (is_kind_of_BigDecimal(rr)) { - /* do nothing */ - } - else if (RB_INTEGER_TYPE_P(r)) { - rr = rb_inum_convert_to_BigDecimal(r, 0, true); - } - else if (RB_TYPE_P(r, T_FLOAT)) { - rr = rb_float_convert_to_BigDecimal(r, 0, true); - } - else if (RB_TYPE_P(r, T_RATIONAL)) { - rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); - } - - if (!is_kind_of_BigDecimal(rr)) { - return DoSomeOne(self, r, '/'); - } - - TypedData_Get_Struct(rr, Real, &BigDecimal_data_type, b); - SAVE(b); - *div = b; - - BigDecimal_count_precision_and_scale(self, &a_prec, NULL); - BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); - mx = (a_prec > b_prec) ? a_prec : b_prec; - mx *= 2; - - if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) - mx = 2*BIGDECIMAL_DOUBLE_FIGURES; - - GUARD_OBJ((*c), NewZeroWrapNolimit(1, mx + 2*BASE_FIG)); - GUARD_OBJ((*res), NewZeroWrapNolimit(1, (mx + 1)*2 + 2*BASE_FIG)); - VpDivd(*c, *res, a, b); - - return Qnil; -} - static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod); /* call-seq: @@ -1885,20 +1836,15 @@ static VALUE BigDecimal_div(VALUE self, VALUE r) /* For c = self/r: with round operation */ { - ENTER(5); - Real *c=NULL, *res=NULL, *div = NULL; - r = BigDecimal_divide(self, r, &c, &res, &div); - if (!NIL_P(r)) return r; /* coerced by other */ - SAVE(c); SAVE(res); SAVE(div); - /* a/b = c + r/b */ - /* c xxxxx - r 00000yyyyy ==> (y/b)*BASE >= HALF_BASE - */ - /* Round */ - if (VpHasVal(div)) { /* frac[0] must be zero for NaN,INF,Zero */ - VpInternalRound(c, 0, c->frac[c->Prec-1], (DECDIG)(VpBaseVal() * (DECDIG_DBL)res->frac[0] / div->frac[0])); + if ( + !is_kind_of_BigDecimal(r) && + !RB_INTEGER_TYPE_P(r) && + !RB_TYPE_P(r, T_FLOAT) && + !RB_TYPE_P(r, T_RATIONAL) + ) { + return DoSomeOne(self, r, '/'); } - return VpCheckGetValue(c); + return BigDecimal_div2(self, r, INT2FIX(0)); } static VALUE BigDecimal_round(int argc, VALUE *argv, VALUE self); @@ -2188,6 +2134,9 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) { ENTER(5); SIGNED_VALUE ix; + Real *res = NULL; + Real *av = NULL, *bv = NULL, *cv = NULL; + size_t mx, pl; if (NIL_P(n)) { /* div in Float sense */ Real *div = NULL; @@ -2200,33 +2149,39 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) /* div in BigDecimal sense */ ix = check_int_precision(n); - if (ix == 0) { - return BigDecimal_div(self, b); - } - else { - Real *res = NULL; - Real *av = NULL, *bv = NULL, *cv = NULL; - size_t mx = ix + VpBaseFig()*2; - size_t b_prec = ix; - size_t pl = VpSetPrecLimit(0); - - GUARD_OBJ(cv, NewZeroWrapLimited(1, mx + VpBaseFig())); - GUARD_OBJ(av, GetVpValue(self, 1)); + + pl = VpSetPrecLimit(0); + + GUARD_OBJ(av, GetVpValue(self, 1)); + if (RB_FLOAT_TYPE_P(b) && ix > BIGDECIMAL_DOUBLE_FIGURES) { /* TODO: I want to refactor this precision control for a float value later * by introducing an implicit conversion function instead of * GetVpValueWithPrec. */ - if (RB_FLOAT_TYPE_P(b) && b_prec > BIGDECIMAL_DOUBLE_FIGURES) { - b_prec = BIGDECIMAL_DOUBLE_FIGURES; - } - GUARD_OBJ(bv, GetVpValueWithPrec(b, b_prec, 1)); - mx = av->Prec + bv->Prec + 2; - if (mx <= cv->MaxPrec) mx = cv->MaxPrec + 1; - GUARD_OBJ(res, NewZeroWrapNolimit(1, (mx * 2 + 2)*VpBaseFig())); - VpDivd(cv, res, av, bv); - VpSetPrecLimit(pl); - VpLeftRound(cv, VpGetRoundMode(), ix); - return VpCheckGetValue(cv); + GUARD_OBJ(bv, GetVpValueWithPrec(b, BIGDECIMAL_DOUBLE_FIGURES, 1)); } + else { + GUARD_OBJ(bv, GetVpValueWithPrec(b, ix, 1)); + } + + if (ix == 0) { + ssize_t a_prec, b_prec; + BigDecimal_count_precision_and_scale(av->obj, &a_prec, NULL); + BigDecimal_count_precision_and_scale(bv->obj, &b_prec, NULL); + ix = ((a_prec > b_prec) ? a_prec : b_prec) + BIGDECIMAL_DOUBLE_FIGURES; + if (2 * BIGDECIMAL_DOUBLE_FIGURES > ix) + ix = 2 * BIGDECIMAL_DOUBLE_FIGURES; + } + + // VpDivd needs 2 extra DECDIGs. One more is needed for rounding. + GUARD_OBJ(cv, NewZeroWrapLimited(1, ix + 3 * VpBaseFig())); + + mx = bv->Prec + cv->MaxPrec - 1; + if (mx <= av->Prec) mx = av->Prec + 1; + GUARD_OBJ(res, NewZeroWrapNolimit(1, mx * VpBaseFig())); + VpDivd(cv, res, av, bv); + VpSetPrecLimit(pl); + VpLeftRound(cv, VpGetRoundMode(), ix); + return VpCheckGetValue(cv); } /* @@ -6166,7 +6121,7 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) word_c = c->MaxPrec; word_r = r->MaxPrec; - if (word_a >= word_r) goto space_error; + if (word_a >= word_r || word_b + word_c - 2 >= word_r) goto space_error; ind_r = 1; r->frac[0] = 0; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index fab9622a..ac14a0e8 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -510,10 +510,10 @@ def test_round_up BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_DOWN) assert_operator(n4, :>, n4 / 9 * 9) - assert_operator(n5, :>, n5 / 9 * 9) + assert_operator(n5, :<, n5 / 9 * 9) assert_operator(n6, :<, n6 / 9 * 9) assert_operator(m4, :<, m4 / 9 * 9) - assert_operator(m5, :<, m5 / 9 * 9) + assert_operator(m5, :>, m5 / 9 * 9) assert_operator(m6, :>, m6 / 9 * 9) assert_equal(2, n2h.round) assert_equal(3, n3h.round) @@ -982,7 +982,7 @@ def test_div def test_div_gh220 x = BigDecimal("1.0") y = BigDecimal("3672577333.6608990499165058135986328125") - c = BigDecimal("0.272288343892592687909520102748926752911779209181321744700032723729015151607289998e-9") + c = BigDecimal("0.272288343892592687909520102748926752911779209181321745e-9") assert_equal(c, x / y, "[GH-220]") end @@ -995,6 +995,23 @@ def test_div_precision "(101/0.9163472602589686).precision >= (0.9163472602589686).precision #{bug13754}") end + def test_div_various_precisions + a_precs = [5, 20, 70] + b_precs = [*5..80] + exponents = [-5, 0, 5] + a_precs.product(exponents, b_precs, exponents).each do |prec_a, ex_a, prec_b, ex_b| + a = BigDecimal('7.' + '1' * (prec_a - 1) + "e#{ex_a}") + b = BigDecimal('3.' + '1' * (prec_b - 1) + "e#{ex_b}") + c = a / b + max = [prec_a, prec_b, BigDecimal.double_fig].max + # Precision must be enough and not too large + precision_min = max + BigDecimal.double_fig / 2 + precision_max = max + 2 * BigDecimal.double_fig + assert_includes(precision_min..precision_max, c.n_significant_digits) + assert_in_delta(a, c * b, a * 10**(1 - precision_min)) + end + end + def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) @@ -1119,7 +1136,7 @@ def test_div_bigdecimal_with_float_and_precision def test_quo_without_prec x = BigDecimal(5) y = BigDecimal(229) - assert_equal(BigDecimal("0.021834061135371179039301310043668122"), x.quo(y)) + assert_equal(BigDecimal("0.021834061135371179039301310043668"), x.quo(y)) end def test_quo_with_prec @@ -1129,7 +1146,7 @@ def test_quo_with_prec x = BigDecimal(5) y = BigDecimal(229) - assert_equal(BigDecimal("0.021834061135371179039301310043668122"), x.quo(y, 0)) + assert_equal(BigDecimal("0.021834061135371179039301310043668"), x.quo(y, 0)) assert_equal(BigDecimal("0.022"), x.quo(y, 2)) assert_equal(BigDecimal("0.0218"), x.quo(y, 3)) assert_equal(BigDecimal("0.0218341"), x.quo(y, 6)) From c216ed44d9d5832802f573fdceb99ed4ed985629 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 29 May 2025 21:54:07 +0900 Subject: [PATCH 376/546] Fix division rounding (#330) When the intermediate calculation of `a.div(b, 4)` is `0.123000000 000000000 ...`, Round up result may be 0.1230 or 0.1231 depend on remainder of the division. Treat the calculation as `0.123000000 000000001` in such case to calculate rounding correctly. --- ext/bigdecimal/bigdecimal.c | 9 +++++++++ test/bigdecimal/test_bigdecimal.rb | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 90bf9542..7561fb44 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2180,6 +2180,15 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) GUARD_OBJ(res, NewZeroWrapNolimit(1, mx * VpBaseFig())); VpDivd(cv, res, av, bv); VpSetPrecLimit(pl); + if (!VpIsZero(res)) { + // Remainder value affects rounding result. + // ROUND_UP cv = 0.1e0 with ix=10 will be: + // 0.1e0 if remainder == 0 + // 0.1000000001e0 if remainder != 0 + size_t idx = roomof(ix, BASE_FIG); + while (cv->Prec <= idx) cv->frac[cv->Prec++] = 0; + if (cv->frac[idx] == 0 || cv->frac[idx] == HALF_BASE) cv->frac[idx]++; + } VpLeftRound(cv, VpGetRoundMode(), ix); return VpCheckGetValue(cv); } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index ac14a0e8..3dd795cb 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1012,6 +1012,30 @@ def test_div_various_precisions end end + def test_div_rounding_with_small_remainder + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) + assert_equal(BigDecimal('0.12e1'), BigDecimal('1.25').div(BigDecimal("1.#{'0' * 30}1"), 2)) + + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_DOWN) + assert_equal(BigDecimal('0.500000002e0'), BigDecimal('1.000000005').div(2, 9)) + assert_equal(BigDecimal('0.500000003e0'), BigDecimal('1.0000000050000000000001').div(2, 9)) + assert_equal(BigDecimal('0.3333333333e0'), BigDecimal(1).div(3, 10)) + assert_equal(BigDecimal('0.3333333333333333333333333333333333333333e0'), BigDecimal(1).div(3, 40)) + assert_equal(BigDecimal("0.5000000000000000000000000000000000000002e0"), BigDecimal("1.#{'0' * 39}5").div(2, 40)) + assert_equal(BigDecimal("0.5000000000000000000000000000000000000003e0"), BigDecimal("1.#{'0' * 39}5#{'0' * 40}1").div(2, 40)) + assert_equal(BigDecimal("0.5000000000000000000000000000000000000000e0"), BigDecimal("1.#{'0' * 39}1").div(2, 40)) + assert_equal(BigDecimal("0.5000000000000000000000000000000000000001e0"), BigDecimal("1.#{'0' * 39}1#{'0' * 40}1").div(2, 40)) + + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_UP) + assert_equal(BigDecimal('0.3333333334e0'), BigDecimal(1).div(3, 10)) + assert_equal(BigDecimal('0.3333333333333333333333333333333333333334e0'), BigDecimal(1).div(3, 40)) + assert_equal(BigDecimal("0.1000000000000000000000000000000000000001e1"), BigDecimal("3.#{'0' * 40}1").div(3, 40)) + assert_equal(BigDecimal("0.1000000000000000000000000000000000000001e1"), BigDecimal("3.#{'0' * 60}1").div(3, 40)) + assert_equal(BigDecimal("0.100000000000000000000000000001e1"), BigDecimal("3.#{'0' * 40}1").div(3, 30)) + assert_equal(BigDecimal("0.10000000000000000001e1"), BigDecimal("3.#{'0' * 40}1").div(3, 20)) + assert_equal(BigDecimal("0.10000000000000000001e6"), BigDecimal("3.#{'0' * 40}1e5").div(3, 20)) + end + def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) From 5003f24d9c16f47633e452941d38637ab7cef165 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 29 May 2025 23:50:07 +0900 Subject: [PATCH 377/546] CHANGES: Add v3.2.0 entries --- CHANGES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 50ac97e0..b4d88ae5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # CHANGES +## 3.2.0 + +* Fix division rounding. [GH-330] [GH-328] + + **@tompng** + +* Fix exponential precision growth in division. [GH-329] [GH-220] [GH-222] [GH-272] + + **@tompng** + ## 3.1.9 * Accept no digits in the fractional part (#302) From b295c49790a7578d68850eb1e99b1bc4e303a674 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 29 May 2025 23:59:45 +0900 Subject: [PATCH 378/546] Add dev:version:bump rake task --- Rakefile | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Rakefile b/Rakefile index 65384de2..9514e271 100644 --- a/Rakefile +++ b/Rakefile @@ -46,3 +46,34 @@ end desc "Run all benchmarks" task benchmark: benchmark_tasks + +def bump_version(version, commit: false) + bigdecimal_c = File.read("ext/bigdecimal/bigdecimal.c") + current_version = bigdecimal_c[/^#define BIGDECIMAL_VERSION "(.*)"/, 1] + version = version || current_version.succ + puts "Bumping version from #{current_version} to #{version}" + bigdecimal_c.gsub!(/^#define BIGDECIMAL_VERSION "(.*)"/, "#define BIGDECIMAL_VERSION \"#{version}\"") + File.write("ext/bigdecimal/bigdecimal.c", bigdecimal_c) + + if commit + puts "Committing changes" + sh("git", "add", "ext/bigdecimal/bigdecimal.c") + sh("git", "commit", "-m", "Bump version to #{version}") + else + puts "Changes are not committed" + end +end + +namespace :dev do + namespace :version do + task :bump, [:version] do |t, args| + bump_version(args[:version], commit: false) + end + + namespace :bump do + task :commit, [:version] do |t, args| + bump_version(args[:version], commit: true) + end + end + end +end \ No newline at end of file From b5611d7ba61e19af334352f848e457fdf88a1e08 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 30 May 2025 00:03:48 +0900 Subject: [PATCH 379/546] Bump version to 3.2.0 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7561fb44..96a754a9 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.1.9" +#define BIGDECIMAL_VERSION "3.2.0" /* #define ENABLE_NUMERIC_STRING */ From e0cb4a6f86a6f762ea5e06f0335ec0392f04feee Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 30 May 2025 17:59:42 +0900 Subject: [PATCH 380/546] Enabled trusted publisher for rubygems.org (#333) --- .github/workflows/push_gem.yml | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/push_gem.yml diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml new file mode 100644 index 00000000..312504c3 --- /dev/null +++ b/.github/workflows/push_gem.yml @@ -0,0 +1,51 @@ +name: Publish gem to rubygems.org + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + push: + if: github.repository == 'ruby/bigdecimal' + runs-on: ubuntu-latest + + environment: + name: rubygems.org + url: https://rubygems.org/gems/bigdecimal + + permissions: + contents: write + id-token: write + + strategy: + matrix: + ruby: ["ruby", "jruby"] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set up Ruby + uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 + with: + bundler-cache: true + ruby-version: ${{ matrix.ruby }} + + - name: Publish to RubyGems + uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 + + - name: Create GitHub release + run: | + tag_name="$(git describe --tags --abbrev=0)" + gh release create "${tag_name}" --verify-tag --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: matrix.ruby == 'ruby' From 5edc77926adf09ba3ee3178f996e6da327416789 Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 31 May 2025 03:01:41 +0900 Subject: [PATCH 381/546] Apply preclimit in BigDecimal_div2 when specified prec is 0 --- ext/bigdecimal/bigdecimal.c | 1 + test/bigdecimal/test_bigdecimal.rb | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 96a754a9..6b7eaa6c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2151,6 +2151,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) ix = check_int_precision(n); pl = VpSetPrecLimit(0); + if (ix == 0) ix = pl; GUARD_OBJ(av, GetVpValue(self, 1)); if (RB_FLOAT_TYPE_P(b) && ix > BIGDECIMAL_DOUBLE_FIGURES) { diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 3dd795cb..60ce2a20 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1725,6 +1725,24 @@ def test_limit end end + def test_arithmetic_operation_with_limit + BigDecimal.save_limit do + BigDecimal.limit(3) + assert_equal(BigDecimal('0.889'), (BigDecimal('0.8888') + BigDecimal('0'))) + assert_equal(BigDecimal('0.889'), (BigDecimal('0.8888') - BigDecimal('0'))) + assert_equal(BigDecimal('2.66'), (BigDecimal('0.888') * BigDecimal('3'))) + assert_equal(BigDecimal('0.296'), (BigDecimal('0.8888') / BigDecimal('3'))) + assert_equal(BigDecimal('0.889'), BigDecimal('0.8888').add(BigDecimal('0'), 0)) + assert_equal(BigDecimal('0.889'), BigDecimal('0.8888').sub(BigDecimal('0'), 0)) + assert_equal(BigDecimal('2.66'), BigDecimal('0.888').mult(BigDecimal('3'), 0)) + assert_equal(BigDecimal('0.296'), BigDecimal('0.8888').div(BigDecimal('3'), 0)) + assert_equal(BigDecimal('0.8888'), BigDecimal('0.8888').add(BigDecimal('0'), 5)) + assert_equal(BigDecimal('0.8888'), BigDecimal('0.8888').sub(BigDecimal('0'), 5)) + assert_equal(BigDecimal('2.664'), BigDecimal('0.888').mult(BigDecimal('3'), 5)) + assert_equal(BigDecimal('0.29627'), BigDecimal('0.8888').div(BigDecimal('3'), 5)) + end + end + def test_sign BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) From 1cce03cc2aa43014e6955969274d19685a276457 Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 31 May 2025 14:25:31 +0900 Subject: [PATCH 382/546] CHANGES: Add v3.2.1 entries --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b4d88ae5..f34391cf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.2.1 + +* Fix division precision limit. [GH-335] + + **@tompng** + ## 3.2.0 * Fix division rounding. [GH-330] [GH-328] From d6faddba71628291198a2064ec3fc9fec966d360 Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 31 May 2025 14:26:32 +0900 Subject: [PATCH 383/546] Bump version to 3.2.1 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6b7eaa6c..be0756ea 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.2.0" +#define BIGDECIMAL_VERSION "3.2.1" /* #define ENABLE_NUMERIC_STRING */ From 199ebfdf4b4208efa551c16398fc05ca5bbb887d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 4 Jun 2025 23:08:27 +0900 Subject: [PATCH 384/546] Make precision calculation in bigdecimal.div(value, 0) gc-compaction safe (#339) ((Real*)x)->obj, a reference from T_DATA to VALUE, will be garbled after gc compaction. We should avoid using x->obj as possible for now. It should be removed from struct Real in the future. --- ext/bigdecimal/bigdecimal.c | 20 ++++++++----- test/bigdecimal/test_bigdecimal.rb | 47 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index be0756ea..c2706dd2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -513,15 +513,10 @@ BigDecimal_prec(VALUE self) } static void -BigDecimal_count_precision_and_scale(VALUE self, ssize_t *out_precision, ssize_t *out_scale) +VpCountPrecisionAndScale(Real *p, ssize_t *out_precision, ssize_t *out_scale) { - ENTER(1); - if (out_precision == NULL && out_scale == NULL) return; - - Real *p; - GUARD_OBJ(p, GetVpValue(self, 1)); if (VpIsZero(p) || !VpIsDef(p)) { zero: if (out_precision) *out_precision = 0; @@ -625,6 +620,15 @@ BigDecimal_count_precision_and_scale(VALUE self, ssize_t *out_precision, ssize_t } } +static void +BigDecimal_count_precision_and_scale(VALUE self, ssize_t *out_precision, ssize_t *out_scale) +{ + ENTER(1); + Real *p; + GUARD_OBJ(p, GetVpValue(self, 1)); + VpCountPrecisionAndScale(p, out_precision, out_scale); +} + /* * call-seq: * precision -> integer @@ -2166,8 +2170,8 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) if (ix == 0) { ssize_t a_prec, b_prec; - BigDecimal_count_precision_and_scale(av->obj, &a_prec, NULL); - BigDecimal_count_precision_and_scale(bv->obj, &b_prec, NULL); + VpCountPrecisionAndScale(av, &a_prec, NULL); + VpCountPrecisionAndScale(bv, &b_prec, NULL); ix = ((a_prec > b_prec) ? a_prec : b_prec) + BIGDECIMAL_DOUBLE_FIGURES; if (2 * BIGDECIMAL_DOUBLE_FIGURES > ix) ix = 2 * BIGDECIMAL_DOUBLE_FIGURES; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 60ce2a20..c7cbe0fc 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2340,6 +2340,53 @@ def test_bsearch_for_bigdecimal } end + def test_gc_compaction_safe + omit if RUBY_VERSION < "3.2" || RUBY_ENGINE == "truffleruby" + + assert_separately(["-rbigdecimal"], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + x = 1.5 + y = 0.5 + nan = BigDecimal("NaN") + inf = BigDecimal("Infinity") + bx = BigDecimal(x.to_s) + by = BigDecimal(y.to_s) + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_in_delta(x + y, bx + by) + assert_in_delta(x + y, bx.add(by, 10)) + assert_in_delta(x - y, bx - by) + assert_in_delta(x - y, bx.sub(by, 10)) + assert_in_delta(x * y, bx * by) + assert_in_delta(x * y, bx.mult(by, 10)) + assert_in_delta(x / y, bx / by) + assert_in_delta(x / y, bx.div(by, 10)) + assert_in_delta((x / y).floor, bx.div(by)) + assert_in_delta(x % y, bx % by) + assert_in_delta(Math.sqrt(x), bx.sqrt(10)) + assert_equal(x.div(y), bx.div(by)) + assert_equal(x.remainder(y), bx.remainder(by)) + assert_equal(x.divmod(y), bx.divmod(by)) + # assert_equal([0, x], bx.divmod(inf)) + # assert_in_delta(x, bx.remainder(inf)) + # assert((nan + nan).nan?) + # assert((nan - nan).nan?) + assert((nan * nan).nan?) + assert((nan / nan).nan?) + assert((nan % nan).nan?) + assert((inf + inf).infinite?) + assert((inf - inf).nan?) + assert((inf * inf).infinite?) + assert((inf / inf).nan?) + assert((inf % inf).nan?) + # assert_in_delta(Math.exp(x), BigMath.exp(bx, 10)) + # assert_in_delta(x**y, bx**by) + # assert_in_delta(x**y, bx.power(by, 10)) + # assert_in_delta(Math.exp(x), BigMath.exp(bx, 10)) + # assert_in_delta(Math.log(x), BigMath.log(bx, 10)) + end; + end + def assert_no_memory_leak(code, *rest, **opt) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" paths = $LOAD_PATH.map{|path| "-I#{path}" } From 9d9c3525b16151af6209142e4b7142b27fa3b763 Mon Sep 17 00:00:00 2001 From: tompng Date: Wed, 4 Jun 2025 23:35:28 +0900 Subject: [PATCH 385/546] Update CHANGES for 3.2.2 --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f34391cf..cdb6d36f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.2.2 + +* Make precision calculation in bigdecimal.div(value, 0) gc-compaction safe. [GH-340] + + **@tompng** + ## 3.2.1 * Fix division precision limit. [GH-335] From 0838cb1f65d3634df48503a0859eb832427d5b7b Mon Sep 17 00:00:00 2001 From: tompng Date: Wed, 4 Jun 2025 23:35:37 +0900 Subject: [PATCH 386/546] Bump version to 3.2.2 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c2706dd2..486aee86 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.2.1" +#define BIGDECIMAL_VERSION "3.2.2" /* #define ENABLE_NUMERIC_STRING */ From 87c487e8eb7695c4df929a42fd6db031362ce6a4 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 5 Jun 2025 22:51:44 +0900 Subject: [PATCH 387/546] Fix sign of bigdecimal**bigint (#341) --- ext/bigdecimal/bigdecimal.c | 10 +++++----- test/bigdecimal/test_bigdecimal.rb | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 486aee86..b7f78779 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3155,15 +3155,15 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else if (RB_TYPE_P(vexp, T_BIGNUM)) { VALUE abs_value = BigDecimal_abs(self); if (is_one(abs_value)) { - return VpCheckGetValue(NewOneWrapLimited(1, n)); + return VpCheckGetValue(NewOneWrapLimited(is_even(vexp) ? 1 : VpGetSign(x), n)); } else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { if (is_negative(vexp)) { y = NewZeroWrapLimited(1, n); - VpSetInf(y, (is_even(vexp) ? 1 : -1) * VpGetSign(x)); + VpSetInf(y, is_even(vexp) ? 1 : VpGetSign(x)); return VpCheckGetValue(y); } - else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { + else if (BIGDECIMAL_NEGATIVE_P(x) && !is_even(vexp)) { return VpCheckGetValue(NewZeroWrapLimited(-1, n)); } else { @@ -3173,10 +3173,10 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { if (is_positive(vexp)) { y = NewZeroWrapLimited(1, n); - VpSetInf(y, (is_even(vexp) ? 1 : -1) * VpGetSign(x)); + VpSetInf(y, is_even(vexp) ? 1 : VpGetSign(x)); return VpCheckGetValue(y); } - else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { + else if (BIGDECIMAL_NEGATIVE_P(x) && !is_even(vexp)) { return VpCheckGetValue(NewZeroWrapLimited(-1, n)); } else { diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index c7cbe0fc..19a6fb69 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1534,22 +1534,25 @@ def test_power_with_Bignum assert_negative_infinite((-BigDecimal(0)) ** -(2**100 + 1)) assert_equal(1, BigDecimal(1) ** (2**100)) + assert_equal(1, BigDecimal(-1) ** (2**100)) + assert_equal(1, BigDecimal(1) ** (2**100 + 1)) + assert_equal(-1, BigDecimal(-1) ** (2**100 + 1)) assert_positive_infinite(BigDecimal(3) ** (2**100)) assert_positive_zero(BigDecimal(3) ** (-2**100)) - assert_negative_infinite(BigDecimal(-3) ** (2**100)) - assert_positive_infinite(BigDecimal(-3) ** (2**100 + 1)) - assert_negative_zero(BigDecimal(-3) ** (-2**100)) - assert_positive_zero(BigDecimal(-3) ** (-2**100 - 1)) + assert_positive_infinite(BigDecimal(-3) ** (2**100)) + assert_negative_infinite(BigDecimal(-3) ** (2**100 + 1)) + assert_positive_zero(BigDecimal(-3) ** (-2**100)) + assert_negative_zero(BigDecimal(-3) ** (-2**100 - 1)) assert_positive_zero(BigDecimal(0.5, Float::DIG) ** (2**100)) assert_positive_infinite(BigDecimal(0.5, Float::DIG) ** (-2**100)) - assert_negative_zero(BigDecimal(-0.5, Float::DIG) ** (2**100)) - assert_positive_zero(BigDecimal(-0.5, Float::DIG) ** (2**100 - 1)) - assert_negative_infinite(BigDecimal(-0.5, Float::DIG) ** (-2**100)) - assert_positive_infinite(BigDecimal(-0.5, Float::DIG) ** (-2**100 - 1)) + assert_positive_zero(BigDecimal(-0.5, Float::DIG) ** (2**100)) + assert_negative_zero(BigDecimal(-0.5, Float::DIG) ** (2**100 - 1)) + assert_positive_infinite(BigDecimal(-0.5, Float::DIG) ** (-2**100)) + assert_negative_infinite(BigDecimal(-0.5, Float::DIG) ** (-2**100 - 1)) end end From 8459a3cc82e686070f7ab69969471ab27d1c95ba Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 8 Jun 2025 15:26:38 +0900 Subject: [PATCH 388/546] Increase BigMath.atan(x,prec) precision when |x|>0.5 (#320) BigMath.atan(x, prec) is calculated with precision=prec+BigDecimal.double_fig when |x| <= 0.5. But this safe margin was not properly applied when |x| > 0.5. --- lib/bigdecimal/math.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 3ab84d84..5dc33eb5 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -149,9 +149,9 @@ def atan(x, prec) x = -x if neg = x < 0 return pi.div(neg ? -2 : 2, prec) if x.infinite? return pi / (neg ? -4 : 4) if x.round(prec) == 1 - x = BigDecimal("1").div(x, prec) if inv = x > 1 - x = (-1 + sqrt(1 + x**2, prec))/x if dbl = x > 0.5 - n = prec + BigDecimal.double_fig + n = prec + BigDecimal.double_fig + x = BigDecimal("1").div(x, n) if inv = x > 1 + x = (-1 + sqrt(1 + x.mult(x, n), n)).div(x, n) if dbl = x > 0.5 y = x d = y t = x From 641ee7e7803b9f27810ce676db085daa3d4c629a Mon Sep 17 00:00:00 2001 From: Tim Craft Date: Mon, 9 Jun 2025 13:56:30 +0100 Subject: [PATCH 389/546] Fix typo in BigDecimal#scale comment (#348) --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b7f78779..e7bd08c6 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -664,8 +664,8 @@ BigDecimal_precision(VALUE self) * BigDecimal("1").scale # => 0 * BigDecimal("1.1").scale # => 1 * BigDecimal("3.1415").scale # => 4 - * BigDecimal("-1e20").precision # => 0 - * BigDecimal("1e-20").precision # => 20 + * BigDecimal("-1e20").scale # => 0 + * BigDecimal("1e-20").scale # => 20 * BigDecimal("Infinity").scale # => 0 * BigDecimal("-Infinity").scale # => 0 * BigDecimal("NaN").scale # => 0 From 57e0d4037170edb670a5889d56d2bf80b6ec407c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciek=20Rz=C4=85sa?= Date: Tue, 10 Jun 2025 13:10:37 +0200 Subject: [PATCH 390/546] Allow BigDecimal accept Float without precision (#314) * Allow BigDecimal accept Float without precision * Fix typo in BigDecimal#scale comment (#348) * Added tests for float --------- Co-authored-by: Tim Craft --- ext/bigdecimal/bigdecimal.c | 12 ++++-------- test/bigdecimal/test_bigdecimal.rb | 9 ++++++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e7bd08c6..0f2bfc13 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3428,11 +3428,7 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } if (digs == SIZE_MAX) { - if (!raise_exception) - return Qnil; - rb_raise(rb_eArgError, - "can't omit precision for a %"PRIsVALUE".", - CLASS_OF(val)); + digs = 0; } else if (digs > BIGDECIMAL_DOUBLE_FIGURES) { if (!raise_exception) @@ -3682,12 +3678,12 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) * * - Integer, Float, Rational, Complex, or BigDecimal: converted directly: * - * # Integer, Complex, or BigDecimal value does not require ndigits; ignored if given. + * # Integer, Complex, Float, or BigDecimal value does not require ndigits; ignored if given. * BigDecimal(2) # => 0.2e1 * BigDecimal(Complex(2, 0)) # => 0.2e1 * BigDecimal(BigDecimal(2)) # => 0.2e1 - * # Float or Rational value requires ndigits. - * BigDecimal(2.0, 0) # => 0.2e1 + * BigDecimal(2.0) # => 0.2e1 + * # Rational value requires ndigits. * BigDecimal(Rational(2, 1), 0) # => 0.2e1 * * - String: converted by parsing if it contains an integer or floating-point literal; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 19a6fb69..74dadd75 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -166,7 +166,10 @@ def test_BigDecimal_with_float assert_equal(BigDecimal("0.1235"), BigDecimal(0.1234567, 4)) assert_equal(BigDecimal("-0.1235"), BigDecimal(-0.1234567, 4)) assert_equal(BigDecimal("0.01"), BigDecimal(0.01, Float::DIG + 1)) - assert_raise_with_message(ArgumentError, "can't omit precision for a Float.") { BigDecimal(4.2) } + assert_nothing_raised { BigDecimal(4.2) } + assert_equal(BigDecimal(4.2), BigDecimal('4.2')) + assert_equal(BigDecimal("0.12345"), BigDecimal(0.12345, 0)) + assert_equal(BigDecimal("0.12345"), BigDecimal(0.12345)) assert_raise(ArgumentError) { BigDecimal(0.1, Float::DIG + 2) } assert_nothing_raised { BigDecimal(0.1, Float::DIG + 1) } @@ -242,10 +245,10 @@ def test_BigDecimal_with_exception_keyword assert_equal(nil, BigDecimal(42.quo(7), exception: false)) } assert_raise(ArgumentError) { - BigDecimal(4.2, exception: true) + BigDecimal(4.2, Float::DIG + 2, exception: true) } assert_nothing_raised(ArgumentError) { - assert_equal(nil, BigDecimal(4.2, exception: false)) + assert_equal(nil, BigDecimal(4.2, Float::DIG + 2, exception: false)) } # TODO: support conversion from complex # assert_raise(RangeError) { From 2f9a42424702f1f8c3a5680650863a06eab84b66 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 11 Jun 2025 00:52:17 +0900 Subject: [PATCH 391/546] Fix edgecase segfault of BigDecimal#remainder (#349) There is a possibility where coerced remainder returns Qnil. NIL_P(BigDecimal_divremain(self, r, &d, &rv)) doesn't mean d and rv has a calculated value. --- ext/bigdecimal/bigdecimal.c | 14 +++++++------- test/bigdecimal/test_bigdecimal.rb | 6 ++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0f2bfc13..5798a5f8 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2043,14 +2043,14 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) b = GetVpValue(r, 0); } - if (!b) return DoSomeOne(self, r, rb_intern("remainder")); + if (!b) return Qfalse; SAVE(b); if (VpIsPosInf(b) || VpIsNegInf(b)) { GUARD_OBJ(*dv, NewZeroWrapLimited(1, 1)); VpSetZero(*dv, 1); *rv = a; - return Qnil; + return Qtrue; } mx = (a->MaxPrec + b->MaxPrec) *VpBaseFig(); @@ -2074,7 +2074,7 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) *dv = d; *rv = ff; - return Qnil; + return Qtrue; } /* call-seq: @@ -2087,11 +2087,11 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) static VALUE BigDecimal_remainder(VALUE self, VALUE r) /* remainder */ { - VALUE f; Real *d, *rv = 0; - f = BigDecimal_divremain(self, r, &d, &rv); - if (!NIL_P(f)) return f; - return VpCheckGetValue(rv); + if (BigDecimal_divremain(self, r, &d, &rv)) { + return VpCheckGetValue(rv); + } + return DoSomeOne(self, r, rb_intern("remainder")); } /* call-seq: diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 74dadd75..8f1474ad 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1089,6 +1089,12 @@ def test_remainder_with_rational assert_kind_of(BigDecimal, BigDecimal("3").remainder(1.quo(3))) end + def test_remainder_coerce + o = Object.new + def o.coerce(x); [x, BigDecimal("3")]; end + assert_equal(BigDecimal("1.1"), BigDecimal("7.1").remainder(o)) + end + def test_divmod x = BigDecimal((2**100).to_s) assert_equal([(x / 3).floor, 1], x.divmod(3)) From f8928e22faa73444db75a9dbb7690eede4f2a9fd Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 11 Jun 2025 00:53:45 +0900 Subject: [PATCH 392/546] Fix precision of BigMath.sin(x,prec) and BigMath.cos(x,prec) for large x (#346) For large x, sin and cos is calculated: sin(x) = sin(x % (2*PI)) cos(x) = cos(x % (2*PI)) When x is large, PI(prec) is not precise enough to calculate `x%(2*PI)`. Extra precision is needed. --- lib/bigdecimal/math.rb | 6 ++++-- test/bigdecimal/test_bigmath.rb | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 5dc33eb5..3d96e150 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -61,7 +61,8 @@ def sin(x, prec) one = BigDecimal("1") two = BigDecimal("2") x = -x if neg = x < 0 - if x > (twopi = two * BigMath.PI(prec)) + if x > 6 + twopi = two * BigMath.PI(prec + x.exponent) if x > 30 x %= twopi else @@ -105,7 +106,8 @@ def cos(x, prec) one = BigDecimal("1") two = BigDecimal("2") x = -x if x < 0 - if x > (twopi = two * BigMath.PI(prec)) + if x > 6 + twopi = two * BigMath.PI(prec + x.exponent) if x > 30 x %= twopi else diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index d1c94cc6..545a963e 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -49,6 +49,7 @@ def test_sin assert_in_delta(SQRT3 / 2, sin(PI(100) / 3, 100), BigDecimal("1e-100")) assert_in_delta(SQRT2 / 2, sin(PI(100) / 4, 100), BigDecimal("1e-100")) assert_fixed_point_precision {|n| sin(BigDecimal("1"), n) } + assert_fixed_point_precision {|n| sin(BigDecimal("1e50"), n) } assert_fixed_point_precision {|n| sin(BigDecimal("1e-30"), n) } assert_fixed_point_precision {|n| sin(BigDecimal(PI(50)), n) } assert_fixed_point_precision {|n| sin(BigDecimal(PI(50) * 100), n) } @@ -70,6 +71,7 @@ def test_cos assert_in_delta(SQRT3 / 2, cos(PI(100) / 6, 100), BigDecimal("1e-100")) assert_in_delta(SQRT2 / 2, cos(PI(100) / 4, 100), BigDecimal("1e-100")) assert_fixed_point_precision {|n| cos(BigDecimal("1"), n) } + assert_fixed_point_precision {|n| cos(BigDecimal("1e50"), n) } assert_fixed_point_precision {|n| cos(BigDecimal(PI(50) / 2), n) } assert_fixed_point_precision {|n| cos(BigDecimal(PI(50) * 201 / 2), n) } end From 95bf47abb1ebf29fc1a11b54cf2a1ce0c8fa59a0 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 13 Jun 2025 22:30:54 +0900 Subject: [PATCH 393/546] Fix wrong converge check in VpSqrt (#353) --- ext/bigdecimal/bigdecimal.c | 2 +- test/bigdecimal/test_bigdecimal.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 5798a5f8..974cb653 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -7261,7 +7261,7 @@ VpSqrt(Real *y, Real *x) VpDivd(f, r, x, y); /* f = x/y */ VpAddSub(r, f, y, -1); /* r = f - y */ VpMult(f, VpConstPt5, r); /* f = 0.5*r */ - if (VpIsZero(f)) + if (y_prec == y->MaxPrec && VpIsZero(f)) goto converge; VpAddSub(r, f, y, 1); /* r = y + f */ VpAsgn(y, r, 1); /* y = r */ diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 8f1474ad..b0d416d9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1225,6 +1225,8 @@ def test_sqrt_bigdecimal x = BigDecimal((2**200).to_s) assert_equal(2**100, x.sqrt(1)) + assert_in_delta(BigDecimal("4.0000000000000000000125"), BigDecimal("16.0000000000000000001").sqrt(100), BigDecimal("1e-40")) + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) assert_raise_with_message(FloatDomainError, "sqrt of 'NaN'(Not a Number)") { BigDecimal("NaN").sqrt(1) } From 0d7cd7736f489e2521c5141ff048e7c81cae22c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:17:58 +0000 Subject: [PATCH 394/546] Bump step-security/harden-runner from 2.12.0 to 2.12.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.12.0 to 2.12.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/0634a2670c59f64b4a01f0f96f84700a4088b9f0...002fdce3c6a235733a90a27c80493a3241e56863) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.12.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 312504c3..1af8447f 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit From eaa9d8acd659c57b558e150f6266452a011d0be8 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 17 Jun 2025 23:37:54 +0900 Subject: [PATCH 395/546] Ensure BigMath.sin and BigMath.cos to be within -1..1 (#317) --- lib/bigdecimal/math.rb | 3 ++- test/bigdecimal/test_bigmath.rb | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 3d96e150..b264c8b8 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -85,6 +85,7 @@ def sin(x, prec) d = sign * x1.div(z,m) y += d end + y = BigDecimal("1") if y > 1 neg ? -y : y end @@ -130,7 +131,7 @@ def cos(x, prec) d = sign * x1.div(z,m) y += d end - y + y < -1 ? BigDecimal("-1") : y > 1 ? BigDecimal("1") : y end # call-seq: diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 545a963e..e3402211 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -53,6 +53,8 @@ def test_sin assert_fixed_point_precision {|n| sin(BigDecimal("1e-30"), n) } assert_fixed_point_precision {|n| sin(BigDecimal(PI(50)), n) } assert_fixed_point_precision {|n| sin(BigDecimal(PI(50) * 100), n) } + assert_operator(sin(PI(30) / 2, 30), :<=, 1) + assert_operator(sin(-PI(30) / 2, 30), :>=, -1) end def test_cos @@ -74,6 +76,8 @@ def test_cos assert_fixed_point_precision {|n| cos(BigDecimal("1e50"), n) } assert_fixed_point_precision {|n| cos(BigDecimal(PI(50) / 2), n) } assert_fixed_point_precision {|n| cos(BigDecimal(PI(50) * 201 / 2), n) } + assert_operator(cos(PI(30), 30), :>=, -1) + assert_operator(cos(PI(30) * 2, 30), :<=, 1) end def test_atan From bf22f516ab03b60e4800922beeff967ef805a84b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 17 Jun 2025 23:47:54 +0900 Subject: [PATCH 396/546] Remove BigDecimal_divremain(which has a bug) and use BigDecimal_DoDivmod instead (#351) --- ext/bigdecimal/bigdecimal.c | 77 ++++++------------------------ test/bigdecimal/test_bigdecimal.rb | 3 +- 2 files changed, 16 insertions(+), 64 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 974cb653..b4d29958 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1823,7 +1823,7 @@ BigDecimal_mult(VALUE self, VALUE r) return VpCheckGetValue(c); } -static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod); +static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod, bool truncate); /* call-seq: * a / b -> bigdecimal @@ -1893,9 +1893,10 @@ BigDecimal_quo(int argc, VALUE *argv, VALUE self) /* * %: mod = a%b = a - (a.to_f/b).floor * b * div = (a.to_f/b).floor + * In truncate mode, use truncate instead of floor. */ static VALUE -BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) +BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod, bool truncate) { ENTER(8); Real *c=NULL, *d=NULL, *res=NULL; @@ -1978,7 +1979,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) VpMult(res, d, b); VpAddSub(c, a, res, -1); - if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { + if (!truncate && !VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { /* result adjustment for negative case */ res = rbd_reallocate_struct(res, d->MaxPrec); res->MaxPrec = d->MaxPrec; @@ -2017,66 +2018,13 @@ BigDecimal_mod(VALUE self, VALUE r) /* %: a%b = a - (a.to_f/b).floor * b */ ENTER(3); Real *div = NULL, *mod = NULL; - if (BigDecimal_DoDivmod(self, r, &div, &mod)) { - SAVE(div); SAVE(mod); + if (BigDecimal_DoDivmod(self, r, &div, &mod, false)) { + SAVE(div); SAVE(mod); return VpCheckGetValue(mod); } return DoSomeOne(self, r, '%'); } -static VALUE -BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) -{ - ENTER(10); - size_t mx; - Real *a = NULL, *b = NULL, *c = NULL, *res = NULL, *d = NULL, *rr = NULL, *ff = NULL; - Real *f = NULL; - - GUARD_OBJ(a, GetVpValue(self, 1)); - if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, 0, 1); - } - else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); - } - else { - b = GetVpValue(r, 0); - } - - if (!b) return Qfalse; - SAVE(b); - - if (VpIsPosInf(b) || VpIsNegInf(b)) { - GUARD_OBJ(*dv, NewZeroWrapLimited(1, 1)); - VpSetZero(*dv, 1); - *rv = a; - return Qtrue; - } - - mx = (a->MaxPrec + b->MaxPrec) *VpBaseFig(); - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); - GUARD_OBJ(res, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); - GUARD_OBJ(rr, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); - GUARD_OBJ(ff, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); - - VpDivd(c, res, a, b); - - mx = c->Prec *(VpBaseFig() + 1); - - GUARD_OBJ(d, NewZeroWrapLimited(1, mx)); - GUARD_OBJ(f, NewZeroWrapLimited(1, mx)); - - VpActiveRound(d, c, VP_ROUND_DOWN, 0); /* 0: round off */ - - VpFrac(f, c); - VpMult(rr, f, b); - VpAddSub(ff, res, rr, 1); - - *dv = d; - *rv = ff; - return Qtrue; -} - /* call-seq: * remainder(value) * @@ -2087,9 +2035,12 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) static VALUE BigDecimal_remainder(VALUE self, VALUE r) /* remainder */ { - Real *d, *rv = 0; - if (BigDecimal_divremain(self, r, &d, &rv)) { - return VpCheckGetValue(rv); + ENTER(3); + Real *div = NULL, *mod = NULL; + + if (BigDecimal_DoDivmod(self, r, &div, &mod, true)) { + SAVE(div); SAVE(mod); + return VpCheckGetValue(mod); } return DoSomeOne(self, r, rb_intern("remainder")); } @@ -2122,7 +2073,7 @@ BigDecimal_divmod(VALUE self, VALUE r) ENTER(5); Real *div = NULL, *mod = NULL; - if (BigDecimal_DoDivmod(self, r, &div, &mod)) { + if (BigDecimal_DoDivmod(self, r, &div, &mod, false)) { SAVE(div); SAVE(mod); return rb_assoc_new(VpCheckGetValue(div), VpCheckGetValue(mod)); } @@ -2145,7 +2096,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) if (NIL_P(n)) { /* div in Float sense */ Real *div = NULL; Real *mod; - if (BigDecimal_DoDivmod(self, b, &div, &mod)) { + if (BigDecimal_DoDivmod(self, b, &div, &mod, false)) { return BigDecimal_to_i(VpCheckGetValue(div)); } return DoSomeOne(self, b, rb_intern("div")); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index b0d416d9..9a7c71b2 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1079,6 +1079,7 @@ def test_remainder assert_equal(-1, (-x).remainder(3)) assert_equal(1, x.remainder(-3)) assert_equal(-1, (-x).remainder(-3)) + assert_equal(BigDecimal("1e-10"), BigDecimal("1e10").remainder(BigDecimal("3e-10"))) end def test_remainder_with_float @@ -1091,7 +1092,7 @@ def test_remainder_with_rational def test_remainder_coerce o = Object.new - def o.coerce(x); [x, BigDecimal("3")]; end + def o.coerce(x); [x, BigDecimal("-3")]; end assert_equal(BigDecimal("1.1"), BigDecimal("7.1").remainder(o)) end From 33b3e0e3ff13519dbd5ea55e654c58a60e0e4468 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:02:36 +0000 Subject: [PATCH 397/546] Bump step-security/harden-runner from 2.12.1 to 2.12.2 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.12.1 to 2.12.2. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/002fdce3c6a235733a90a27c80493a3241e56863...6c439dc8bdf85cadbbce9ed30d1c7b959517bc49) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.12.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 1af8447f..ed57baee 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 with: egress-policy: audit From b6d2987b94f8aef278613ff03131559ef67440a3 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 6 Jul 2025 12:39:17 +0900 Subject: [PATCH 398/546] Remove back pointer from Real to VALUE (#344) ((Real*)x)->obj, a reference from DATA to VALUE, will be garbled after gc compaction. Real should not hold a reference to VALUE. --- ext/bigdecimal/bigdecimal.c | 895 +++++++++++++++-------------- ext/bigdecimal/bigdecimal.h | 5 - test/bigdecimal/test_bigdecimal.rb | 18 +- 3 files changed, 472 insertions(+), 446 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b4d29958..beedb7c2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -68,6 +68,29 @@ static struct { uint8_t mode; } rbd_rounding_modes[RBD_NUM_ROUNDING_MODES]; +typedef struct { + VALUE bigdecimal; + Real *real; +} BDVALUE; + +typedef struct { + VALUE bigdecimal_or_nil; + Real *real_or_null; +} NULLABLE_BDVALUE; + +static inline BDVALUE +bdvalue_nonnullable(NULLABLE_BDVALUE v) +{ + assert(v.real_or_null != NULL); + return (BDVALUE) { v.bigdecimal_or_nil, v.real_or_null }; +} + +static inline NULLABLE_BDVALUE +bdvalue_nullable(BDVALUE v) +{ + return (NULLABLE_BDVALUE) { v.bigdecimal, v.real }; +} + /* MACRO's to guard objects from GC by keeping them in stack */ #ifdef RBIMPL_ATTR_MAYBE_UNUSED #define ENTER(n) RBIMPL_ATTR_MAYBE_UNUSED() volatile VALUE vStack[n];int iStack=0 @@ -75,8 +98,8 @@ static struct { #define ENTER(n) volatile VALUE RB_UNUSED_VAR(vStack[n]);int iStack=0 #endif #define PUSH(x) (vStack[iStack++] = (VALUE)(x)) -#define SAVE(p) PUSH((p)->obj) -#define GUARD_OBJ(p,y) ((p)=(y), SAVE(p)) +#define GUARD_OBJ(p, y) ((p)=(y), PUSH((p).bigdecimal)) +#define GUARD_OBJ_OR_NIL(p, y) ((p)=(y), PUSH((p).bigdecimal_or_nil)) #define BASE_FIG BIGDECIMAL_COMPONENT_FIGURES #define BASE BIGDECIMAL_BASE @@ -145,6 +168,9 @@ check_allocation_count_nonzero(void) # define check_allocation_count_nonzero() /* nothing */ #endif /* BIGDECIMAL_DEBUG */ +static NULLABLE_BDVALUE +CreateFromString(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception); + PUREFUNC(static inline size_t rbd_struct_size(size_t const)); static inline size_t @@ -190,18 +216,14 @@ rbd_allocate_struct_decimal_digits(size_t const decimal_digits, bool limit_preci static VALUE BigDecimal_wrap_struct(VALUE obj, Real *vp); -static Real * -rbd_reallocate_struct(Real *real, size_t const internal_digits) +static BDVALUE +rbd_reallocate_struct(BDVALUE value, size_t const internal_digits) { size_t const size = rbd_struct_size(internal_digits); - VALUE obj = real ? real->obj : 0; - Real *new_real = (Real *)ruby_xrealloc(real, size); + Real *new_real = (Real *)ruby_xrealloc(value.real, size); new_real->MaxPrec = internal_digits; - if (obj) { - new_real->obj = 0; - BigDecimal_wrap_struct(obj, new_real); - } - return new_real; + BigDecimal_wrap_struct(value.bigdecimal, new_real); + return (BDVALUE) { value.bigdecimal, new_real }; } static void @@ -277,7 +299,7 @@ rbd_allocate_struct_one_nolimit(int sign, size_t const digits) static unsigned short VpGetException(void); static void VpSetException(unsigned short f); static void VpCheckException(Real *p, bool always); -static VALUE VpCheckGetValue(Real *p); +static VALUE CheckGetValue(BDVALUE v); static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); static Real *VpCopy(Real *pv, Real const* const x); @@ -319,58 +341,60 @@ static const rb_data_type_t BigDecimal_data_type = { #endif }; -static Real * +static NULLABLE_BDVALUE rbd_allocate_struct_zero_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) { + VALUE obj = Qnil; Real *real = rbd_allocate_struct_zero(sign, digits, limit_precision); if (real != NULL) { - VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); BigDecimal_wrap_struct(obj, real); } - return real; + return (NULLABLE_BDVALUE) { obj, real }; } -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits)); +MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits)); #define NewZeroWrapLimited rbd_allocate_struct_zero_limited_wrap -static inline Real * +static inline BDVALUE rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits) { - return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, true); + return bdvalue_nonnullable(rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, true)); } -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits)); +MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits)); #define NewZeroWrapNolimit rbd_allocate_struct_zero_nolimit_wrap -static inline Real * +static inline BDVALUE rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits) { - return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, false); + return bdvalue_nonnullable(rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, false)); } -static Real * +static NULLABLE_BDVALUE rbd_allocate_struct_one_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) { + VALUE obj = Qnil; Real *real = rbd_allocate_struct_one(sign, digits, limit_precision); if (real != NULL) { - VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); BigDecimal_wrap_struct(obj, real); } - return real; + return (NULLABLE_BDVALUE) { obj, real }; } -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits)); +MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits)); #define NewOneWrapLimited rbd_allocate_struct_one_limited_wrap -static inline Real * +static inline BDVALUE rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits) { - return rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, true); + return bdvalue_nonnullable(rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, true)); } -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits)); +MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits)); #define NewOneWrapNolimit rbd_allocate_struct_one_nolimit_wrap -static inline Real * +static inline BDVALUE rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits) { - return rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, false); + return bdvalue_nonnullable(rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, false)); } static inline int @@ -404,8 +428,8 @@ static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); static VALUE rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); -static Real* -GetVpValueWithPrec(VALUE v, long prec, int must) +static NULLABLE_BDVALUE +GetBDValueWithPrecInternal(VALUE v, long prec, int must) { const size_t digs = prec < 0 ? SIZE_MAX : (size_t)prec; @@ -452,19 +476,45 @@ GetVpValueWithPrec(VALUE v, long prec, int must) Real *vp; TypedData_Get_Struct(v, Real, &BigDecimal_data_type, vp); - return vp; + return (NULLABLE_BDVALUE) { v, vp }; SomeOneMayDoIt: if (must) { cannot_be_coerced_into_BigDecimal(rb_eTypeError, v); } - return NULL; /* NULL means to coerce */ + return (NULLABLE_BDVALUE) { Qnil, NULL }; /* NULL means to coerce */ +} + +static inline NULLABLE_BDVALUE +GetBDValueWithPrec(VALUE v, long prec) +{ + return GetBDValueWithPrecInternal(v, prec, 0); } + +static inline BDVALUE +GetBDValueWithPrecMust(VALUE v, long prec) +{ + return bdvalue_nonnullable(GetBDValueWithPrecInternal(v, prec, 1)); +} + +// self must be a receiver of BigDecimal instance method or a gc guarded BigDecimal object. static inline Real* -GetVpValue(VALUE v, int must) +GetSelfVpValue(VALUE self) +{ + return GetBDValueWithPrecMust(self, -1).real; +} + +static inline NULLABLE_BDVALUE +GetBDValue(VALUE v) { - return GetVpValueWithPrec(v, -1, must); + return GetBDValueWithPrec(v, -1); +} + +static inline BDVALUE +GetBDValueMust(VALUE v) +{ + return GetBDValueWithPrecMust(v, -1); } /* call-seq: @@ -499,16 +549,16 @@ static VALUE BigDecimal_prec(VALUE self) { ENTER(1); - Real *p; + BDVALUE v; VALUE obj; rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "BigDecimal#precs is deprecated and will be removed in the future; " "use BigDecimal#precision instead."); - GUARD_OBJ(p, GetVpValue(self, 1)); - obj = rb_assoc_new(SIZET2NUM(p->Prec*VpBaseFig()), - SIZET2NUM(p->MaxPrec*VpBaseFig())); + GUARD_OBJ(v, GetBDValueMust(self)); + obj = rb_assoc_new(SIZET2NUM(v.real->Prec*VpBaseFig()), + SIZET2NUM(v.real->MaxPrec*VpBaseFig())); return obj; } @@ -624,9 +674,9 @@ static void BigDecimal_count_precision_and_scale(VALUE self, ssize_t *out_precision, ssize_t *out_scale) { ENTER(1); - Real *p; - GUARD_OBJ(p, GetVpValue(self, 1)); - VpCountPrecisionAndScale(p, out_precision, out_scale); + BDVALUE v; + GUARD_OBJ(v, GetBDValueMust(self)); + VpCountPrecisionAndScale(v.real, out_precision, out_scale); } /* @@ -716,23 +766,22 @@ static VALUE BigDecimal_n_significant_digits(VALUE self) { ENTER(1); - - Real *p; - GUARD_OBJ(p, GetVpValue(self, 1)); - if (VpIsZero(p) || !VpIsDef(p)) { + BDVALUE v; + GUARD_OBJ(v, GetBDValueMust(self)); + if (VpIsZero(v.real) || !VpIsDef(v.real)) { return INT2FIX(0); } - ssize_t n = p->Prec; /* The length of frac without trailing zeros. */ - for (n = p->Prec; n > 0 && p->frac[n-1] == 0; --n); + ssize_t n = v.real->Prec; /* The length of frac without trailing zeros. */ + for (n = v.real->Prec; n > 0 && v.real->frac[n-1] == 0; --n); if (n == 0) return INT2FIX(0); DECDIG x; int nlz = BASE_FIG; - for (x = p->frac[0]; x > 0; x /= 10) --nlz; + for (x = v.real->frac[0]; x > 0; x /= 10) --nlz; int ntz = 0; - for (x = p->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz; + for (x = v.real->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz; ssize_t n_significant_digits = BASE_FIG*n - nlz - ntz; return SSIZET2NUM(n_significant_digits); @@ -756,15 +805,15 @@ static VALUE BigDecimal_hash(VALUE self) { ENTER(1); - Real *p; + BDVALUE v; st_index_t hash; - GUARD_OBJ(p, GetVpValue(self, 1)); - hash = (st_index_t)p->sign; + GUARD_OBJ(v, GetBDValueMust(self)); + hash = (st_index_t)v.real->sign; /* hash!=2: the case for 0(1),NaN(0) or +-Infinity(3) is sign itself */ if(hash == 2 || hash == (st_index_t)-2) { - hash ^= rb_memhash(p->frac, sizeof(DECDIG)*p->Prec); - hash += p->exponent; + hash ^= rb_memhash(v.real->frac, sizeof(DECDIG)*v.real->Prec); + hash += v.real->exponent; } return ST2FIX(hash); } @@ -785,19 +834,19 @@ static VALUE BigDecimal_dump(int argc, VALUE *argv, VALUE self) { ENTER(5); - Real *vp; + BDVALUE v; char *psz; VALUE dummy; volatile VALUE dump; size_t len; rb_scan_args(argc, argv, "01", &dummy); - GUARD_OBJ(vp,GetVpValue(self, 1)); - dump = rb_str_new(0, VpNumOfChars(vp, "E")+50); + GUARD_OBJ(v, GetBDValueMust(self)); + dump = rb_str_new(0, VpNumOfChars(v.real, "E")+50); psz = RSTRING_PTR(dump); - snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", VpMaxPrec(vp)*VpBaseFig()); + snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", VpMaxPrec(v.real)*VpBaseFig()); len = strlen(psz); - VpToString(vp, psz+len, RSTRING_LEN(dump)-len, 0, 0); + VpToString(v.real, psz+len, RSTRING_LEN(dump)-len, 0, 0); rb_str_resize(dump, strlen(psz)); return dump; } @@ -809,7 +858,7 @@ static VALUE BigDecimal_load(VALUE self, VALUE str) { ENTER(2); - Real *pv; + BDVALUE v; unsigned char *pch; unsigned char ch; unsigned long m=0; @@ -823,12 +872,12 @@ BigDecimal_load(VALUE self, VALUE str) m = m*10 + (unsigned long)(ch-'0'); } if (m > VpBaseFig()) m -= VpBaseFig(); - GUARD_OBJ(pv, VpNewRbClass(m, (char *)pch, self, true, true)); + GUARD_OBJ(v, bdvalue_nonnullable(CreateFromString(m, (char *)pch, self, true, true))); m /= VpBaseFig(); - if (m && pv->MaxPrec > m) { - pv->MaxPrec = m+1; + if (m && v.real->MaxPrec > m) { + v.real->MaxPrec = m+1; } - return VpCheckGetValue(pv); + return CheckGetValue(v); } static unsigned short @@ -1162,33 +1211,25 @@ BigDecimal_wrap_struct(VALUE obj, Real *vp) assert(is_kind_of_BigDecimal(obj)); assert(vp != NULL); - if (vp->obj == obj && RTYPEDDATA_DATA(obj) == vp) + if (RTYPEDDATA_DATA(obj) == vp) return obj; assert(RTYPEDDATA_DATA(obj) == NULL); - assert(vp->obj == 0); RTYPEDDATA_DATA(obj) = vp; - vp->obj = obj; RB_OBJ_FREEZE(obj); return obj; } -VP_EXPORT Real * -VpNewRbClass(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception) +static NULLABLE_BDVALUE +CreateFromString(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception) { VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); Real *pv = VpAlloc(mx, str, strict_p, raise_exception); if (!pv) - return NULL; + return (NULLABLE_BDVALUE) { Qnil, NULL }; BigDecimal_wrap_struct(obj, pv); - return pv; -} - -VP_EXPORT Real * -VpCreateRbObject(size_t mx, const char *str, bool raise_exception) -{ - return VpNewRbClass(mx, str, rb_cBigDecimal, true, raise_exception); + return (NULLABLE_BDVALUE) { obj, pv }; } static Real * @@ -1196,7 +1237,7 @@ VpCopy(Real *pv, Real const* const x) { assert(x != NULL); - pv = rbd_reallocate_struct(pv, x->MaxPrec); + pv = (Real *)ruby_xrealloc(pv, rbd_struct_size(x->MaxPrec)); pv->MaxPrec = x->MaxPrec; pv->Prec = x->Prec; pv->exponent = x->exponent; @@ -1211,7 +1252,7 @@ VpCopy(Real *pv, Real const* const x) static VALUE BigDecimal_IsNaN(VALUE self) { - Real *p = GetVpValue(self, 1); + Real *p = GetSelfVpValue(self); if (VpIsNaN(p)) return Qtrue; return Qfalse; } @@ -1222,7 +1263,7 @@ BigDecimal_IsNaN(VALUE self) static VALUE BigDecimal_IsInfinite(VALUE self) { - Real *p = GetVpValue(self, 1); + Real *p = GetSelfVpValue(self); if (VpIsPosInf(p)) return INT2FIX(1); if (VpIsNegInf(p)) return INT2FIX(-1); return Qnil; @@ -1232,7 +1273,7 @@ BigDecimal_IsInfinite(VALUE self) static VALUE BigDecimal_IsFinite(VALUE self) { - Real *p = GetVpValue(self, 1); + Real *p = GetSelfVpValue(self); if (VpIsNaN(p)) return Qfalse; if (VpIsInf(p)) return Qfalse; return Qtrue; @@ -1255,16 +1296,16 @@ BigDecimal_to_i(VALUE self) { ENTER(5); ssize_t e, nf; - Real *p; + BDVALUE v; - GUARD_OBJ(p, GetVpValue(self, 1)); - BigDecimal_check_num(p); + GUARD_OBJ(v, GetBDValueMust(self)); + BigDecimal_check_num(v.real); - e = VpExponent10(p); + e = VpExponent10(v.real); if (e <= 0) return INT2FIX(0); nf = VpBaseFig(); if (e <= nf) { - return LONG2NUM((long)(VpGetSign(p) * (DECDIG_DBL_SIGNED)p->frac[0])); + return LONG2NUM((long)(VpGetSign(v.real) * (DECDIG_DBL_SIGNED)v.real->frac[0])); } else { VALUE a = BigDecimal_split(self); @@ -1273,7 +1314,7 @@ BigDecimal_to_i(VALUE self) VALUE ret; ssize_t dpower = e - (ssize_t)RSTRING_LEN(digits); - if (BIGDECIMAL_NEGATIVE_P(p)) { + if (BIGDECIMAL_NEGATIVE_P(v.real)) { numerator = rb_funcall(numerator, '*', 1, INT2FIX(-1)); } if (dpower < 0) { @@ -1301,23 +1342,23 @@ static VALUE BigDecimal_to_f(VALUE self) { ENTER(1); - Real *p; + BDVALUE v; double d; SIGNED_VALUE e; char *buf; volatile VALUE str; - GUARD_OBJ(p, GetVpValue(self, 1)); - if (VpVtoD(&d, &e, p) != 1) + GUARD_OBJ(v, GetBDValueMust(self)); + if (VpVtoD(&d, &e, v.real) != 1) return rb_float_new(d); if (e > (SIGNED_VALUE)(DBL_MAX_10_EXP+BASE_FIG)) goto overflow; if (e < (SIGNED_VALUE)(DBL_MIN_10_EXP-BASE_FIG)) goto underflow; - str = rb_str_new(0, VpNumOfChars(p, "E")); + str = rb_str_new(0, VpNumOfChars(v.real, "E")); buf = RSTRING_PTR(str); - VpToString(p, buf, RSTRING_LEN(str), 0, 0); + VpToString(v.real, buf, RSTRING_LEN(str), 0, 0); errno = 0; d = strtod(buf, 0); if (errno == ERANGE) { @@ -1328,14 +1369,14 @@ BigDecimal_to_f(VALUE self) overflow: VpException(VP_EXCEPTION_OVERFLOW, "BigDecimal to Float conversion", 0); - if (BIGDECIMAL_NEGATIVE_P(p)) + if (BIGDECIMAL_NEGATIVE_P(v.real)) return rb_float_new(VpGetDoubleNegInf()); else return rb_float_new(VpGetDoublePosInf()); underflow: VpException(VP_EXCEPTION_UNDERFLOW, "BigDecimal to Float conversion", 0); - if (BIGDECIMAL_NEGATIVE_P(p)) + if (BIGDECIMAL_NEGATIVE_P(v.real)) return rb_float_new(-0.0); else return rb_float_new(0.0); @@ -1347,15 +1388,15 @@ BigDecimal_to_f(VALUE self) static VALUE BigDecimal_to_r(VALUE self) { - Real *p; + BDVALUE v; ssize_t sign, power, denomi_power; VALUE a, digits, numerator; - p = GetVpValue(self, 1); - BigDecimal_check_num(p); + v = GetBDValueMust(self); + BigDecimal_check_num(v.real); - sign = VpGetSign(p); - power = VpExponent10(p); + sign = VpGetSign(v.real); + power = VpExponent10(v.real); a = BigDecimal_split(self); digits = RARRAY_AREF(a, 1); denomi_power = power - RSTRING_LEN(digits); @@ -1395,21 +1436,21 @@ BigDecimal_coerce(VALUE self, VALUE other) { ENTER(2); VALUE obj; - Real *b; + BDVALUE b; if (RB_TYPE_P(other, T_FLOAT)) { - GUARD_OBJ(b, GetVpValueWithPrec(other, 0, 1)); - obj = rb_assoc_new(VpCheckGetValue(b), self); + GUARD_OBJ(b, GetBDValueWithPrecMust(other, 0)); + obj = rb_assoc_new(CheckGetValue(b), self); } else { if (RB_TYPE_P(other, T_RATIONAL)) { Real* pv = DATA_PTR(self); - GUARD_OBJ(b, GetVpValueWithPrec(other, pv->Prec*VpBaseFig(), 1)); + GUARD_OBJ(b, GetBDValueWithPrecMust(other, pv->Prec*VpBaseFig())); } else { - GUARD_OBJ(b, GetVpValue(other, 1)); + GUARD_OBJ(b, GetBDValueMust(other)); } - obj = rb_assoc_new(b->obj, self); + obj = rb_assoc_new(b.bigdecimal, self); } return obj; @@ -1452,41 +1493,41 @@ static VALUE BigDecimal_add(VALUE self, VALUE r) { ENTER(5); - Real *c, *a, *b; + BDVALUE a, b, c; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); + GUARD_OBJ(a, GetBDValueMust(self)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, 0, 1); + GUARD_OBJ(b, GetBDValueWithPrecMust(r, 0)); } else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + GUARD_OBJ(b, GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig())); } else { - b = GetVpValue(r, 0); + NULLABLE_BDVALUE b2; + GUARD_OBJ_OR_NIL(b2, GetBDValue(r)); + if (!b2.real_or_null) return DoSomeOne(self, r, '+'); + b = bdvalue_nonnullable(b2); } - if (!b) return DoSomeOne(self,r,'+'); - SAVE(b); - - if (VpIsNaN(b)) return b->obj; - if (VpIsNaN(a)) return a->obj; + if (VpIsNaN(b.real)) return b.bigdecimal; + if (VpIsNaN(a.real)) return a.bigdecimal; - mx = GetAddSubPrec(a, b); + mx = GetAddSubPrec(a.real, b.real); if (mx == (size_t)-1L) { GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); - VpAddSub(c, a, b, 1); + VpAddSub(c.real, a.real, b.real, 1); } else { GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); if (!mx) { - VpSetInf(c, VpGetSign(a)); + VpSetInf(c.real, VpGetSign(a.real)); } else { - VpAddSub(c, a, b, 1); + VpAddSub(c.real, a.real, b.real, 1); } } - return VpCheckGetValue(c); + return CheckGetValue(c); } /* @@ -1508,41 +1549,41 @@ static VALUE BigDecimal_sub(VALUE self, VALUE r) { ENTER(5); - Real *c, *a, *b; + BDVALUE a, b, c; size_t mx; - GUARD_OBJ(a, GetVpValue(self,1)); + GUARD_OBJ(a, GetBDValueMust(self)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, 0, 1); + GUARD_OBJ(b, GetBDValueWithPrecMust(r, 0)); } else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + GUARD_OBJ(b, GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig())); } else { - b = GetVpValue(r,0); + NULLABLE_BDVALUE b2; + GUARD_OBJ_OR_NIL(b2, GetBDValue(r)); + if (!b2.real_or_null) return DoSomeOne(self, r, '-'); + b = bdvalue_nonnullable(b2); } - if (!b) return DoSomeOne(self,r,'-'); - SAVE(b); + if (VpIsNaN(b.real)) return b.bigdecimal; + if (VpIsNaN(a.real)) return a.bigdecimal; - if (VpIsNaN(b)) return b->obj; - if (VpIsNaN(a)) return a->obj; - - mx = GetAddSubPrec(a,b); + mx = GetAddSubPrec(a.real, b.real); if (mx == (size_t)-1L) { GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); - VpAddSub(c, a, b, -1); + VpAddSub(c.real, a.real, b.real, -1); } else { GUARD_OBJ(c, NewZeroWrapLimited(1, mx *(VpBaseFig() + 1))); if (!mx) { - VpSetInf(c,VpGetSign(a)); + VpSetInf(c.real, VpGetSign(a.real)); } else { - VpAddSub(c, a, b, -1); + VpAddSub(c.real, a.real, b.real, -1); } } - return VpCheckGetValue(c); + return CheckGetValue(c); } static VALUE @@ -1550,8 +1591,9 @@ BigDecimalCmp(VALUE self, VALUE r,char op) { ENTER(5); SIGNED_VALUE e; - Real *a, *b=0; - GUARD_OBJ(a, GetVpValue(self, 1)); + BDVALUE a; + NULLABLE_BDVALUE b = { Qnil, NULL }; + GUARD_OBJ(a, GetBDValueMust(self)); switch (TYPE(r)) { case T_DATA: if (!is_kind_of_BigDecimal(r)) break; @@ -1559,21 +1601,21 @@ BigDecimalCmp(VALUE self, VALUE r,char op) case T_FIXNUM: /* fall through */ case T_BIGNUM: - GUARD_OBJ(b, GetVpValue(r, 0)); + GUARD_OBJ_OR_NIL(b, GetBDValue(r)); break; case T_FLOAT: - GUARD_OBJ(b, GetVpValueWithPrec(r, 0, 0)); + GUARD_OBJ_OR_NIL(b, GetBDValueWithPrec(r, 0)); break; case T_RATIONAL: - GUARD_OBJ(b, GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 0)); + GUARD_OBJ_OR_NIL(b, GetBDValueWithPrec(r, a.real->Prec*VpBaseFig())); break; default: break; } - if (b == NULL) { + if (b.real_or_null == NULL) { ID f = 0; switch (op) { @@ -1602,8 +1644,7 @@ BigDecimalCmp(VALUE self, VALUE r,char op) } return rb_num_coerce_relop(self, r, f); } - SAVE(b); - e = VpComp(a, b); + e = VpComp(a.real, b.real_or_null); if (e == 999) return (op == '*') ? Qnil : Qfalse; switch (op) { @@ -1643,7 +1684,7 @@ BigDecimalCmp(VALUE self, VALUE r,char op) static VALUE BigDecimal_zero(VALUE self) { - Real *a = GetVpValue(self, 1); + Real *a = GetSelfVpValue(self); return VpIsZero(a) ? Qtrue : Qfalse; } @@ -1651,7 +1692,7 @@ BigDecimal_zero(VALUE self) static VALUE BigDecimal_nonzero(VALUE self) { - Real *a = GetVpValue(self, 1); + Real *a = GetSelfVpValue(self); return VpIsZero(a) ? Qnil : self; } @@ -1778,11 +1819,11 @@ static VALUE BigDecimal_neg(VALUE self) { ENTER(5); - Real *c, *a; - GUARD_OBJ(a, GetVpValue(self, 1)); - GUARD_OBJ(c, NewZeroWrapLimited(1, a->Prec *(VpBaseFig() + 1))); - VpAsgn(c, a, -1); - return VpCheckGetValue(c); + BDVALUE c, a; + GUARD_OBJ(a, GetBDValueMust(self)); + GUARD_OBJ(c, NewZeroWrapLimited(1, a.real->Prec *(VpBaseFig() + 1))); + VpAsgn(c.real, a.real, -1); + return CheckGetValue(c); } /* @@ -1800,30 +1841,30 @@ static VALUE BigDecimal_mult(VALUE self, VALUE r) { ENTER(5); - Real *c, *a, *b; + BDVALUE a, b, c; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); + GUARD_OBJ(a, GetBDValueMust(self)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, 0, 1); + GUARD_OBJ(b, GetBDValueWithPrecMust(r, 0)); } else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + GUARD_OBJ(b, GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig())); } else { - b = GetVpValue(r,0); + NULLABLE_BDVALUE b2; + GUARD_OBJ_OR_NIL(b2, GetBDValue(r)); + if (!b2.real_or_null) return DoSomeOne(self, r, '*'); + b = bdvalue_nonnullable(b2); } - if (!b) return DoSomeOne(self, r, '*'); - SAVE(b); - - mx = a->Prec + b->Prec; + mx = a.real->Prec + b.real->Prec; GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); - VpMult(c, a, b); - return VpCheckGetValue(c); + VpMult(c.real, a.real, b.real); + return CheckGetValue(c); } -static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod, bool truncate); +static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod, bool truncate); /* call-seq: * a / b -> bigdecimal @@ -1896,16 +1937,14 @@ BigDecimal_quo(int argc, VALUE *argv, VALUE self) * In truncate mode, use truncate instead of floor. */ static VALUE -BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod, bool truncate) +BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod, bool truncate) { ENTER(8); - Real *c=NULL, *d=NULL, *res=NULL; - Real *a, *b; + BDVALUE a, b, c, d, e, res; ssize_t a_prec, b_prec; size_t mx; - TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); - SAVE(a); + GUARD_OBJ(a, GetBDValueMust(self)); VALUE rr = r; if (is_kind_of_BigDecimal(rr)) { @@ -1918,44 +1957,42 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod, bool truncate) rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { - rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); + rr = rb_rational_convert_to_BigDecimal(r, a.real->Prec*BASE_FIG, true); } if (!is_kind_of_BigDecimal(rr)) { return Qfalse; } - TypedData_Get_Struct(rr, Real, &BigDecimal_data_type, b); - SAVE(b); + GUARD_OBJ(b, GetBDValueMust(rr)); - if (VpIsNaN(a) || VpIsNaN(b)) goto NaN; - if (VpIsInf(a) && VpIsInf(b)) goto NaN; - if (VpIsZero(b)) { + if (VpIsNaN(a.real) || VpIsNaN(b.real)) goto NaN; + if (VpIsInf(a.real) && VpIsInf(b.real)) goto NaN; + if (VpIsZero(b.real)) { rb_raise(rb_eZeroDivError, "divided by 0"); } - if (VpIsInf(a)) { - if (VpGetSign(a) == VpGetSign(b)) { + if (VpIsInf(a.real)) { + if (VpGetSign(a.real) == VpGetSign(b.real)) { VALUE inf = BigDecimal_positive_infinity(); - TypedData_Get_Struct(inf, Real, &BigDecimal_data_type, *div); + *div = (NULLABLE_BDVALUE) { inf, DATA_PTR(inf) }; } else { VALUE inf = BigDecimal_negative_infinity(); - TypedData_Get_Struct(inf, Real, &BigDecimal_data_type, *div); + *div = (NULLABLE_BDVALUE) { inf, DATA_PTR(inf) }; } VALUE nan = BigDecimal_nan(); - TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *mod); + *mod = (NULLABLE_BDVALUE) { nan, DATA_PTR(nan) }; return Qtrue; } - if (VpIsInf(b)) { + if (VpIsInf(b.real)) { VALUE zero = BigDecimal_positive_zero(); - TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *div); - *mod = a; + *div = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; + *mod = bdvalue_nullable(a); return Qtrue; } - if (VpIsZero(a)) { + if (VpIsZero(a.real)) { VALUE zero = BigDecimal_positive_zero(); - TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *div); - TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *mod); + *div = *mod = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; return Qtrue; } @@ -1970,36 +2007,35 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod, bool truncate) GUARD_OBJ(c, NewZeroWrapLimited(1, mx + 2*BASE_FIG)); GUARD_OBJ(res, NewZeroWrapNolimit(1, mx*2 + 2*BASE_FIG)); - VpDivd(c, res, a, b); + VpDivd(c.real, res.real, a.real, b.real); - mx = c->Prec * BASE_FIG; + mx = c.real->Prec * BASE_FIG; GUARD_OBJ(d, NewZeroWrapLimited(1, mx)); - VpActiveRound(d, c, VP_ROUND_DOWN, 0); + VpActiveRound(d.real, c.real, VP_ROUND_DOWN, 0); - VpMult(res, d, b); - VpAddSub(c, a, res, -1); + VpMult(res.real, d.real, b.real); + VpAddSub(c.real, a.real, res.real, -1); - if (!truncate && !VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { + if (!truncate && !VpIsZero(c.real) && (VpGetSign(a.real) * VpGetSign(b.real) < 0)) { /* result adjustment for negative case */ - res = rbd_reallocate_struct(res, d->MaxPrec); - res->MaxPrec = d->MaxPrec; - VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, NewZeroWrapLimited(1, GetAddSubPrec(c, b) * 2*BASE_FIG)); - VpAddSub(d, c, b, 1); - *div = res; - *mod = d; + res = rbd_reallocate_struct(res, d.real->MaxPrec); + res.real->MaxPrec = d.real->MaxPrec; + VpAddSub(res.real, d.real, VpOne(), -1); + GUARD_OBJ(e, NewZeroWrapLimited(1, GetAddSubPrec(c.real, b.real) * 2*BASE_FIG)); + VpAddSub(e.real, c.real, b.real, 1); + *div = bdvalue_nullable(res); + *mod = bdvalue_nullable(e); } else { - *div = d; - *mod = c; + *div = bdvalue_nullable(d); + *mod = bdvalue_nullable(c); } return Qtrue; NaN: { VALUE nan = BigDecimal_nan(); - TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *div); - TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *mod); + *div = *mod = (NULLABLE_BDVALUE) { nan, DATA_PTR(nan) }; } return Qtrue; } @@ -2015,12 +2051,10 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod, bool truncate) static VALUE BigDecimal_mod(VALUE self, VALUE r) /* %: a%b = a - (a.to_f/b).floor * b */ { - ENTER(3); - Real *div = NULL, *mod = NULL; + NULLABLE_BDVALUE div, mod; if (BigDecimal_DoDivmod(self, r, &div, &mod, false)) { - SAVE(div); SAVE(mod); - return VpCheckGetValue(mod); + return CheckGetValue(bdvalue_nonnullable(mod)); } return DoSomeOne(self, r, '%'); } @@ -2035,12 +2069,10 @@ BigDecimal_mod(VALUE self, VALUE r) /* %: a%b = a - (a.to_f/b).floor * b */ static VALUE BigDecimal_remainder(VALUE self, VALUE r) /* remainder */ { - ENTER(3); - Real *div = NULL, *mod = NULL; + NULLABLE_BDVALUE div, mod = { Qnil, NULL }; if (BigDecimal_DoDivmod(self, r, &div, &mod, true)) { - SAVE(div); SAVE(mod); - return VpCheckGetValue(mod); + return CheckGetValue(bdvalue_nonnullable(mod)); } return DoSomeOne(self, r, rb_intern("remainder")); } @@ -2070,12 +2102,10 @@ BigDecimal_remainder(VALUE self, VALUE r) /* remainder */ static VALUE BigDecimal_divmod(VALUE self, VALUE r) { - ENTER(5); - Real *div = NULL, *mod = NULL; + NULLABLE_BDVALUE div, mod; if (BigDecimal_DoDivmod(self, r, &div, &mod, false)) { - SAVE(div); SAVE(mod); - return rb_assoc_new(VpCheckGetValue(div), VpCheckGetValue(mod)); + return rb_assoc_new(CheckGetValue(bdvalue_nonnullable(div)), CheckGetValue(bdvalue_nonnullable(mod))); } return DoSomeOne(self,r,rb_intern("divmod")); } @@ -2089,15 +2119,14 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) { ENTER(5); SIGNED_VALUE ix; - Real *res = NULL; - Real *av = NULL, *bv = NULL, *cv = NULL; + BDVALUE av, bv, cv, res; size_t mx, pl; if (NIL_P(n)) { /* div in Float sense */ - Real *div = NULL; - Real *mod; + NULLABLE_BDVALUE div; + NULLABLE_BDVALUE mod; if (BigDecimal_DoDivmod(self, b, &div, &mod, false)) { - return BigDecimal_to_i(VpCheckGetValue(div)); + return BigDecimal_to_i(CheckGetValue(bdvalue_nonnullable(div))); } return DoSomeOne(self, b, rb_intern("div")); } @@ -2108,21 +2137,21 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) pl = VpSetPrecLimit(0); if (ix == 0) ix = pl; - GUARD_OBJ(av, GetVpValue(self, 1)); + GUARD_OBJ(av, GetBDValueMust(self)); if (RB_FLOAT_TYPE_P(b) && ix > BIGDECIMAL_DOUBLE_FIGURES) { /* TODO: I want to refactor this precision control for a float value later * by introducing an implicit conversion function instead of - * GetVpValueWithPrec. */ - GUARD_OBJ(bv, GetVpValueWithPrec(b, BIGDECIMAL_DOUBLE_FIGURES, 1)); + * GetBDValueWithPrecMust. */ + GUARD_OBJ(bv, GetBDValueWithPrecMust(b, BIGDECIMAL_DOUBLE_FIGURES)); } else { - GUARD_OBJ(bv, GetVpValueWithPrec(b, ix, 1)); + GUARD_OBJ(bv, GetBDValueWithPrecMust(b, ix)); } if (ix == 0) { ssize_t a_prec, b_prec; - VpCountPrecisionAndScale(av, &a_prec, NULL); - VpCountPrecisionAndScale(bv, &b_prec, NULL); + VpCountPrecisionAndScale(av.real, &a_prec, NULL); + VpCountPrecisionAndScale(bv.real, &b_prec, NULL); ix = ((a_prec > b_prec) ? a_prec : b_prec) + BIGDECIMAL_DOUBLE_FIGURES; if (2 * BIGDECIMAL_DOUBLE_FIGURES > ix) ix = 2 * BIGDECIMAL_DOUBLE_FIGURES; @@ -2131,22 +2160,22 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) // VpDivd needs 2 extra DECDIGs. One more is needed for rounding. GUARD_OBJ(cv, NewZeroWrapLimited(1, ix + 3 * VpBaseFig())); - mx = bv->Prec + cv->MaxPrec - 1; - if (mx <= av->Prec) mx = av->Prec + 1; + mx = bv.real->Prec + cv.real->MaxPrec - 1; + if (mx <= av.real->Prec) mx = av.real->Prec + 1; GUARD_OBJ(res, NewZeroWrapNolimit(1, mx * VpBaseFig())); - VpDivd(cv, res, av, bv); + VpDivd(cv.real, res.real, av.real, bv.real); VpSetPrecLimit(pl); - if (!VpIsZero(res)) { + if (!VpIsZero(res.real)) { // Remainder value affects rounding result. // ROUND_UP cv = 0.1e0 with ix=10 will be: // 0.1e0 if remainder == 0 // 0.1000000001e0 if remainder != 0 size_t idx = roomof(ix, BASE_FIG); - while (cv->Prec <= idx) cv->frac[cv->Prec++] = 0; - if (cv->frac[idx] == 0 || cv->frac[idx] == HALF_BASE) cv->frac[idx]++; + while (cv.real->Prec <= idx) cv.real->frac[cv.real->Prec++] = 0; + if (cv.real->frac[idx] == 0 || cv.real->frac[idx] == HALF_BASE) cv.real->frac[idx]++; } - VpLeftRound(cv, VpGetRoundMode(), ix); - return VpCheckGetValue(cv); + VpLeftRound(cv.real, VpGetRoundMode(), ix); + return CheckGetValue(cv); } /* @@ -2223,16 +2252,16 @@ static VALUE BigDecimal_add2(VALUE self, VALUE b, VALUE n) { ENTER(2); - Real *cv; + BDVALUE cv; SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_add(self, b); else { size_t pl = VpSetPrecLimit(0); VALUE c = BigDecimal_add(self, b); VpSetPrecLimit(pl); - GUARD_OBJ(cv, GetVpValue(c, 1)); - VpLeftRound(cv, VpGetRoundMode(), mx); - return VpCheckGetValue(cv); + GUARD_OBJ(cv, GetBDValueMust(c)); + VpLeftRound(cv.real, VpGetRoundMode(), mx); + return CheckGetValue(cv); } } @@ -2253,16 +2282,16 @@ static VALUE BigDecimal_sub2(VALUE self, VALUE b, VALUE n) { ENTER(2); - Real *cv; + BDVALUE cv; SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_sub(self, b); else { size_t pl = VpSetPrecLimit(0); VALUE c = BigDecimal_sub(self, b); VpSetPrecLimit(pl); - GUARD_OBJ(cv, GetVpValue(c, 1)); - VpLeftRound(cv, VpGetRoundMode(), mx); - return VpCheckGetValue(cv); + GUARD_OBJ(cv, GetBDValueMust(c)); + VpLeftRound(cv.real, VpGetRoundMode(), mx); + return CheckGetValue(cv); } } @@ -2296,16 +2325,16 @@ static VALUE BigDecimal_mult2(VALUE self, VALUE b, VALUE n) { ENTER(2); - Real *cv; + BDVALUE cv; SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_mult(self, b); else { size_t pl = VpSetPrecLimit(0); VALUE c = BigDecimal_mult(self, b); VpSetPrecLimit(pl); - GUARD_OBJ(cv, GetVpValue(c, 1)); - VpLeftRound(cv, VpGetRoundMode(), mx); - return VpCheckGetValue(cv); + GUARD_OBJ(cv, GetBDValueMust(c)); + VpLeftRound(cv.real, VpGetRoundMode(), mx); + return CheckGetValue(cv); } } @@ -2324,15 +2353,15 @@ static VALUE BigDecimal_abs(VALUE self) { ENTER(5); - Real *c, *a; + BDVALUE c, a; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); - mx = a->Prec *(VpBaseFig() + 1); + GUARD_OBJ(a, GetBDValueMust(self)); + mx = a.real->Prec *(VpBaseFig() + 1); GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); - VpAsgn(c, a, 1); - VpChangeSign(c, 1); - return VpCheckGetValue(c); + VpAsgn(c.real, a.real, 1); + VpChangeSign(c.real, 1); + return CheckGetValue(c); } /* call-seq: @@ -2346,18 +2375,18 @@ static VALUE BigDecimal_sqrt(VALUE self, VALUE nFig) { ENTER(5); - Real *c, *a; + BDVALUE c, a; size_t mx, n; - GUARD_OBJ(a, GetVpValue(self, 1)); - mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(a, GetBDValueMust(self)); + mx = a.real->Prec * (VpBaseFig() + 1); n = check_int_precision(nFig); n += VpDblFig() + VpBaseFig(); if (mx <= n) mx = n; GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); - VpSqrt(c, a); - return VpCheckGetValue(c); + VpSqrt(c.real, a.real); + return CheckGetValue(c); } /* Return the integer part of the number, as a BigDecimal. @@ -2366,14 +2395,14 @@ static VALUE BigDecimal_fix(VALUE self) { ENTER(5); - Real *c, *a; + BDVALUE c, a; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); - mx = a->Prec *(VpBaseFig() + 1); + GUARD_OBJ(a, GetBDValueMust(self)); + mx = a.real->Prec *(VpBaseFig() + 1); GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); - VpActiveRound(c, a, VP_ROUND_DOWN, 0); /* 0: round off */ - return VpCheckGetValue(c); + VpActiveRound(c.real, a.real, VP_ROUND_DOWN, 0); /* 0: round off */ + return CheckGetValue(c); } /* call-seq: @@ -2406,7 +2435,7 @@ static VALUE BigDecimal_round(int argc, VALUE *argv, VALUE self) { ENTER(5); - Real *c, *a; + BDVALUE c, a; int iLoc = 0; VALUE vLoc; VALUE vRound; @@ -2443,15 +2472,15 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) } pl = VpSetPrecLimit(0); - GUARD_OBJ(a, GetVpValue(self, 1)); - mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(a, GetBDValueMust(self)); + mx = a.real->Prec * (VpBaseFig() + 1); GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); - VpActiveRound(c, a, sw, iLoc); + VpActiveRound(c.real, a.real, sw, iLoc); if (round_to_int) { - return BigDecimal_to_i(VpCheckGetValue(c)); + return BigDecimal_to_i(CheckGetValue(c)); } - return VpCheckGetValue(c); + return CheckGetValue(c); } /* call-seq: @@ -2477,7 +2506,7 @@ static VALUE BigDecimal_truncate(int argc, VALUE *argv, VALUE self) { ENTER(5); - Real *c, *a; + BDVALUE c, a; int iLoc; VALUE vLoc; size_t mx, pl = VpSetPrecLimit(0); @@ -2489,15 +2518,15 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self) iLoc = NUM2INT(vLoc); } - GUARD_OBJ(a, GetVpValue(self, 1)); - mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(a, GetBDValueMust(self)); + mx = a.real->Prec * (VpBaseFig() + 1); GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); - VpActiveRound(c, a, VP_ROUND_DOWN, iLoc); /* 0: truncate */ + VpActiveRound(c.real, a.real, VP_ROUND_DOWN, iLoc); /* 0: truncate */ if (argc == 0) { - return BigDecimal_to_i(VpCheckGetValue(c)); + return BigDecimal_to_i(CheckGetValue(c)); } - return VpCheckGetValue(c); + return CheckGetValue(c); } /* Return the fractional part of the number, as a BigDecimal. @@ -2506,14 +2535,14 @@ static VALUE BigDecimal_frac(VALUE self) { ENTER(5); - Real *c, *a; + BDVALUE c, a; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); - mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(a, GetBDValueMust(self)); + mx = a.real->Prec * (VpBaseFig() + 1); GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); - VpFrac(c, a); - return VpCheckGetValue(c); + VpFrac(c.real, a.real); + return CheckGetValue(c); } /* call-seq: @@ -2537,7 +2566,7 @@ static VALUE BigDecimal_floor(int argc, VALUE *argv, VALUE self) { ENTER(5); - Real *c, *a; + BDVALUE c, a; int iLoc; VALUE vLoc; size_t mx, pl = VpSetPrecLimit(0); @@ -2549,18 +2578,18 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) iLoc = NUM2INT(vLoc); } - GUARD_OBJ(a, GetVpValue(self, 1)); - mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(a, GetBDValueMust(self)); + mx = a.real->Prec * (VpBaseFig() + 1); GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); - VpActiveRound(c, a, VP_ROUND_FLOOR, iLoc); + VpActiveRound(c.real, a.real, VP_ROUND_FLOOR, iLoc); #ifdef BIGDECIMAL_DEBUG VPrint(stderr, "floor: c=%\n", c); #endif if (argc == 0) { - return BigDecimal_to_i(VpCheckGetValue(c)); + return BigDecimal_to_i(CheckGetValue(c)); } - return VpCheckGetValue(c); + return CheckGetValue(c); } /* call-seq: @@ -2584,7 +2613,7 @@ static VALUE BigDecimal_ceil(int argc, VALUE *argv, VALUE self) { ENTER(5); - Real *c, *a; + BDVALUE c, a; int iLoc; VALUE vLoc; size_t mx, pl = VpSetPrecLimit(0); @@ -2595,15 +2624,15 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) iLoc = NUM2INT(vLoc); } - GUARD_OBJ(a, GetVpValue(self, 1)); - mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(a, GetBDValueMust(self)); + mx = a.real->Prec * (VpBaseFig() + 1); GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); - VpActiveRound(c, a, VP_ROUND_CEIL, iLoc); + VpActiveRound(c.real, a.real, VP_ROUND_CEIL, iLoc); if (argc == 0) { - return BigDecimal_to_i(VpCheckGetValue(c)); + return BigDecimal_to_i(CheckGetValue(c)); } - return VpCheckGetValue(c); + return CheckGetValue(c); } /* call-seq: @@ -2645,7 +2674,7 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) ENTER(5); int fmt = 0; /* 0: E format, 1: F format */ int fPlus = 0; /* 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ - Real *vp; + BDVALUE v; volatile VALUE str; char *psz; char ch; @@ -2653,7 +2682,7 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) SIGNED_VALUE m; VALUE f; - GUARD_OBJ(vp, GetVpValue(self, 1)); + GUARD_OBJ(v, GetBDValueMust(self)); if (rb_scan_args(argc, argv, "01", &f) == 1) { if (RB_TYPE_P(f, T_STRING)) { @@ -2688,10 +2717,10 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) } } if (fmt) { - nc = VpNumOfChars(vp, "F"); + nc = VpNumOfChars(v.real, "F"); } else { - nc = VpNumOfChars(vp, "E"); + nc = VpNumOfChars(v.real, "E"); } if (mc > 0) { nc += (nc + mc - 1) / mc + 1; @@ -2701,10 +2730,10 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) psz = RSTRING_PTR(str); if (fmt) { - VpToFString(vp, psz, RSTRING_LEN(str), mc, fPlus); + VpToFString(v.real, psz, RSTRING_LEN(str), mc, fPlus); } else { - VpToString (vp, psz, RSTRING_LEN(str), mc, fPlus); + VpToString (v.real, psz, RSTRING_LEN(str), mc, fPlus); } rb_str_resize(str, strlen(psz)); return str; @@ -2738,15 +2767,15 @@ static VALUE BigDecimal_split(VALUE self) { ENTER(5); - Real *vp; + BDVALUE v; VALUE obj,str; ssize_t e, s; char *psz1; - GUARD_OBJ(vp, GetVpValue(self, 1)); - str = rb_str_new(0, VpNumOfChars(vp, "E")); + GUARD_OBJ(v, GetBDValueMust(self)); + str = rb_str_new(0, VpNumOfChars(v.real, "E")); psz1 = RSTRING_PTR(str); - VpSzMantissa(vp, psz1, RSTRING_LEN(str)); + VpSzMantissa(v.real, psz1, RSTRING_LEN(str)); s = 1; if(psz1[0] == '-') { size_t len = strlen(psz1 + 1); @@ -2756,7 +2785,7 @@ BigDecimal_split(VALUE self) s = -1; } if (psz1[0] == 'N') s = 0; /* NaN */ - e = VpExponent10(vp); + e = VpExponent10(v.real); obj = rb_ary_new2(4); rb_ary_push(obj, INT2FIX(s)); rb_ary_push(obj, str); @@ -2774,7 +2803,7 @@ BigDecimal_split(VALUE self) static VALUE BigDecimal_exponent(VALUE self) { - ssize_t e = VpExponent10(GetVpValue(self, 1)); + ssize_t e = VpExponent10(GetSelfVpValue(self)); return SSIZET2NUM(e); } @@ -2787,15 +2816,15 @@ static VALUE BigDecimal_inspect(VALUE self) { ENTER(5); - Real *vp; + BDVALUE v; volatile VALUE str; size_t nc; - GUARD_OBJ(vp, GetVpValue(self, 1)); - nc = VpNumOfChars(vp, "E"); + GUARD_OBJ(v, GetBDValueMust(self)); + nc = VpNumOfChars(v.real, "E"); str = rb_str_new(0, nc); - VpToString(vp, RSTRING_PTR(str), RSTRING_LEN(str), 0, 0); + VpToString(v.real, RSTRING_PTR(str), RSTRING_LEN(str), 0, 0); rb_str_resize(str, strlen(RSTRING_PTR(str))); return str; } @@ -2899,19 +2928,18 @@ is_even(VALUE x) } static VALUE -bigdecimal_power_by_bigdecimal(Real const* x, Real const* exp, ssize_t const n) +bigdecimal_power_by_bigdecimal(BDVALUE x, BDVALUE exp, ssize_t const n) { VALUE log_x, multiplied, y; - volatile VALUE obj = exp->obj; - if (VpIsZero(exp)) { - return VpCheckGetValue(NewOneWrapLimited(1, n)); + if (VpIsZero(exp.real)) { + return CheckGetValue(NewOneWrapLimited(1, n)); } - log_x = BigMath_log(x->obj, SSIZET2NUM(n+1)); - multiplied = BigDecimal_mult2(exp->obj, log_x, SSIZET2NUM(n+1)); + log_x = BigMath_log(x.bigdecimal, SSIZET2NUM(n+1)); + multiplied = BigDecimal_mult2(exp.bigdecimal, log_x, SSIZET2NUM(n+1)); y = BigMath_exp(multiplied, SSIZET2NUM(n)); - RB_GC_GUARD(obj); + RB_GC_GUARD(exp.bigdecimal); return y; } @@ -2931,22 +2959,21 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) { ENTER(5); VALUE vexp, prec; - Real* exp = NULL; - Real *x, *y; + NULLABLE_BDVALUE exp = { Qnil, NULL }; + BDVALUE x, y; ssize_t mp, ma, n; SIGNED_VALUE int_exp; double d; rb_scan_args(argc, argv, "11", &vexp, &prec); - GUARD_OBJ(x, GetVpValue(self, 1)); - n = NIL_P(prec) ? (ssize_t)(x->Prec*VpBaseFig()) : NUM2SSIZET(prec); + GUARD_OBJ(x, GetBDValueMust(self)); + n = NIL_P(prec) ? (ssize_t)(x.real->Prec*VpBaseFig()) : NUM2SSIZET(prec); - if (VpIsNaN(x)) { + if (VpIsNaN(x.real)) { y = NewZeroWrapLimited(1, n); - VpSetNaN(y); - RB_GC_GUARD(y->obj); - return VpCheckGetValue(y); + VpSetNaN(y.real); + return CheckGetValue(y); } retry: @@ -2971,7 +2998,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (NIL_P(prec)) { n += BIGDECIMAL_DOUBLE_FIGURES; } - exp = GetVpValueWithPrec(vexp, 0, 1); + exp = bdvalue_nullable(GetBDValueWithPrecMust(vexp, 0)); break; case T_RATIONAL: @@ -2985,7 +3012,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) vexp = rb_rational_num(vexp); goto retry; } - exp = GetVpValueWithPrec(vexp, n, 1); + exp = bdvalue_nullable(GetBDValueWithPrecMust(vexp, n)); if (NIL_P(prec)) { n += n; } @@ -3000,10 +3027,10 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) goto retry; } if (NIL_P(prec)) { - GUARD_OBJ(y, GetVpValue(vexp, 1)); - n += y->Prec*VpBaseFig(); + GUARD_OBJ(y, GetBDValueMust(vexp)); + n += y.real->Prec*VpBaseFig(); } - exp = DATA_PTR(vexp); + GUARD_OBJ_OR_NIL(exp, bdvalue_nullable(GetBDValueMust(vexp))); break; } /* fall through */ @@ -3013,78 +3040,77 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) RB_OBJ_CLASSNAME(vexp)); } - if (VpIsZero(x)) { + if (VpIsZero(x.real)) { if (is_negative(vexp)) { y = NewZeroWrapNolimit(1, n); - if (BIGDECIMAL_NEGATIVE_P(x)) { + if (BIGDECIMAL_NEGATIVE_P(x.real)) { if (is_integer(vexp)) { if (is_even(vexp)) { /* (-0) ** (-even_integer) -> Infinity */ - VpSetPosInf(y); + VpSetPosInf(y.real); } else { /* (-0) ** (-odd_integer) -> -Infinity */ - VpSetNegInf(y); + VpSetNegInf(y.real); } } else { /* (-0) ** (-non_integer) -> Infinity */ - VpSetPosInf(y); + VpSetPosInf(y.real); } } else { /* (+0) ** (-num) -> Infinity */ - VpSetPosInf(y); + VpSetPosInf(y.real); } - RB_GC_GUARD(y->obj); - return VpCheckGetValue(y); + return CheckGetValue(y); } else if (is_zero(vexp)) { - return VpCheckGetValue(NewOneWrapLimited(1, n)); + return CheckGetValue(NewOneWrapLimited(1, n)); } else { - return VpCheckGetValue(NewZeroWrapLimited(1, n)); + return CheckGetValue(NewZeroWrapLimited(1, n)); } } if (is_zero(vexp)) { - return VpCheckGetValue(NewOneWrapLimited(1, n)); + return CheckGetValue(NewOneWrapLimited(1, n)); } else if (is_one(vexp)) { return self; } - if (VpIsInf(x)) { + if (VpIsInf(x.real)) { if (is_negative(vexp)) { - if (BIGDECIMAL_NEGATIVE_P(x)) { + if (BIGDECIMAL_NEGATIVE_P(x.real)) { if (is_integer(vexp)) { if (is_even(vexp)) { /* (-Infinity) ** (-even_integer) -> +0 */ - return VpCheckGetValue(NewZeroWrapLimited(1, n)); + return CheckGetValue(NewZeroWrapLimited(1, n)); } else { /* (-Infinity) ** (-odd_integer) -> -0 */ - return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + return CheckGetValue(NewZeroWrapLimited(-1, n)); } } else { /* (-Infinity) ** (-non_integer) -> -0 */ - return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + return CheckGetValue(NewZeroWrapLimited(-1, n)); } } else { - return VpCheckGetValue(NewZeroWrapLimited(1, n)); + return CheckGetValue(NewZeroWrapLimited(1, n)); } } else { y = NewZeroWrapLimited(1, n); - if (BIGDECIMAL_NEGATIVE_P(x)) { + if (BIGDECIMAL_NEGATIVE_P(x.real)) { if (is_integer(vexp)) { if (is_even(vexp)) { - VpSetPosInf(y); + VpSetPosInf(y.real); } else { - VpSetNegInf(y); + VpSetNegInf(y.real); } } else { @@ -3094,44 +3120,44 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) } } else { - VpSetPosInf(y); + VpSetPosInf(y.real); } - return VpCheckGetValue(y); + return CheckGetValue(y); } } - if (exp != NULL) { - return bigdecimal_power_by_bigdecimal(x, exp, n); + if (exp.real_or_null != NULL) { + return bigdecimal_power_by_bigdecimal(x, bdvalue_nonnullable(exp), n); } else if (RB_TYPE_P(vexp, T_BIGNUM)) { VALUE abs_value = BigDecimal_abs(self); if (is_one(abs_value)) { - return VpCheckGetValue(NewOneWrapLimited(is_even(vexp) ? 1 : VpGetSign(x), n)); + return CheckGetValue(NewOneWrapLimited(is_even(vexp) ? 1 : VpGetSign(x.real), n)); } else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { if (is_negative(vexp)) { y = NewZeroWrapLimited(1, n); - VpSetInf(y, is_even(vexp) ? 1 : VpGetSign(x)); - return VpCheckGetValue(y); + VpSetInf(y.real, is_even(vexp) ? 1 : VpGetSign(x.real)); + return CheckGetValue(y); } - else if (BIGDECIMAL_NEGATIVE_P(x) && !is_even(vexp)) { - return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + else if (BIGDECIMAL_NEGATIVE_P(x.real) && !is_even(vexp)) { + return CheckGetValue(NewZeroWrapLimited(-1, n)); } else { - return VpCheckGetValue(NewZeroWrapLimited(1, n)); + return CheckGetValue(NewZeroWrapLimited(1, n)); } } else { if (is_positive(vexp)) { y = NewZeroWrapLimited(1, n); - VpSetInf(y, is_even(vexp) ? 1 : VpGetSign(x)); - return VpCheckGetValue(y); + VpSetInf(y.real, is_even(vexp) ? 1 : VpGetSign(x.real)); + return CheckGetValue(y); } - else if (BIGDECIMAL_NEGATIVE_P(x) && !is_even(vexp)) { - return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + else if (BIGDECIMAL_NEGATIVE_P(x.real) && !is_even(vexp)) { + return CheckGetValue(NewZeroWrapLimited(-1, n)); } else { - return VpCheckGetValue(NewZeroWrapLimited(1, n)); + return CheckGetValue(NewZeroWrapLimited(1, n)); } } } @@ -3141,18 +3167,18 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (ma < 0) ma = -ma; if (ma == 0) ma = 1; - if (VpIsDef(x)) { - mp = x->Prec * (VpBaseFig() + 1); + if (VpIsDef(x.real)) { + mp = x.real->Prec * (VpBaseFig() + 1); GUARD_OBJ(y, NewZeroWrapLimited(1, mp * (ma + 1))); } else { GUARD_OBJ(y, NewZeroWrapLimited(1, 1)); } - VpPowerByInt(y, x, int_exp); - if (!NIL_P(prec) && VpIsDef(y)) { - VpMidRound(y, VpGetRoundMode(), n); + VpPowerByInt(y.real, x.real, int_exp); + if (!NIL_P(prec) && VpIsDef(y.real)) { + VpMidRound(y.real, VpGetRoundMode(), n); } - return VpCheckGetValue(y); + return CheckGetValue(y); } /* call-seq: @@ -3185,7 +3211,7 @@ BigDecimal_initialize_copy(VALUE self, VALUE other) Real *x = rb_check_typeddata(other, &BigDecimal_data_type); if (self != other) { - DATA_PTR(self) = VpCopy(pv, x); + BigDecimal_wrap_struct(self, VpCopy(pv, x)); } return self; } @@ -3228,7 +3254,7 @@ check_exception(VALUE bd) Real *vp; TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); - VpCheckGetValue(vp); /* VpCheckGetValue performs exception check */ + VpCheckException(vp, false); return bd; } @@ -3329,10 +3355,15 @@ rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_ex #endif else { VALUE str = rb_big2str(val, 10); - Real *vp = VpCreateRbObject(RSTRING_LEN(str) + BASE_FIG + 1, - RSTRING_PTR(str), true); + BDVALUE v = bdvalue_nonnullable(CreateFromString( + RSTRING_LEN(str) + BASE_FIG + 1, + RSTRING_PTR(str), + rb_cBigDecimal, + true, + true + )); RB_GC_GUARD(str); - return check_exception(vp->obj); + return CheckGetValue(v); } } @@ -3528,10 +3559,9 @@ rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exceptio if (digs == SIZE_MAX) digs = 0; - Real *vp = VpCreateRbObject(digs, c_str, raise_exception); - if (!vp) - return Qnil; - return VpCheckGetValue(vp); + NULLABLE_BDVALUE v = CreateFromString(digs, c_str, rb_cBigDecimal, true, raise_exception); + if (v.bigdecimal_or_nil == Qnil) return Qnil; + return CheckGetValue(bdvalue_nonnullable(v)); } static inline VALUE @@ -3573,7 +3603,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) vp = VpCopy(NULL, vp); /* TODO: rounding */ BigDecimal_wrap_struct(copy, vp); - return VpCheckGetValue(vp); + return check_exception(copy); } else if (RB_INTEGER_TYPE_P(val)) { return rb_inum_convert_to_BigDecimal(val, digs, raise_exception); @@ -3698,11 +3728,11 @@ static VALUE BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) { char const *c_str = StringValueCStr(str); - Real *vp = VpNewRbClass(0, c_str, klass, false, true); - if (!vp) + NULLABLE_BDVALUE v = CreateFromString(0, c_str, klass, false, true); + if (v.bigdecimal_or_nil == Qnil) return Qnil; else - return VpCheckGetValue(vp); + return CheckGetValue(bdvalue_nonnullable(v)); } /* @@ -3757,7 +3787,7 @@ BigDecimal_limit(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_sign(VALUE self) { /* sign */ - int s = GetVpValue(self, 1)->sign; + int s = GetSelfVpValue(self)->sign; return INT2FIX(s); } @@ -3854,7 +3884,8 @@ static VALUE BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) { ssize_t prec, n, i; - Real* vx = NULL; + BDVALUE vx; + NULLABLE_BDVALUE nullable_vx = { Qnil, NULL }; VALUE one, d, y; int negative = 0; int infinite = 0; @@ -3871,16 +3902,17 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) switch (TYPE(x)) { case T_DATA: if (!is_kind_of_BigDecimal(x)) break; - vx = DATA_PTR(x); - negative = BIGDECIMAL_NEGATIVE_P(vx); - infinite = VpIsPosInf(vx) || VpIsNegInf(vx); - nan = VpIsNaN(vx); + vx = GetBDValueMust(x); + nullable_vx = bdvalue_nullable(vx); + negative = BIGDECIMAL_NEGATIVE_P(vx.real); + infinite = VpIsPosInf(vx.real) || VpIsNegInf(vx.real); + nan = VpIsNaN(vx.real); break; case T_FIXNUM: /* fall through */ case T_BIGNUM: - vx = GetVpValue(x, 0); + nullable_vx = GetBDValue(x); break; case T_FLOAT: @@ -3889,12 +3921,12 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) infinite = isinf(flo); nan = isnan(flo); if (!infinite && !nan) { - vx = GetVpValueWithPrec(x, 0, 0); + nullable_vx = GetBDValueWithPrec(x, 0); } break; case T_RATIONAL: - vx = GetVpValueWithPrec(x, prec, 0); + nullable_vx = GetBDValueWithPrec(x, prec); break; default: @@ -3902,37 +3934,35 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } if (infinite) { if (negative) { - return VpCheckGetValue(GetVpValueWithPrec(INT2FIX(0), prec, 1)); + return CheckGetValue(GetBDValueWithPrecMust(INT2FIX(0), prec)); } else { - Real* vy = NewZeroWrapNolimit(1, prec); - VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); - RB_GC_GUARD(vy->obj); - return VpCheckGetValue(vy); + BDVALUE y = NewZeroWrapNolimit(1, prec); + VpSetInf(y.real, VP_SIGN_POSITIVE_INFINITE); + return CheckGetValue(y); } } else if (nan) { - Real* vy = NewZeroWrapNolimit(1, prec); - VpSetNaN(vy); - RB_GC_GUARD(vy->obj); - return VpCheckGetValue(vy); + BDVALUE y = NewZeroWrapNolimit(1, prec); + VpSetNaN(y.real); + return CheckGetValue(y); } - else if (vx == NULL) { + else if (nullable_vx.real_or_null == NULL) { cannot_be_coerced_into_BigDecimal(rb_eArgError, x); } - x = vx->obj; + vx = bdvalue_nonnullable(nullable_vx); + x = vx.bigdecimal; n = prec + BIGDECIMAL_DOUBLE_FIGURES; - negative = BIGDECIMAL_NEGATIVE_P(vx); + negative = BIGDECIMAL_NEGATIVE_P(vx.real); if (negative) { VALUE x_zero = INT2NUM(1); VALUE x_copy = f_BigDecimal(1, &x_zero, klass); x = BigDecimal_initialize_copy(x_copy, x); - vx = DATA_PTR(x); - VpSetSign(vx, 1); + VpSetSign((Real*)DATA_PTR(x), 1); } - one = VpCheckGetValue(NewOneWrapLimited(1, 1)); + one = CheckGetValue(NewOneWrapLimited(1, 1)); y = one; d = y; i = 1; @@ -3988,7 +4018,8 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) { ssize_t prec, n, i; SIGNED_VALUE expo; - Real* vx = NULL; + BDVALUE vx; + NULLABLE_BDVALUE nullable_vx = { Qnil, NULL }; VALUE vn, one, two, w, x2, y, d; int zero = 0; int negative = 0; @@ -4011,11 +4042,12 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) switch (TYPE(x)) { case T_DATA: if (!is_kind_of_BigDecimal(x)) break; - vx = DATA_PTR(x); - zero = VpIsZero(vx); - negative = BIGDECIMAL_NEGATIVE_P(vx); - infinite = VpIsPosInf(vx) || VpIsNegInf(vx); - nan = VpIsNaN(vx); + vx = GetBDValueMust(x); + nullable_vx = bdvalue_nullable(vx); + zero = VpIsZero(vx.real); + negative = BIGDECIMAL_NEGATIVE_P(vx.real); + infinite = VpIsPosInf(vx.real) || VpIsNegInf(vx.real); + nan = VpIsNaN(vx.real); break; case T_FIXNUM: @@ -4030,7 +4062,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) negative = i < 0; get_vp_value: if (zero || negative) break; - vx = GetVpValue(x, 0); + nullable_vx = GetBDValue(x); break; case T_FLOAT: @@ -4040,7 +4072,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) infinite = isinf(flo); nan = isnan(flo); if (!zero && !negative && !infinite && !nan) { - vx = GetVpValueWithPrec(x, 0, 1); + nullable_vx = bdvalue_nullable(GetBDValueWithPrecMust(x, 0)); } break; @@ -4048,7 +4080,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) zero = RRATIONAL_ZERO_P(x); negative = RRATIONAL_NEGATIVE_P(x); if (zero || negative) break; - vx = GetVpValueWithPrec(x, prec, 1); + nullable_vx = bdvalue_nullable(GetBDValueWithPrecMust(x, prec)); break; case T_COMPLEX: @@ -4059,36 +4091,35 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) break; } if (infinite && !negative) { - Real *vy = NewZeroWrapNolimit(1, prec); - RB_GC_GUARD(vy->obj); - VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); - return VpCheckGetValue(vy); + BDVALUE y = NewZeroWrapNolimit(1, prec); + VpSetInf(y.real, VP_SIGN_POSITIVE_INFINITE); + return CheckGetValue(y); } else if (nan) { - Real* vy = NewZeroWrapNolimit(1, prec); - RB_GC_GUARD(vy->obj); - VpSetNaN(vy); - return VpCheckGetValue(vy); + BDVALUE y = NewZeroWrapNolimit(1, prec); + VpSetNaN(y.real); + return CheckGetValue(y); } else if (zero || negative) { rb_raise(rb_eMathDomainError, "Zero or negative argument for log"); } - else if (vx == NULL) { + else if (nullable_vx.real_or_null == NULL) { cannot_be_coerced_into_BigDecimal(rb_eArgError, x); } - x = VpCheckGetValue(vx); + vx = bdvalue_nonnullable(nullable_vx); + x = CheckGetValue(vx); - one = VpCheckGetValue(NewOneWrapLimited(1, 1)); - two = VpCheckGetValue(VpCreateRbObject(1, "2", true)); + one = CheckGetValue(NewOneWrapLimited(1, 1)); + two = CheckGetValue(bdvalue_nonnullable(CreateFromString(1, "2", rb_cBigDecimal, true, true))); n = prec + BIGDECIMAL_DOUBLE_FIGURES; vn = SSIZET2NUM(n); - expo = VpExponent10(vx); + expo = VpExponent10(vx.real); if (expo < 0 || expo >= 3) { char buf[DECIMAL_SIZE_OF_BITS(SIZEOF_VALUE * CHAR_BIT) + 4]; snprintf(buf, sizeof(buf), "1E%"PRIdVALUE, -expo); - x = BigDecimal_mult2(x, VpCheckGetValue(VpCreateRbObject(1, buf, true)), vn); + x = BigDecimal_mult2(x, CheckGetValue(bdvalue_nonnullable(CreateFromString(1, buf, rb_cBigDecimal, true, true))), vn); } else { expo = 0; @@ -4120,7 +4151,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) if (expo != 0) { VALUE log10, vexpo, dy; log10 = BigMath_s_log(klass, INT2FIX(10), vprec); - vexpo = VpCheckGetValue(GetVpValue(SSIZET2NUM(expo), 1)); + vexpo = CheckGetValue(GetBDValueMust(SSIZET2NUM(expo))); dy = BigDecimal_mult(log10, vexpo); y = BigDecimal_add(y, dy); } @@ -4680,10 +4711,10 @@ VpCheckException(Real *p, bool always) } static VALUE -VpCheckGetValue(Real *p) +CheckGetValue(BDVALUE v) { - VpCheckException(p, false); - return p->obj; + VpCheckException(v.real, false); + return v.bigdecimal; } /* diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 54fed811..7b7815be 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -167,7 +167,6 @@ enum rbd_rounding_mode { * r = 0.xxxxxxxxx *BASE**exponent */ typedef struct { - VALUE obj; /* Back pointer(VALUE) for Ruby object. */ size_t MaxPrec; /* Maximum precision size */ /* This is the actual size of frac[] */ /*(frac[0] to frac[MaxPrec] are available). */ @@ -195,10 +194,6 @@ typedef struct { * ------------------ */ -VP_EXPORT Real *VpNewRbClass(size_t mx, char const *str, VALUE klass, bool strict_p, bool raise_exception); - -VP_EXPORT Real *VpCreateRbObject(size_t mx, const char *str, bool raise_exception); - #define VpBaseFig() BIGDECIMAL_COMPONENT_FIGURES #define VpDblFig() BIGDECIMAL_DOUBLE_FIGURES #define VpBaseVal() BIGDECIMAL_BASE diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 9a7c71b2..e14a9f0f 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2382,10 +2382,10 @@ def test_gc_compaction_safe assert_equal(x.div(y), bx.div(by)) assert_equal(x.remainder(y), bx.remainder(by)) assert_equal(x.divmod(y), bx.divmod(by)) - # assert_equal([0, x], bx.divmod(inf)) - # assert_in_delta(x, bx.remainder(inf)) - # assert((nan + nan).nan?) - # assert((nan - nan).nan?) + assert_equal([0, x], bx.divmod(inf)) + assert_in_delta(x, bx.remainder(inf)) + assert((nan + nan).nan?) + assert((nan - nan).nan?) assert((nan * nan).nan?) assert((nan / nan).nan?) assert((nan % nan).nan?) @@ -2394,11 +2394,11 @@ def test_gc_compaction_safe assert((inf * inf).infinite?) assert((inf / inf).nan?) assert((inf % inf).nan?) - # assert_in_delta(Math.exp(x), BigMath.exp(bx, 10)) - # assert_in_delta(x**y, bx**by) - # assert_in_delta(x**y, bx.power(by, 10)) - # assert_in_delta(Math.exp(x), BigMath.exp(bx, 10)) - # assert_in_delta(Math.log(x), BigMath.log(bx, 10)) + assert_in_delta(Math.exp(x), BigMath.exp(bx, 10)) + assert_in_delta(x**y, bx**by) + assert_in_delta(x**y, bx.power(by, 10)) + assert_in_delta(Math.exp(x), BigMath.exp(bx, 10)) + assert_in_delta(Math.log(x), BigMath.log(bx, 10)) end; end From 1b88a50bfcfbe5827c2019a5609c7467fa97981e Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger <697964+dduugg@users.noreply.github.com> Date: Sat, 5 Jul 2025 22:42:17 -0700 Subject: [PATCH 399/546] Update docs for to_d core extensions (#360) --- ext/bigdecimal/bigdecimal.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index beedb7c2..21f6d87b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4333,14 +4333,16 @@ BigDecimal_literal(const char *str) * * When you require +bigdecimal/util+, the #to_d method will be * available on BigDecimal and the native Integer, Float, Rational, - * and String classes: + * String, Complex, and NilClass classes: * * require 'bigdecimal/util' * - * 42.to_d # => 0.42e2 - * 0.5.to_d # => 0.5e0 - * (2/3r).to_d(3) # => 0.667e0 - * "0.5".to_d # => 0.5e0 + * 42.to_d # => 0.42e2 + * 0.5.to_d # => 0.5e0 + * (2/3r).to_d(3) # => 0.667e0 + * "0.5".to_d # => 0.5e0 + * Complex(0.1234567, 0).to_d(4) # => 0.1235e0 + * nil.to_d # => 0.0 * * == Methods for Working with \JSON * From b70a896be6f86d8189c91a98f5c3bf52fee808e2 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 6 Jul 2025 22:02:56 +0900 Subject: [PATCH 400/546] Fix compiling issue (when BIGDECIMAL_DEBUG is 1) (#363) --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 21f6d87b..bae9bec6 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2584,7 +2584,7 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) VpSetPrecLimit(pl); VpActiveRound(c.real, a.real, VP_ROUND_FLOOR, iLoc); #ifdef BIGDECIMAL_DEBUG - VPrint(stderr, "floor: c=%\n", c); + VPrint(stderr, "floor: c=%\n", c.real); #endif if (argc == 0) { return BigDecimal_to_i(CheckGetValue(c)); From f5d17e209e5a8aec11d5cb5adf82fd9996c263d9 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 9 Jul 2025 21:47:34 +0900 Subject: [PATCH 401/546] Use a correct term: engineering notation -> scientific notation (#365) Engineering notation means that the exponent of ten is a multiple of 3. https://en.wikipedia.org/wiki/Engineering_notation --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index bae9bec6..59b93191 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2653,7 +2653,7 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) * If s contains a number, a space is inserted after each group of that many * digits, starting from '.' and counting outwards. * - * If s ends with an 'E', engineering notation (0.xxxxEnn) is used. + * If s ends with an 'E', scientific notation (0.xxxxEnn) is used. * * If s ends with an 'F', conventional floating point notation is used. * From d246504d98c54a378a8975af6ce6d1510c9a9c85 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 10 Jul 2025 22:46:34 +0900 Subject: [PATCH 402/546] Fix to_f underflow check when DECDIG is uint16_t (#364) --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 59b93191..2a576be8 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1353,7 +1353,7 @@ BigDecimal_to_f(VALUE self) return rb_float_new(d); if (e > (SIGNED_VALUE)(DBL_MAX_10_EXP+BASE_FIG)) goto overflow; - if (e < (SIGNED_VALUE)(DBL_MIN_10_EXP-BASE_FIG)) + if (e < (SIGNED_VALUE)(DBL_MIN_10_EXP-DBL_DIG)) goto underflow; str = rb_str_new(0, VpNumOfChars(v.real, "E")); From 1fa512f2a90ab81e5d9685b821a9a2c19da13c7f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 10 Jul 2025 22:53:08 +0900 Subject: [PATCH 403/546] Fix VpNumOfChars calculation for the longest case (#366) --- ext/bigdecimal/bigdecimal.c | 2 +- test/bigdecimal/test_bigdecimal.rb | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 2a576be8..0baea2ae 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -5028,7 +5028,7 @@ VpNumOfChars(Real *vp,const char *pszFmt) case 'E': /* fall through */ default: - nc = BASE_FIG*(vp->Prec + 2)+6; /* 3: sign + exponent chars */ + nc = BASE_FIG * vp->Prec + 25; /* "-0."(3) + digits_chars + "e-"(2) + 64bit_exponent_chars(19) + null(1) */ } return nc; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e14a9f0f..4b114d86 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1503,6 +1503,9 @@ def test_inspect assert_equal("0.123456789012e0", BigDecimal("0.123456789012").inspect) assert_equal("0.123456789012e4", BigDecimal("1234.56789012").inspect) assert_equal("0.123456789012e-4", BigDecimal("0.0000123456789012").inspect) + s = '-0.123456789e-1000000000000000008' + x = BigDecimal(s) + assert_equal(s, x.inspect) unless x.infinite? end def test_power From a35090be89dc931736024015b46e104a4f5d0ec8 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 11 Jul 2025 03:04:52 +0900 Subject: [PATCH 404/546] Fix a bug that exponent overflow is ignored in add, sub, mult and div operation (#367) * Fix a bug that exponent overflow is ignored in x+y and x-y Check VpIsInf before VpSetSign. `VpSetSign(c, sign)` will clear the infinity result. * Fix div and mult sign when result overflows or underflows --- ext/bigdecimal/bigdecimal.c | 23 +++++++++--------- test/bigdecimal/test_bigdecimal.rb | 39 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0baea2ae..86a30da4 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -5489,7 +5489,7 @@ VpAsgn(Real *c, Real *a, int isw) VP_EXPORT size_t VpAddSub(Real *c, Real *a, Real *b, int operation) { - short sw, isw; + short sw, isw, sign; Real *a_ptr, *b_ptr; size_t n, na, nb, i; DECDIG mrv; @@ -5590,19 +5590,20 @@ VpAddSub(Real *c, Real *a, Real *b, int operation) if (isw) { /* addition */ VpSetSign(c, 1); mrv = VpAddAbs(a_ptr, b_ptr, c); - VpSetSign(c, isw / 2); + sign = isw / 2; } else { /* subtraction */ VpSetSign(c, 1); mrv = VpSubAbs(a_ptr, b_ptr, c); - if (a_ptr == a) { - VpSetSign(c,VpGetSign(a)); - } - else { - VpSetSign(c, VpGetSign(a_ptr) * sw); - } + sign = a_ptr == a ? VpGetSign(a) : VpGetSign(a_ptr) * sw; + } + if (VpIsInf(c)) { + VpSetInf(c, sign); + } + else { + VpSetSign(c, sign); + VpInternalRound(c, 0, (c->Prec > 0) ? c->frac[c->Prec-1] : 0, mrv); } - VpInternalRound(c, 0, (c->Prec > 0) ? c->frac[c->Prec-1] : 0, mrv); #ifdef BIGDECIMAL_DEBUG if (gfDebug) { @@ -5995,11 +5996,11 @@ VpMult(Real *c, Real *a, Real *b) /* set LHSV c info */ c->exponent = a->exponent; /* set exponent */ + VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ if (!AddExponent(c, b->exponent)) { if (w) rbd_free_struct(c); return 0; } - VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ carry = 0; nc = ind_c = MxIndAB; memset(c->frac, 0, (nc + 1) * sizeof(DECDIG)); /* Initialize c */ @@ -6246,10 +6247,10 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) out_side: c->Prec = word_c; c->exponent = a->exponent; + VpSetSign(c, VpGetSign(a) * VpGetSign(b)); if (!AddExponent(c, 2)) return 0; if (!AddExponent(c, -(b->exponent))) return 0; - VpSetSign(c, VpGetSign(a) * VpGetSign(b)); VpNmlz(c); /* normalize c */ r->Prec = word_r; r->exponent = a->exponent; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 4b114d86..c90d10ce 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -460,6 +460,45 @@ def test_exception_overflow end end + def test_mult_div_overflow_underflow_sign + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) + + large_x = BigDecimal("10") + 100.times do + x2 = large_x * large_x + break if x2.infinite? + large_x = x2 + end + + small_x = BigDecimal("0.1") + 100.times do + x2 = small_x * small_x + break if x2.zero? + small_x = x2 + end + + assert_positive_infinite(large_x * large_x) + assert_negative_infinite(large_x * (-large_x)) + assert_negative_infinite((-large_x) * large_x) + assert_positive_infinite((-large_x) * (-large_x)) + + assert_positive_zero(small_x * small_x) + assert_negative_zero(small_x * (-small_x)) + assert_negative_zero((-small_x) * small_x) + assert_positive_zero((-small_x) * (-small_x)) + + assert_positive_infinite(large_x.div(small_x, 10)) + assert_negative_infinite(large_x.div(-small_x, 10)) + assert_negative_infinite((-large_x).div(small_x, 10)) + assert_positive_infinite((-large_x).div(-small_x, 10)) + + assert_positive_zero(small_x.div(large_x, 10)) + assert_negative_zero(small_x.div(-large_x, 10)) + assert_negative_zero((-small_x).div(large_x, 10)) + assert_positive_zero((-small_x).div(-large_x, 10)) + end + def test_exception_zerodivide BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) _test_mode(BigDecimal::EXCEPTION_ZERODIVIDE) { 1 / BigDecimal("0") } From 30053448651d248bff4840fc7f4f3ec8cddf62e2 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 11 Jul 2025 03:21:43 +0900 Subject: [PATCH 405/546] Fix dump/load bigdecimal with few or large precs (#362) Dumped string should include Prec instead of MaxPrec. Don't trust MaxPrec of the dumped string. --- ext/bigdecimal/bigdecimal.c | 13 +++---------- test/bigdecimal/test_bigdecimal.rb | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 86a30da4..feb35653 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -844,7 +844,7 @@ BigDecimal_dump(int argc, VALUE *argv, VALUE self) GUARD_OBJ(v, GetBDValueMust(self)); dump = rb_str_new(0, VpNumOfChars(v.real, "E")+50); psz = RSTRING_PTR(dump); - snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", VpMaxPrec(v.real)*VpBaseFig()); + snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", VpPrec(v.real)*VpBaseFig()); len = strlen(psz); VpToString(v.real, psz+len, RSTRING_LEN(dump)-len, 0, 0); rb_str_resize(dump, strlen(psz)); @@ -861,22 +861,15 @@ BigDecimal_load(VALUE self, VALUE str) BDVALUE v; unsigned char *pch; unsigned char ch; - unsigned long m=0; pch = (unsigned char *)StringValueCStr(str); - /* First get max prec */ + /* First skip max prec. Don't trust the value. */ while((*pch) != (unsigned char)'\0' && (ch = *pch++) != (unsigned char)':') { if(!ISDIGIT(ch)) { rb_raise(rb_eTypeError, "load failed: invalid character in the marshaled string"); } - m = m*10 + (unsigned long)(ch-'0'); - } - if (m > VpBaseFig()) m -= VpBaseFig(); - GUARD_OBJ(v, bdvalue_nonnullable(CreateFromString(m, (char *)pch, self, true, true))); - m /= VpBaseFig(); - if (m && v.real->MaxPrec > m) { - v.real->MaxPrec = m+1; } + GUARD_OBJ(v, bdvalue_nonnullable(CreateFromString(0, (char *)pch, self, true, true))); return CheckGetValue(v); } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index c90d10ce..d57868f5 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -760,6 +760,31 @@ def test_marshal assert_raise(TypeError) { Marshal.load(s) } end + def test_dump_extra_high_maxprec + m = BigDecimal(2 ** 1000) + n = BigDecimal(2) ** 1000 + # Even if two bigdecimals have different MaxPrec, + # _dump should return same string if they represent the same value. + assert_equal(m._dump, n._dump) + end + + def test_load_invalid_precision + $VERBOSE, verbose = nil, $VERBOSE + dumped = BigDecimal('1' * 1000)._dump + n = BigDecimal._load(dumped) + digits_part = dumped.split(':').last + too_few_precs = BigDecimal._load('100:' + digits_part) + assert_equal(1000, too_few_precs.precision) + assert_equal(n, too_few_precs) + assert_equal(n.precs, too_few_precs.precs) + too_large_precs = BigDecimal._load('999999999999:' + digits_part) + assert_equal(1000, too_large_precs.precision) + assert_equal(n, too_large_precs) + assert_equal(n.precs, too_large_precs.precs) + ensure + $VERBOSE = verbose + end + def test_finite_infinite_nan BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, false) From 15b7e0a0dc0fa3274f9911218f75ef6b23b6540e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 12 Jul 2025 01:45:23 +0900 Subject: [PATCH 406/546] Check exponent overflow/underflow strictly in AddExponent (#368) BigDecimal's exponent should not overflow/underflow long. To check, BigDecimal restricts the range of vp->exponent. The calculation is complicated and not correct when BASE_FIG is 4. --- ext/bigdecimal/bigdecimal.c | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index feb35653..26ea8a95 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -39,6 +39,11 @@ #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) +/* max_value = 0.9999_9999_9999E[exponent], exponent <= SIGNED_VALUE_MAX */ +#define VP_EXPONENT_MAX (SIGNED_VALUE_MAX / BASE_FIG) +/* min_value = 0.0001_0000_0000E[exponent], exponent-(BASE_FIG-1) >= SIGNED_VALUE_MIN */ +#define VP_EXPONENT_MIN ((SIGNED_VALUE_MIN + BASE_FIG - 1) / BASE_FIG) + VALUE rb_cBigDecimal; VALUE rb_mBigMath; @@ -5084,24 +5089,14 @@ AddExponent(Real *a, SIGNED_VALUE n) { SIGNED_VALUE e = a->exponent; SIGNED_VALUE m = e+n; - SIGNED_VALUE eb, mb; - if (e > 0) { - if (n > 0) { - if (MUL_OVERFLOW_SIGNED_VALUE_P(m, (SIGNED_VALUE)BASE_FIG) || - MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG)) - goto overflow; - mb = m*(SIGNED_VALUE)BASE_FIG; - eb = e*(SIGNED_VALUE)BASE_FIG; - if (eb - mb > 0) goto overflow; - } - } - else if (n < 0) { - if (MUL_OVERFLOW_SIGNED_VALUE_P(m, (SIGNED_VALUE)BASE_FIG) || - MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG)) - goto underflow; - mb = m*(SIGNED_VALUE)BASE_FIG; - eb = e*(SIGNED_VALUE)BASE_FIG; - if (mb - eb > 0) goto underflow; + if (e > 0 && n > 0) { + if (n > VP_EXPONENT_MAX - e) goto overflow; + } else if (e < 0 && n < 0) { + if (n < VP_EXPONENT_MIN - e) goto underflow; + } else if (m > VP_EXPONENT_MAX) { + goto overflow; + } else if (m < VP_EXPONENT_MIN) { + goto underflow; } a->exponent = m; return 1; From 46c0ad661751393b9ad3a8e06a725f8a577518db Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 12 Jul 2025 22:15:28 +0900 Subject: [PATCH 407/546] Strict BigDecimal("0.1e#{exponent}") exponent overflow/underflow check (#369) --- ext/bigdecimal/bigdecimal.c | 63 +++++++++++++---------------- ext/bigdecimal/bits.h | 3 ++ test/bigdecimal/test_bigdecimal.rb | 64 +++++++++++++++++++++--------- 3 files changed, 76 insertions(+), 54 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 26ea8a95..74968576 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -38,11 +38,14 @@ #define SIGNED_VALUE_MAX INTPTR_MAX #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) +#define ADD_OVERFLOW_SIGNED_VALUE_P(a, b) ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) /* max_value = 0.9999_9999_9999E[exponent], exponent <= SIGNED_VALUE_MAX */ #define VP_EXPONENT_MAX (SIGNED_VALUE_MAX / BASE_FIG) /* min_value = 0.0001_0000_0000E[exponent], exponent-(BASE_FIG-1) >= SIGNED_VALUE_MIN */ #define VP_EXPONENT_MIN ((SIGNED_VALUE_MIN + BASE_FIG - 1) / BASE_FIG) +#define EXPONENT_MAX (VP_EXPONENT_MAX * BASE_FIG) +#define EXPONENT_MIN (VP_EXPONENT_MIN * BASE_FIG - (BASE_FIG - 1)) VALUE rb_cBigDecimal; VALUE rb_mBigMath; @@ -6812,7 +6815,7 @@ VP_EXPORT int VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne) { size_t i, j, ind_a, ma, mi, me; - SIGNED_VALUE e, es, eb, ef; + SIGNED_VALUE e; int sign, signe, exponent_overflow; /* get exponent part */ @@ -6835,23 +6838,13 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con ++me; } while (i < me) { - if (MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG)) { - es = e; - goto exp_overflow; - } - es = e * (SIGNED_VALUE)BASE_FIG; - if (MUL_OVERFLOW_SIGNED_VALUE_P(e, 10) || - SIGNED_VALUE_MAX - (exp_chr[i] - '0') < e * 10) - goto exp_overflow; - e = e * 10 + exp_chr[i] - '0'; - if (MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG)) - goto exp_overflow; - if (es > (SIGNED_VALUE)(e * BASE_FIG)) { - exp_overflow: + int dig = exp_chr[i] - '0'; + if (MUL_OVERFLOW_SIGNED_VALUE_P(e, 10) || + ADD_OVERFLOW_SIGNED_VALUE_P(e * 10, signe * dig)) { exponent_overflow = 1; - e = es; /* keep sign */ break; } + e = e * 10 + signe * dig; ++i; } } @@ -6870,34 +6863,32 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con ++mi; } } + /* skip leading zeros in integer part */ + while (i < mi && int_chr[i] == '0') { + ++i; + --ni; + } - e = signe * e; /* e: The value of exponent part. */ - e = e + ni; /* set actual exponent size. */ - - if (e > 0) signe = 1; - else signe = -1; + /* set actual exponent size. */ + if (ADD_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)ni)) { + exponent_overflow = 1; + } else { + e += ni; + } /* Adjust the exponent so that it is the multiple of BASE_FIG. */ - j = 0; - ef = 1; - while (ef) { - if (e >= 0) eb = e; - else eb = -e; - ef = eb / (SIGNED_VALUE)BASE_FIG; - ef = eb - ef * (SIGNED_VALUE)BASE_FIG; - if (ef) { - ++j; /* Means to add one more preceding zero */ - ++e; - } + j = (BASE_FIG - e % BASE_FIG) % BASE_FIG; + if (ADD_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)j)) { + exponent_overflow = 1; + } else { + e += j; } - eb = e / (SIGNED_VALUE)BASE_FIG; - - if (exponent_overflow) { + if (exponent_overflow || e < EXPONENT_MIN || e > EXPONENT_MAX) { int zero = 1; for ( ; i < mi && zero; i++) zero = int_chr[i] == '0'; for (i = 0; i < nf && zero; i++) zero = frac[i] == '0'; - if (!zero && signe > 0) { + if (!zero && e > 0) { VpSetInf(a, sign); VpException(VP_EXCEPTION_INFINITY, "exponent overflow",0); } @@ -6947,7 +6938,7 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con ++j; } a->Prec = ind_a + 1; - a->exponent = eb; + a->exponent = e / (SIGNED_VALUE)BASE_FIG; VpSetSign(a, sign); VpNmlz(a); return 1; diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index 6e1e4776..66efce45 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -26,6 +26,9 @@ ((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \ ((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b))) +#define ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ + ((a) > 0) == ((b) > 0) && ((a) > 0 ? (max) - (a) < (b) : (min) - (a) > (b))) + #ifdef HAVE_UINT128_T # define bit_length(x) \ (unsigned int) \ diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index d57868f5..2a96c9c8 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -9,12 +9,16 @@ class TestBigDecimal < Test::Unit::TestCase LIMITS = RbConfig::LIMITS else require 'fiddle' + INTPTR_MAX = (1 << (Fiddle::SIZEOF_INTPTR_T*8 - 1)) - 1 + INTPTR_MIN = [INTPTR_MAX + 1].pack("L!").unpack("l!")[0] LONG_MAX = (1 << (Fiddle::SIZEOF_LONG*8 - 1)) - 1 LONG_MIN = [LONG_MAX + 1].pack("L!").unpack("l!")[0] LLONG_MAX = (1 << (Fiddle::SIZEOF_LONG_LONG*8 - 1)) - 1 LLONG_MIN = [LLONG_MAX + 1].pack("Q!").unpack("q!")[0] ULLONG_MAX = (1 << Fiddle::SIZEOF_LONG_LONG*8) - 1 LIMITS = { + "INTPTR_MAX" => INTPTR_MAX, + "INTPTR_MIN" => INTPTR_MIN, "LLONG_MIN" => LLONG_MIN, "ULLONG_MAX" => ULLONG_MAX, "FIXNUM_MIN" => LONG_MIN / 2, @@ -25,6 +29,9 @@ class TestBigDecimal < Test::Unit::TestCase }.freeze end + EXPONENT_MAX = LIMITS['INTPTR_MAX'] / BASE_FIG * BASE_FIG + EXPONENT_MIN = (LIMITS['INTPTR_MIN'] - 2) / BASE_FIG * BASE_FIG + BASE_FIG + 1 + ROUNDING_MODE_MAP = [ [ BigDecimal::ROUND_UP, :up], [ BigDecimal::ROUND_DOWN, :down], @@ -80,12 +87,24 @@ def test_BigDecimal assert_raise(ArgumentError) { BigDecimal("1", -1) } assert_raise_with_message(ArgumentError, /"1__1_1"/) { BigDecimal("1__1_1") } assert_raise_with_message(ArgumentError, /"_1_1_1"/) { BigDecimal("_1_1_1") } + assert(BigDecimal("0.1E#{EXPONENT_MAX}").finite?) + assert(BigDecimal("0.1E#{EXPONENT_MIN}").finite?) BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) assert_positive_infinite(BigDecimal("Infinity")) - assert_positive_infinite(BigDecimal("1E1111111111111111111")) + assert_positive_infinite(BigDecimal("0.1E#{EXPONENT_MAX + 1}")) + assert_negative_infinite(BigDecimal("-0.1E#{EXPONENT_MAX + 1}")) + assert_positive_infinite(BigDecimal("1E#{EXPONENT_MAX}")) + assert_negative_infinite(BigDecimal("-1E#{EXPONENT_MAX}")) + assert_positive_zero(BigDecimal("0E#{EXPONENT_MAX + 1}")) + assert_negative_zero(BigDecimal("-0E#{EXPONENT_MAX + 1}")) + assert_positive_zero(BigDecimal("0.1E#{EXPONENT_MIN - 1}")) + assert_negative_zero(BigDecimal("-0.1E#{EXPONENT_MIN - 1}")) + assert_positive_zero(BigDecimal("0.01E#{EXPONENT_MIN}")) + assert_negative_zero(BigDecimal("-0.01E#{EXPONENT_MIN}")) assert_positive_infinite(BigDecimal(" \t\n\r \rInfinity \t\n\r \r")) assert_negative_infinite(BigDecimal("-Infinity")) assert_negative_infinite(BigDecimal(" \t\n\r \r-Infinity \t\n\r \r")) @@ -460,39 +479,48 @@ def test_exception_overflow end end + def test_add_sub_underflow + BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) + x = BigDecimal("0.100000000002E#{EXPONENT_MIN + 10}") + y = BigDecimal("0.100000000001E#{EXPONENT_MIN + 10}") + z = BigDecimal("0.101E#{EXPONENT_MIN + 10}") + assert_not_equal(0, x - z) + assert_not_equal(0, z - y) + assert_positive_zero(x + (-y)) + assert_positive_zero(x - y) + assert_positive_zero((-y) - (-x)) + assert_negative_zero((-x) + y) + assert_negative_zero(y - x) + assert_negative_zero((-x) - (-y)) + end + def test_mult_div_overflow_underflow_sign BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) + large_x = BigDecimal("0.1E#{EXPONENT_MAX}") + small_x = BigDecimal("0.1E#{EXPONENT_MIN}") - large_x = BigDecimal("10") - 100.times do - x2 = large_x * large_x - break if x2.infinite? - large_x = x2 - end - - small_x = BigDecimal("0.1") - 100.times do - x2 = small_x * small_x - break if x2.zero? - small_x = x2 - end - + assert_positive_infinite(large_x * 10) + assert_positive_infinite(10 * large_x) assert_positive_infinite(large_x * large_x) assert_negative_infinite(large_x * (-large_x)) assert_negative_infinite((-large_x) * large_x) assert_positive_infinite((-large_x) * (-large_x)) + assert_positive_zero(small_x * 0.1) + assert_positive_zero(0.1 * small_x) assert_positive_zero(small_x * small_x) assert_negative_zero(small_x * (-small_x)) assert_negative_zero((-small_x) * small_x) assert_positive_zero((-small_x) * (-small_x)) + assert_positive_infinite(large_x.div(0.1, 10)) assert_positive_infinite(large_x.div(small_x, 10)) assert_negative_infinite(large_x.div(-small_x, 10)) assert_negative_infinite((-large_x).div(small_x, 10)) assert_positive_infinite((-large_x).div(-small_x, 10)) + assert_positive_zero(small_x.div(10, 10)) assert_positive_zero(small_x.div(large_x, 10)) assert_negative_zero(small_x.div(-large_x, 10)) assert_negative_zero((-small_x).div(large_x, 10)) @@ -1567,9 +1595,9 @@ def test_inspect assert_equal("0.123456789012e0", BigDecimal("0.123456789012").inspect) assert_equal("0.123456789012e4", BigDecimal("1234.56789012").inspect) assert_equal("0.123456789012e-4", BigDecimal("0.0000123456789012").inspect) - s = '-0.123456789e-1000000000000000008' - x = BigDecimal(s) - assert_equal(s, x.inspect) unless x.infinite? + # Frac part is fully packed, exponent is minimum multiple of BASE_FIG + s = "-0.#{'1' * BASE_FIG}e#{(EXPONENT_MIN / BASE_FIG + 1) * BASE_FIG}" + assert_equal(s, BigDecimal(s).inspect) end def test_power From 163a7db0c3cdf6afa45b50ac19a23a4f5ee94b45 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 14 Jul 2025 18:50:56 +0900 Subject: [PATCH 408/546] Add DECDIG=16bit CI workflow (#370) * Enable building bigdecimal with DECDIG=uint16_t * Pend power test if BASE_FIG==4 Precision of power is buggy. Pend until power fixed. * Add ci for DECDIG=uint16_t --- .github/workflows/ci.yml | 6 +++++- ext/bigdecimal/bigdecimal.h | 2 +- ext/bigdecimal/extconf.rb | 2 ++ test/bigdecimal/helper.rb | 4 ++-- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ecd1b68..da88a17b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: host: needs: ruby-versions - name: ${{ matrix.os }} ${{ matrix.ruby }} + name: ${{ matrix.os }} ${{ matrix.ruby }} decdig-${{ matrix.decdig_bits }}bit runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -31,7 +31,9 @@ jobs: - macos-14 - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} + decdig_bits: [32] include: + - { os: ubuntu-latest, ruby: "3.4", decdig_bits: 16 } - { os: windows-latest , ruby: mingw } - { os: windows-latest , ruby: mswin } exclude: @@ -40,6 +42,8 @@ jobs: - { os: windows-latest , ruby: debug } - { os: windows-latest , ruby: truffleruby } - { os: windows-latest , ruby: truffleruby-head } + env: + BIGDECIMAL_USE_DECDIG_UINT16_T: ${{ matrix.decdig_bits == 16 }} steps: - uses: actions/checkout@v4 diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 7b7815be..69681eed 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -17,7 +17,7 @@ # include #endif -#ifdef HAVE_INT64_T +#if defined(HAVE_INT64_T) && !defined(BIGDECIMAL_USE_DECDIG_UINT16_T) # define DECDIG uint32_t # define DECDIG_DBL uint64_t # define DECDIG_DBL_SIGNED int64_t diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index cf4290f5..72f9843f 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -59,6 +59,8 @@ def have_builtin_func(name, check_expr, opt = "", &b) bigdecimal_rb = "$(srcdir)/../../lib/bigdecimal.rb" end +$defs.push '-DBIGDECIMAL_USE_DECDIG_UINT16_T' if ENV['BIGDECIMAL_USE_DECDIG_UINT16_T'] == 'true' + create_makefile('bigdecimal') {|mf| mf << "BIGDECIMAL_RB = #{bigdecimal_rb}\n" } diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 9bd09fef..22fc3e7e 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -4,13 +4,13 @@ require 'rbconfig/sizeof' module TestBigDecimalBase - if RbConfig::SIZEOF.key?("int64_t") + if BigDecimal(0)._dump.start_with?('9') SIZEOF_DECDIG = RbConfig::SIZEOF["int32_t"] BASE = 1_000_000_000 BASE_FIG = 9 else SIZEOF_DECDIG = RbConfig::SIZEOF["int16_t"] - BASE = 1000 + BASE = 10000 BASE_FIG = 4 end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 2a96c9c8..70acc99a 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1690,6 +1690,8 @@ def test_power_of_finite_with_zero end def test_power_of_three + pend 'precision of power is buggy' if BASE_FIG == 4 + x = BigDecimal(3) assert_equal(81, x ** 4) assert_equal(1.quo(81), x ** -4) @@ -1786,6 +1788,8 @@ def test_power_of_negative_infinity end def test_power_without_prec + pend 'precision of power is buggy' if BASE_FIG == 4 + pi = BigDecimal("3.14159265358979323846264338327950288419716939937511") e = BigDecimal("2.71828182845904523536028747135266249775724709369996") pow = BigDecimal("0.2245915771836104547342715220454373502758931513399678438732330680117143493477164265678321738086407229773690574073268002736527e2") From 0daef5be34540b8f4a36935322b7bc7bb66fc833 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 14 Jul 2025 22:27:08 +0900 Subject: [PATCH 409/546] Fix wrong multiplying BASE_FIG in precision calculation (#372) Fix prec*(base_fig+1) to (prec+1)*base_fig It's just a frequently occurring mistake in bigdecimal codebase. --- ext/bigdecimal/bigdecimal.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 74968576..3c4dace5 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1516,11 +1516,12 @@ BigDecimal_add(VALUE self, VALUE r) mx = GetAddSubPrec(a.real, b.real); if (mx == (size_t)-1L) { - GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); + /* a or b is inf */ + GUARD_OBJ(c, NewZeroWrapLimited(1, BASE_FIG)); VpAddSub(c.real, a.real, b.real, 1); } else { - GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); + GUARD_OBJ(c, NewZeroWrapLimited(1, (mx + 1) * BASE_FIG)); if (!mx) { VpSetInf(c.real, VpGetSign(a.real)); } @@ -1572,11 +1573,12 @@ BigDecimal_sub(VALUE self, VALUE r) mx = GetAddSubPrec(a.real, b.real); if (mx == (size_t)-1L) { - GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); + /* a or b is inf */ + GUARD_OBJ(c, NewZeroWrapLimited(1, BASE_FIG)); VpAddSub(c.real, a.real, b.real, -1); } else { - GUARD_OBJ(c, NewZeroWrapLimited(1, mx *(VpBaseFig() + 1))); + GUARD_OBJ(c, NewZeroWrapLimited(1, (mx + 1) * BASE_FIG)); if (!mx) { VpSetInf(c.real, VpGetSign(a.real)); } @@ -1822,7 +1824,7 @@ BigDecimal_neg(VALUE self) ENTER(5); BDVALUE c, a; GUARD_OBJ(a, GetBDValueMust(self)); - GUARD_OBJ(c, NewZeroWrapLimited(1, a.real->Prec *(VpBaseFig() + 1))); + GUARD_OBJ(c, NewZeroWrapLimited(1, a.real->Prec * BASE_FIG)); VpAsgn(c.real, a.real, -1); return CheckGetValue(c); } @@ -1860,7 +1862,7 @@ BigDecimal_mult(VALUE self, VALUE r) } mx = a.real->Prec + b.real->Prec; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); + GUARD_OBJ(c, NewZeroWrapLimited(1, (mx + 1) * BASE_FIG)); VpMult(c.real, a.real, b.real); return CheckGetValue(c); } @@ -2358,7 +2360,7 @@ BigDecimal_abs(VALUE self) size_t mx; GUARD_OBJ(a, GetBDValueMust(self)); - mx = a.real->Prec *(VpBaseFig() + 1); + mx = a.real->Prec * BASE_FIG; GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpAsgn(c.real, a.real, 1); VpChangeSign(c.real, 1); @@ -2400,7 +2402,7 @@ BigDecimal_fix(VALUE self) size_t mx; GUARD_OBJ(a, GetBDValueMust(self)); - mx = a.real->Prec *(VpBaseFig() + 1); + mx = (a.real->Prec + 1) * BASE_FIG; GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpActiveRound(c.real, a.real, VP_ROUND_DOWN, 0); /* 0: round off */ return CheckGetValue(c); @@ -2474,7 +2476,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) pl = VpSetPrecLimit(0); GUARD_OBJ(a, GetBDValueMust(self)); - mx = a.real->Prec * (VpBaseFig() + 1); + mx = (a.real->Prec + 1) * BASE_FIG; GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c.real, a.real, sw, iLoc); @@ -2520,7 +2522,7 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self) } GUARD_OBJ(a, GetBDValueMust(self)); - mx = a.real->Prec * (VpBaseFig() + 1); + mx = (a.real->Prec + 1) * BASE_FIG; GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c.real, a.real, VP_ROUND_DOWN, iLoc); /* 0: truncate */ @@ -2540,7 +2542,7 @@ BigDecimal_frac(VALUE self) size_t mx; GUARD_OBJ(a, GetBDValueMust(self)); - mx = a.real->Prec * (VpBaseFig() + 1); + mx = (a.real->Prec + 1) * BASE_FIG; GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpFrac(c.real, a.real); return CheckGetValue(c); @@ -2580,7 +2582,7 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) } GUARD_OBJ(a, GetBDValueMust(self)); - mx = a.real->Prec * (VpBaseFig() + 1); + mx = (a.real->Prec + 1) * BASE_FIG; GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c.real, a.real, VP_ROUND_FLOOR, iLoc); @@ -2626,7 +2628,7 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) } GUARD_OBJ(a, GetBDValueMust(self)); - mx = a.real->Prec * (VpBaseFig() + 1); + mx = (a.real->Prec + 1) * BASE_FIG; GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c.real, a.real, VP_ROUND_CEIL, iLoc); From 0f0b4ed38dad9b7c45df28ec4af439291f89e4c3 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 15 Jul 2025 01:51:49 +0900 Subject: [PATCH 410/546] Remove debug print (#375) VPrint is very useful for debugging. But debug prints remained in source code are useless because: - Too many debug prints for debugging. - Information is missing. While debugging a specific case, I need to add more printf and VPrints. - It's just a noise finding VPrint by grep added in a specific print-debugging. --- ext/bigdecimal/bigdecimal.c | 125 ------------------------------------ 1 file changed, 125 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 3c4dace5..d630380b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2586,9 +2586,6 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); VpSetPrecLimit(pl); VpActiveRound(c.real, a.real, VP_ROUND_FLOOR, iLoc); -#ifdef BIGDECIMAL_DEBUG - VPrint(stderr, "floor: c=%\n", c.real); -#endif if (argc == 0) { return BigDecimal_to_i(CheckGetValue(c)); } @@ -4631,9 +4628,6 @@ Init_bigdecimal(void) */ #ifdef BIGDECIMAL_DEBUG static int gfDebug = 1; /* Debug switch */ -#if 0 -static int gfCheckVal = 1; /* Value checking flag in VpNmlz() */ -#endif #endif /* BIGDECIMAL_DEBUG */ static Real *VpConstOne; /* constant 1.0 */ @@ -5068,17 +5062,6 @@ VpInit(DECDIG BaseVal) gnAlloc = 0; #endif /* BIGDECIMAL_DEBUG */ -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - printf("VpInit: BaseVal = %"PRIuDECDIG"\n", BaseVal); - printf("\tBASE = %"PRIuDECDIG"\n", BASE); - printf("\tHALF_BASE = %"PRIuDECDIG"\n", HALF_BASE); - printf("\tBASE1 = %"PRIuDECDIG"\n", BASE1); - printf("\tBASE_FIG = %u\n", BASE_FIG); - printf("\tBIGDECIMAL_DOUBLE_FIGURES = %d\n", BIGDECIMAL_DOUBLE_FIGURES); - } -#endif /* BIGDECIMAL_DEBUG */ - return BIGDECIMAL_DOUBLE_FIGURES; } @@ -5487,14 +5470,6 @@ VpAddSub(Real *c, Real *a, Real *b, int operation) size_t n, na, nb, i; DECDIG mrv; -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpAddSub(enter) a=% \n", a); - VPrint(stdout, " b=% \n", b); - printf(" operation=%d\n", operation); - } -#endif /* BIGDECIMAL_DEBUG */ - if (!VpIsDefOP(c, a, b, (operation > 0) ? OP_SW_ADD : OP_SW_SUB)) return 0; /* No significant digits */ /* check if a or b is zero */ @@ -5598,14 +5573,6 @@ VpAddSub(Real *c, Real *a, Real *b, int operation) VpInternalRound(c, 0, (c->Prec > 0) ? c->frac[c->Prec-1] : 0, mrv); } -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpAddSub(result) c=% \n", c); - VPrint(stdout, " a=% \n", a); - VPrint(stdout, " b=% \n", b); - printf(" operation=%d\n", operation); - } -#endif /* BIGDECIMAL_DEBUG */ return c->Prec * BASE_FIG; } @@ -5626,13 +5593,6 @@ VpAddAbs(Real *a, Real *b, Real *c) size_t c_pos; DECDIG av, bv, carry, mrv; -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpAddAbs called: a = %\n", a); - VPrint(stdout, " b = %\n", b); - } -#endif /* BIGDECIMAL_DEBUG */ - word_shift = VpSetPTR(a, b, c, &ap, &bp, &cp, &av, &bv); a_pos = ap; b_pos = bp; @@ -5698,11 +5658,6 @@ VpAddAbs(Real *a, Real *b, Real *c) Exit: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpAddAbs exit: c=% \n", c); - } -#endif /* BIGDECIMAL_DEBUG */ return mrv; } @@ -5721,13 +5676,6 @@ VpSubAbs(Real *a, Real *b, Real *c) size_t c_pos; DECDIG av, bv, borrow, mrv; -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpSubAbs called: a = %\n", a); - VPrint(stdout, " b = %\n", b); - } -#endif /* BIGDECIMAL_DEBUG */ - word_shift = VpSetPTR(a, b, c, &ap, &bp, &cp, &av, &bv); a_pos = ap; b_pos = bp; @@ -5803,11 +5751,6 @@ VpSubAbs(Real *a, Real *b, Real *c) mrv = 0; Exit: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpSubAbs exit: c=% \n", c); - } -#endif /* BIGDECIMAL_DEBUG */ return mrv; } @@ -5945,13 +5888,6 @@ VpMult(Real *c, Real *a, Real *b) DECDIG_DBL s; Real *w; -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpMult(Enter): a=% \n", a); - VPrint(stdout, " b=% \n", b); - } -#endif /* BIGDECIMAL_DEBUG */ - if (!VpIsDefOP(c, a, b, OP_SW_MULT)) return 0; /* No significant digit */ if (VpIsZero(a) || VpIsZero(b)) { @@ -6051,13 +5987,6 @@ VpMult(Real *c, Real *a, Real *b) } Exit: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpMult(c=a*b): c=% \n", c); - VPrint(stdout, " a=% \n", a); - VPrint(stdout, " b=% \n", b); - } -#endif /*BIGDECIMAL_DEBUG */ return c->Prec*BASE_FIG; } @@ -6074,13 +6003,6 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) DECDIG borrow, borrow1, borrow2; DECDIG_DBL qb; -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, " VpDivd(c=a/b) a=% \n", a); - VPrint(stdout, " b=% \n", b); - } -#endif /*BIGDECIMAL_DEBUG */ - VpSetNaN(r); if (!VpIsDefOP(c, a, b, OP_SW_DIV)) goto Exit; if (VpIsZero(a) && VpIsZero(b)) { @@ -6253,24 +6175,9 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) goto Exit; space_error: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - printf(" word_a=%"PRIuSIZE"\n", word_a); - printf(" word_b=%"PRIuSIZE"\n", word_b); - printf(" word_c=%"PRIuSIZE"\n", word_c); - printf(" word_r=%"PRIuSIZE"\n", word_r); - printf(" ind_r =%"PRIuSIZE"\n", ind_r); - } -#endif /* BIGDECIMAL_DEBUG */ rb_bug("ERROR(VpDivd): space for remainder too small."); Exit: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, " VpDivd(c=a/b), c=% \n", c); - VPrint(stdout, " r=% \n", r); - } -#endif /* BIGDECIMAL_DEBUG */ return c->Prec * BASE_FIG; } @@ -6393,13 +6300,6 @@ VpComp(Real *a, Real *b) if (val > 1) val = 1; else if (val < -1) val = -1; -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, " VpComp a=%\n", a); - VPrint(stdout, " b=%\n", b); - printf(" ans=%d\n", val); - } -#endif /* BIGDECIMAL_DEBUG */ return (int)val; } @@ -7012,13 +6912,6 @@ VpVtoD(double *d, SIGNED_VALUE *e, Real *m) *d *= VpGetSign(m); Exit: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, " VpVtoD: m=%\n", m); - printf(" d=%e * 10 **%ld\n", *d, *e); - printf(" BIGDECIMAL_DOUBLE_FIGURES = %d\n", BIGDECIMAL_DOUBLE_FIGURES); - } -#endif /*BIGDECIMAL_DEBUG */ return f; } @@ -7081,12 +6974,6 @@ VpDtoV(Real *m, double d) (DECDIG)(val*(double)BASE)); Exit: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - printf("VpDtoV d=%30.30e\n", d); - VPrint(stdout, " m=%\n", m); - } -#endif /* BIGDECIMAL_DEBUG */ return; } @@ -7144,12 +7031,6 @@ VpItoV(Real *m, SIGNED_VALUE ival) VpNmlz(m); Exit: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - printf(" VpItoV i=%d\n", ival); - VPrint(stdout, " m=%\n", m); - } -#endif /* BIGDECIMAL_DEBUG */ return; } #endif @@ -7573,12 +7454,6 @@ VpFrac(Real *y, Real *x) VpNmlz(y); Exit: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpFrac y=%\n", y); - VPrint(stdout, " x=%\n", x); - } -#endif /* BIGDECIMAL_DEBUG */ return; } From 09ee3f461118f22e58bd54e530f41562baf2f27f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 15 Jul 2025 21:04:32 +0900 Subject: [PATCH 411/546] Remove unused #define macros (#376) * Remove unused #define macros * Fix typo in comment * Change BASE and BASE_FIG detection in test helper --- ext/bigdecimal/bigdecimal.c | 83 +++---------------------------------- ext/bigdecimal/bigdecimal.h | 12 ------ test/bigdecimal/helper.rb | 8 ++-- 3 files changed, 9 insertions(+), 94 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d630380b..7c76acc2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -115,8 +115,6 @@ bdvalue_nullable(BDVALUE v) #define HALF_BASE (BASE/2) #define BASE1 (BASE/10) -#define LOG10_2 0.3010299956639812 - #ifndef RRATIONAL_ZERO_P # define RRATIONAL_ZERO_P(x) (FIXNUM_P(rb_rational_num(x)) && \ FIX2LONG(rb_rational_num(x)) == 0) @@ -537,7 +535,7 @@ GetBDValueMust(VALUE v) static inline VALUE BigDecimal_double_fig(VALUE self) { - return INT2FIX(VpDblFig()); + return INT2FIX(BIGDECIMAL_DOUBLE_FIGURES); } /* call-seq: @@ -852,7 +850,7 @@ BigDecimal_dump(int argc, VALUE *argv, VALUE self) GUARD_OBJ(v, GetBDValueMust(self)); dump = rb_str_new(0, VpNumOfChars(v.real, "E")+50); psz = RSTRING_PTR(dump); - snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", VpPrec(v.real)*VpBaseFig()); + snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", v.real->Prec*VpBaseFig()); len = strlen(psz); VpToString(v.real, psz+len, RSTRING_LEN(dump)-len, 0, 0); rb_str_resize(dump, strlen(psz)); @@ -2170,7 +2168,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) VpSetPrecLimit(pl); if (!VpIsZero(res.real)) { // Remainder value affects rounding result. - // ROUND_UP cv = 0.1e0 with ix=10 will be: + // ROUND_UP cv = 0.1e0 with idx=10 will be: // 0.1e0 if remainder == 0 // 0.1000000001e0 if remainder != 0 size_t idx = roomof(ix, BASE_FIG); @@ -4416,7 +4414,7 @@ Init_bigdecimal(void) * guarantee that two groups could always be multiplied together without * overflow.) */ - rb_define_const(rb_cBigDecimal, "BASE", INT2FIX((SIGNED_VALUE)VpBaseVal())); + rb_define_const(rb_cBigDecimal, "BASE", INT2FIX((SIGNED_VALUE)BASE)); /* Exceptions */ @@ -4635,10 +4633,6 @@ static Real *VpConstPt5; /* constant 0.5 */ #define maxnr 100UL /* Maximum iterations for calculating sqrt. */ /* used in VpSqrt() */ -/* ETC */ -#define MemCmp(x,y,z) memcmp(x,y,z) -#define StrCmp(x,y) strcmp(x,y) - enum op_sw { OP_SW_ADD = 1, /* + */ OP_SW_SUB, /* - */ @@ -4865,15 +4859,6 @@ VpGetDoubleNegZero(void) /* Returns the value of -0 */ return nzero; } -#if 0 /* unused */ -VP_EXPORT int -VpIsNegDoubleZero(double v) -{ - double z = VpGetDoubleNegZero(); - return MemCmp(&v,&z,sizeof(v))==0; -} -#endif - VP_EXPORT int VpException(unsigned short f, const char *str,int always) { @@ -6977,64 +6962,6 @@ VpDtoV(Real *m, double d) return; } -/* - * m <- ival - */ -#if 0 /* unused */ -VP_EXPORT void -VpItoV(Real *m, SIGNED_VALUE ival) -{ - size_t mm, ind_m; - size_t val, v1, v2, v; - int isign; - SIGNED_VALUE ne; - - if (ival == 0) { - VpSetZero(m, 1); - goto Exit; - } - isign = 1; - val = ival; - if (ival < 0) { - isign = -1; - val =(size_t)(-ival); - } - ne = 0; - ind_m = 0; - mm = m->MaxPrec; - while (ind_m < mm) { - m->frac[ind_m] = 0; - ++ind_m; - } - ind_m = 0; - while (val > 0) { - if (val) { - v1 = val; - v2 = 1; - while (v1 >= BASE) { - v1 /= BASE; - v2 *= BASE; - } - val = val - v2 * v1; - v = v1; - } - else { - v = 0; - } - m->frac[ind_m] = v; - ++ind_m; - ++ne; - } - m->Prec = ind_m - 1; - m->exponent = ne; - VpSetSign(m, isign); - VpNmlz(m); - -Exit: - return; -} -#endif - /* * y = SQRT(x), y*y - x =>0 */ @@ -7317,7 +7244,7 @@ VpLeftRound(Real *y, unsigned short f, ssize_t nf) DECDIG v; if (!VpHasVal(y)) return 0; /* Unable to round */ v = y->frac[0]; - nf -= VpExponent(y) * (ssize_t)BASE_FIG; + nf -= y->exponent * (ssize_t)BASE_FIG; while ((v /= 10) != 0) nf--; nf += (ssize_t)BASE_FIG-1; return VpMidRound(y, f, nf); diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 69681eed..8e21c1c9 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -196,7 +196,6 @@ typedef struct { #define VpBaseFig() BIGDECIMAL_COMPONENT_FIGURES #define VpDblFig() BIGDECIMAL_DOUBLE_FIGURES -#define VpBaseVal() BIGDECIMAL_BASE /* Zero,Inf,NaN (isinf(),isnan() used to check) */ VP_EXPORT double VpGetDoubleNaN(void); @@ -214,9 +213,6 @@ VP_EXPORT unsigned short VpGetRoundMode(void); VP_EXPORT unsigned short VpSetRoundMode(unsigned short n); VP_EXPORT int VpException(unsigned short f,const char *str,int always); -#if 0 /* unused */ -VP_EXPORT int VpIsNegDoubleZero(double v); -#endif VP_EXPORT size_t VpNumOfChars(Real *vp,const char *pszFmt); VP_EXPORT size_t VpInit(DECDIG BaseVal); VP_EXPORT Real *VpAlloc(size_t mx, const char *szVal, int strict_p, int exc); @@ -233,9 +229,6 @@ VP_EXPORT void VpToFString(Real *a, char *buf, size_t bufsize, size_t fFmt, int VP_EXPORT int VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne); VP_EXPORT int VpVtoD(double *d, SIGNED_VALUE *e, Real *m); VP_EXPORT void VpDtoV(Real *m,double d); -#if 0 /* unused */ -VP_EXPORT void VpItoV(Real *m,S_INT ival); -#endif VP_EXPORT int VpSqrt(Real *y,Real *x); VP_EXPORT int VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t il); VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf); @@ -256,10 +249,6 @@ VP_EXPORT Real *VpOne(void); #define Max(a, b) (((a)>(b))?(a):(b)) #define Min(a, b) (((a)>(b))?(b):(a)) -#define VpMaxPrec(a) ((a)->MaxPrec) -#define VpPrec(a) ((a)->Prec) -#define VpGetFlag(a) ((a)->flag) - /* Sign */ /* VpGetSign(a) returns 1,-1 if a>0,a<0 respectively */ @@ -294,7 +283,6 @@ VP_EXPORT Real *VpOne(void); #define VpSetInf(a,s) (void)(((s)>0)?VpSetPosInf(a):VpSetNegInf(a)) #define VpHasVal(a) (a->frac[0]) #define VpIsOne(a) ((a->Prec==1)&&(a->frac[0]==1)&&(a->exponent==1)) -#define VpExponent(a) (a->exponent) #ifdef BIGDECIMAL_DEBUG int VpVarCheck(Real * v); #endif /* BIGDECIMAL_DEBUG */ diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 22fc3e7e..a23cc5c6 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -4,13 +4,13 @@ require 'rbconfig/sizeof' module TestBigDecimalBase - if BigDecimal(0)._dump.start_with?('9') + BASE = BigDecimal::BASE + case BASE + when 1000000000 SIZEOF_DECDIG = RbConfig::SIZEOF["int32_t"] - BASE = 1_000_000_000 BASE_FIG = 9 - else + when 10000 SIZEOF_DECDIG = RbConfig::SIZEOF["int16_t"] - BASE = 10000 BASE_FIG = 4 end From 4d7bb5b08500cf23ab61f456e8a8c456b0603021 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 16 Jul 2025 00:06:19 +0900 Subject: [PATCH 412/546] VpDivd bugfix (#374) Fix rounding of VpDivd with one: VpDivd can't use VpAsgn which rounds with the current rounding mode. Fix VpDivd loop bug that sometimes skip calculating last DECDIG. --- ext/bigdecimal/bigdecimal.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7c76acc2..93c82068 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -6004,12 +6004,6 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) VpSetZero(r, VpGetSign(a) * VpGetSign(b)); goto Exit; } - if (VpIsOne(b)) { - /* divide by one */ - VpAsgn(c, a, VpGetSign(b)); - VpSetZero(r, VpGetSign(a)); - goto Exit; - } word_a = a->Prec; word_b = b->Prec; @@ -6042,15 +6036,14 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) /* */ /* loop start */ - ind_c = word_r - 1; - nLoop = Min(word_c,ind_c); + nLoop = Min(word_c, word_r); ind_c = 1; while (ind_c < nLoop) { if (r->frac[ind_c] == 0) { ++ind_c; continue; } - r1r2 = (DECDIG_DBL)r->frac[ind_c] * BASE + r->frac[ind_c + 1]; + r1r2 = (DECDIG_DBL)r->frac[ind_c] * BASE + (ind_c + 1 < word_r ? r->frac[ind_c + 1] : 0); if (r1r2 == b1b2) { /* The first two word digits is the same */ ind_b = 2; From 922fa0a362d5b846e07c49da5a650be3f773ee16 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 16 Jul 2025 23:24:29 +0900 Subject: [PATCH 413/546] Fix VpDivd to fully use quotient array (#377) VpDivd was using 1-index in quotient and remainder digits array. c->frac[0] and r->frac[0] was always zero. Fix to use 0-index. Required MaxPrec of remainder reduces. Add Test for VpDivd expected calculation. --- .github/workflows/ci.yml | 1 + ext/bigdecimal/bigdecimal.c | 66 ++++++++++---- ext/bigdecimal/bigdecimal.h | 1 + ext/bigdecimal/extconf.rb | 1 + test/bigdecimal/test_vp_operation.rb | 125 +++++++++++++++++++++++++++ 5 files changed, 175 insertions(+), 19 deletions(-) create mode 100644 test/bigdecimal/test_vp_operation.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da88a17b..1b935355 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,7 @@ jobs: - { os: windows-latest , ruby: truffleruby-head } env: BIGDECIMAL_USE_DECDIG_UINT16_T: ${{ matrix.decdig_bits == 16 }} + BIGDECIMAL_USE_VP_TEST_METHODS: true steps: - uses: actions/checkout@v4 diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 93c82068..bce7f830 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2158,11 +2158,10 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) ix = 2 * BIGDECIMAL_DOUBLE_FIGURES; } - // VpDivd needs 2 extra DECDIGs. One more is needed for rounding. - GUARD_OBJ(cv, NewZeroWrapLimited(1, ix + 3 * VpBaseFig())); + // VpDivd needs 2 extra DECDIGs for rounding. + GUARD_OBJ(cv, NewZeroWrapLimited(1, ix + 2 * VpBaseFig())); - mx = bv.real->Prec + cv.real->MaxPrec - 1; - if (mx <= av.real->Prec) mx = av.real->Prec + 1; + mx = Max(av.real->Prec, bv.real->Prec + cv.real->MaxPrec - 1); GUARD_OBJ(res, NewZeroWrapNolimit(1, mx * VpBaseFig())); VpDivd(cv.real, res.real, av.real, bv.real); VpSetPrecLimit(pl); @@ -4215,6 +4214,35 @@ BigDecimal_literal(const char *str) #define BIGDECIMAL_LITERAL(var, val) (BIGDECIMAL_ ## var = BigDecimal_literal(#val)) +#ifdef BIGDECIMAL_USE_VP_TEST_METHODS +VALUE +BigDecimal_vpdivd(VALUE self, VALUE r, VALUE cprec) { + BDVALUE a,b,c,d; + size_t cn = NUM2INT(cprec); + a = GetBDValueMust(self); + b = GetBDValueMust(r); + size_t dn = Max(a.real->Prec, b.real->Prec + cn - 1); + c = NewZeroWrapLimited(1, cn * BASE_FIG); + d = NewZeroWrapLimited(1, dn * BASE_FIG); + VpDivd(c.real, d.real, a.real, b.real); + VpNmlz(c.real); + VpNmlz(d.real); + return rb_assoc_new(c.bigdecimal, d.bigdecimal); +} + +VALUE +BigDecimal_vpmult(VALUE self, VALUE v) { + BDVALUE a,b,c; + a = GetBDValueMust(self); + b = GetBDValueMust(v); + size_t cn = a.real->Prec + b.real->Prec + 1; + c = NewZeroWrapLimited(1, cn * BASE_FIG); + VpMult(c.real, a.real, b.real); + VpNmlz(c.real); + return c.bigdecimal; +} +#endif /* BIGDECIMAL_USE_VP_TEST_METHODS */ + /* Document-class: BigDecimal * BigDecimal provides arbitrary-precision floating point decimal arithmetic. * @@ -4584,6 +4612,11 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "truncate", BigDecimal_truncate, -1); rb_define_method(rb_cBigDecimal, "_dump", BigDecimal_dump, -1); +#ifdef BIGDECIMAL_USE_VP_TEST_METHODS + rb_define_method(rb_cBigDecimal, "vpdivd", BigDecimal_vpdivd, 2); + rb_define_method(rb_cBigDecimal, "vpmult", BigDecimal_vpmult, 1); +#endif /* BIGDECIMAL_USE_VP_TEST_METHODS */ + rb_mBigMath = rb_define_module("BigMath"); rb_define_singleton_method(rb_mBigMath, "exp", BigMath_s_exp, 2); rb_define_singleton_method(rb_mBigMath, "log", BigMath_s_log, 2); @@ -4645,7 +4678,6 @@ static int AddExponent(Real *a, SIGNED_VALUE n); static DECDIG VpAddAbs(Real *a,Real *b,Real *c); static DECDIG VpSubAbs(Real *a,Real *b,Real *c); static size_t VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, DECDIG *av, DECDIG *bv); -static int VpNmlz(Real *a); static void VpFormatSt(char *psz, size_t fFmt); static int VpRdup(Real *m, size_t ind_m); @@ -5977,6 +6009,10 @@ VpMult(Real *c, Real *a, Real *b) /* * c = a / b, remainder = r + * XXXX_YYYY_ZZZZ / 0001 = XXXX_YYYY_ZZZZ + * XXXX_YYYY_ZZZZ / 1111 = 000X_000Y_000Z + * 00XX_XXYY_YYZZ / 1000 = 0000_0XXX_XYYY + * 0001_0000_0000 / 9999 = 0000_0001_0001 */ VP_EXPORT size_t VpDivd(Real *c, Real *r, Real *a, Real *b) @@ -6010,18 +6046,11 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) word_c = c->MaxPrec; word_r = r->MaxPrec; - if (word_a >= word_r || word_b + word_c - 2 >= word_r) goto space_error; - - ind_r = 1; - r->frac[0] = 0; - while (ind_r <= word_a) { - r->frac[ind_r] = a->frac[ind_r - 1]; - ++ind_r; - } - while (ind_r < word_r) r->frac[ind_r++] = 0; + if (word_a > word_r || word_b + word_c - 2 >= word_r) goto space_error; - ind_c = 0; - while (ind_c < word_c) c->frac[ind_c++] = 0; + for (i = 0; i < word_a; ++i) r->frac[i] = a->frac[i]; + for (i = word_a; i < word_r; ++i) r->frac[i] = 0; + for (i = 0; i < word_c; ++i) c->frac[i] = 0; /* initial procedure */ b1 = b1p1 = b->frac[0]; @@ -6037,7 +6066,7 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) /* */ /* loop start */ nLoop = Min(word_c, word_r); - ind_c = 1; + ind_c = 0; while (ind_c < nLoop) { if (r->frac[ind_c] == 0) { ++ind_c; @@ -6141,13 +6170,12 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) c->Prec = word_c; c->exponent = a->exponent; VpSetSign(c, VpGetSign(a) * VpGetSign(b)); - if (!AddExponent(c, 2)) return 0; + if (!AddExponent(c, 1)) return 0; if (!AddExponent(c, -(b->exponent))) return 0; VpNmlz(c); /* normalize c */ r->Prec = word_r; r->exponent = a->exponent; - if (!AddExponent(r, 1)) return 0; VpSetSign(r, VpGetSign(a)); VpNmlz(r); /* normalize r(remainder) */ goto Exit; diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 8e21c1c9..8a007823 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -220,6 +220,7 @@ VP_EXPORT size_t VpAsgn(Real *c, Real *a, int isw); VP_EXPORT size_t VpAddSub(Real *c,Real *a,Real *b,int operation); VP_EXPORT size_t VpMult(Real *c,Real *a,Real *b); VP_EXPORT size_t VpDivd(Real *c,Real *r,Real *a,Real *b); +VP_EXPORT int VpNmlz(Real *a); VP_EXPORT int VpComp(Real *a,Real *b); VP_EXPORT ssize_t VpExponent10(Real *a); VP_EXPORT void VpSzMantissa(Real *a, char *buf, size_t bufsize); diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 72f9843f..a4825103 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -60,6 +60,7 @@ def have_builtin_func(name, check_expr, opt = "", &b) end $defs.push '-DBIGDECIMAL_USE_DECDIG_UINT16_T' if ENV['BIGDECIMAL_USE_DECDIG_UINT16_T'] == 'true' +$defs.push '-DBIGDECIMAL_USE_VP_TEST_METHODS' if ENV['BIGDECIMAL_USE_VP_TEST_METHODS'] == 'true' create_makefile('bigdecimal') {|mf| mf << "BIGDECIMAL_RB = #{bigdecimal_rb}\n" diff --git a/test/bigdecimal/test_vp_operation.rb b/test/bigdecimal/test_vp_operation.rb new file mode 100644 index 00000000..1ce3a034 --- /dev/null +++ b/test/bigdecimal/test_vp_operation.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: false +require_relative 'helper' +require 'bigdecimal' + +class TestVpOperation < Test::Unit::TestCase + include TestBigDecimalBase + + def setup + super + unless BigDecimal.instance_methods.include?(:vpdivd) + # rake clean && BIGDECIMAL_USE_VP_TEST_METHODS=true rake compile + omit 'Compile with BIGDECIMAL_USE_VP_TEST_METHODS=true to run this test' + end + end + + def test_vpmult + assert_equal(BigDecimal('121932631112635269'), BigDecimal('123456789').vpmult(BigDecimal('987654321'))) + assert_equal(BigDecimal('12193263.1112635269'), BigDecimal('123.456789').vpmult(BigDecimal('98765.4321'))) + x = 123**456 + y = 987**123 + assert_equal(BigDecimal("#{x * y}e-300"), BigDecimal("#{x}e-100").vpmult(BigDecimal("#{y}e-200"))) + end + + def test_vpdivd + # a[0] > b[0] + # XXXX_YYYY_ZZZZ / 1111 #=> 000X_000Y_000Z + x1 = BigDecimal('2' * BASE_FIG + '3' * BASE_FIG + '4' * BASE_FIG + '5' * BASE_FIG + '6' * BASE_FIG) + y = BigDecimal('1' * BASE_FIG) + d1 = BigDecimal("2e#{BASE_FIG * 4}") + d2 = BigDecimal("3e#{BASE_FIG * 3}") + d1 + d3 = BigDecimal("4e#{BASE_FIG * 2}") + d2 + d4 = BigDecimal("5e#{BASE_FIG}") + d3 + d5 = BigDecimal(6) + d4 + assert_equal([d1, x1 - d1 * y], x1.vpdivd(y, 1)) + assert_equal([d2, x1 - d2 * y], x1.vpdivd(y, 2)) + assert_equal([d3, x1 - d3 * y], x1.vpdivd(y, 3)) + assert_equal([d4, x1 - d4 * y], x1.vpdivd(y, 4)) + assert_equal([d5, x1 - d5 * y], x1.vpdivd(y, 5)) + + # a[0] < b[0] + # 00XX_XXYY_YYZZ_ZZ00 / 1111 #=> 0000_0X00_0Y00_0Z00 + shift = BASE_FIG / 2 + x2 = BigDecimal('2' * BASE_FIG + '3' * BASE_FIG + '4' * BASE_FIG + '5' * BASE_FIG + '6' * BASE_FIG + '0' * shift) + d1 = BigDecimal("2e#{4 * BASE_FIG + shift}") + d2 = BigDecimal("3e#{3 * BASE_FIG + shift}") + d1 + d3 = BigDecimal("4e#{2 * BASE_FIG + shift}") + d2 + d4 = BigDecimal("5e#{BASE_FIG + shift}") + d3 + d5 = BigDecimal("6e#{shift}") + d4 + assert_equal([0, x2], x2.vpdivd(y, 1)) + assert_equal([d1, x2 - d1 * y], x2.vpdivd(y, 2)) + assert_equal([d2, x2 - d2 * y], x2.vpdivd(y, 3)) + assert_equal([d3, x2 - d3 * y], x2.vpdivd(y, 4)) + assert_equal([d4, x2 - d4 * y], x2.vpdivd(y, 5)) + assert_equal([d5, x2 - d5 * y], x2.vpdivd(y, 6)) + end + + def test_vpdivd_large_quotient_prec + # 0001 / 0003 = 0000_3333_3333 + assert_equal([BigDecimal('0.' + '3' * BASE_FIG * 9), BigDecimal("1e-#{9 * BASE_FIG}")], BigDecimal(1).vpdivd(BigDecimal(3), 10)) + # 1000 / 0003 = 0333_3333_3333 + assert_equal([BigDecimal('3' * (BASE_FIG - 1) + '.' + '3' * BASE_FIG * 9), BigDecimal("1e-#{9 * BASE_FIG}")], BigDecimal(BASE / 10).vpdivd(BigDecimal(3), 10)) + end + + def test_vpdivd_with_one + x = BigDecimal('1234.2468000001234') + assert_equal([BigDecimal('1234'), BigDecimal('0.2468000001234')], x.vpdivd(BigDecimal(1), 1)) + assert_equal([BigDecimal('+1234.2468'), BigDecimal('+0.1234e-9')], (+x).vpdivd(BigDecimal(+1), 2)) + assert_equal([BigDecimal('-1234.2468'), BigDecimal('+0.1234e-9')], (+x).vpdivd(BigDecimal(-1), 2)) + assert_equal([BigDecimal('-1234.2468'), BigDecimal('-0.1234e-9')], (-x).vpdivd(BigDecimal(+1), 2)) + assert_equal([BigDecimal('+1234.2468'), BigDecimal('-0.1234e-9')], (-x).vpdivd(BigDecimal(-1), 2)) + end + + def test_vpdivd_precisions + xs = [5, 10, 20, 40].map {|n| 123 ** n } + ys = [5, 10, 20, 40].map {|n| 321 ** n } + xs.product(ys).each do |x, y| + [1, 2, 10, 20].each do |n| + xn = (x.digits.size + BASE_FIG - 1) / BASE_FIG + yn = (y.digits.size + BASE_FIG - 1) / BASE_FIG + base = BASE ** (n - xn + yn - 1) + div = BigDecimal((x * base / y).to_i) / base + assert_equal([div, x - y * div], BigDecimal(x).vpdivd(y, n)) + end + end + end + + def test_vpdivd_carry_borrow + y_small = BASE / 7 * BASE ** 4 + y_large = (4 * BASE_FIG).times.map {|i| i % 9 + 1 }.join.to_i + [y_large, y_small].each do |y| + [0, 1, 2, BASE - 2, BASE - 1].repeated_permutation(4) do |a, b, c, d| + x = y * (3 * BASE**4 + a * BASE**3 + b * BASE**2 + c * BASE + d) / BASE + div = BigDecimal(x * BASE / y) / BASE + mod = BigDecimal(x) - div * y + assert_equal([div, mod], BigDecimal(x).vpdivd(BigDecimal(y), 5)) + end + end + end + + def test_vpdivd_large_prec_divisor + x = BigDecimal('2468.000000000000000000000000003') + y1 = BigDecimal('1234.000000000000000000000000001') + y2 = BigDecimal('1234.000000000000000000000000004') + divy1_1 = BigDecimal(2) + divy2_1 = BigDecimal(1) + divy2_2 = BigDecimal('1.' + '9' * BASE_FIG) + assert_equal([divy1_1, x - y1 * divy1_1], x.vpdivd(y1, 1)) + assert_equal([divy2_1, x - y2 * divy2_1], x.vpdivd(y2, 1)) + assert_equal([divy2_2, x - y2 * divy2_2], x.vpdivd(y2, 2)) + end + + def test_vpdivd_intermediate_zero + if BASE_FIG == 9 + x = BigDecimal('123456789.246913578000000000123456789') + y = BigDecimal('123456789') + assert_equal([BigDecimal('1.000000002000000000000000001'), BigDecimal(0)], x.vpdivd(y, 4)) + assert_equal([BigDecimal('1.000000000049999999'), BigDecimal('1e-18')], BigDecimal("2.000000000099999999").vpdivd(2, 3)) + else + x = BigDecimal('1234.246800001234') + y = BigDecimal('1234') + assert_equal([BigDecimal('1.000200000001'), BigDecimal(0)], x.vpdivd(y, 4)) + assert_equal([BigDecimal('1.00000499'), BigDecimal('1e-8')], BigDecimal("2.00000999").vpdivd(2, 3)) + end + end +end From 6af475d01a77199c46253b1cdef562e5d7a0f06d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 17 Jul 2025 01:59:23 +0900 Subject: [PATCH 414/546] Add RB_GC_GUARD to test-only methods (#378) --- ext/bigdecimal/bigdecimal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index bce7f830..a58ec672 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4227,6 +4227,8 @@ BigDecimal_vpdivd(VALUE self, VALUE r, VALUE cprec) { VpDivd(c.real, d.real, a.real, b.real); VpNmlz(c.real); VpNmlz(d.real); + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal); return rb_assoc_new(c.bigdecimal, d.bigdecimal); } @@ -4239,6 +4241,8 @@ BigDecimal_vpmult(VALUE self, VALUE v) { c = NewZeroWrapLimited(1, cn * BASE_FIG); VpMult(c.real, a.real, b.real); VpNmlz(c.real); + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal); return c.bigdecimal; } #endif /* BIGDECIMAL_USE_VP_TEST_METHODS */ From 28903c4d5f4c98cf20aee1a9f03ec093be39d11e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 17 Jul 2025 18:14:07 +0900 Subject: [PATCH 415/546] Use minimum necessary division precision in BigDecimal_DoDivmod (#371) Integer division part of a.divmod(b) only needs (a.exponent-b.exponent+1) digits precision. --- ext/bigdecimal/bigdecimal.c | 95 +++++++++++++----------------- test/bigdecimal/test_bigdecimal.rb | 30 ++++++++++ 2 files changed, 72 insertions(+), 53 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a58ec672..c7546a7d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -174,6 +174,14 @@ check_allocation_count_nonzero(void) # define check_allocation_count_nonzero() /* nothing */ #endif /* BIGDECIMAL_DEBUG */ +/* VpMult VpDivd helpers */ +#define VPMULT_RESULT_PREC(a, b) (a->Prec + b->Prec + 1) +/* To calculate VpDivd with n-digits precision, quotient needs n+2*BASE_FIG-1 digits space */ +/* In the worst precision case 0001_1111_1111 / 9999 = 0000_0001_1112, there are 2*BASE_FIG-1 leading zeros */ +#define VPDIVD_QUO_DIGITS(required_digits) ((required_digits) + 2 * BASE_FIG - 1) +/* Required r.MaxPrec for calculating VpDivd(c, r, a, b) */ +#define VPDIVD_REM_PREC(a, b, c) Max(a->Prec, b->Prec + c->MaxPrec - 1) + static NULLABLE_BDVALUE CreateFromString(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception); @@ -222,16 +230,6 @@ rbd_allocate_struct_decimal_digits(size_t const decimal_digits, bool limit_preci static VALUE BigDecimal_wrap_struct(VALUE obj, Real *vp); -static BDVALUE -rbd_reallocate_struct(BDVALUE value, size_t const internal_digits) -{ - size_t const size = rbd_struct_size(internal_digits); - Real *new_real = (Real *)ruby_xrealloc(value.real, size); - new_real->MaxPrec = internal_digits; - BigDecimal_wrap_struct(value.bigdecimal, new_real); - return (BDVALUE) { value.bigdecimal, new_real }; -} - static void rbd_free_struct(Real *real) { @@ -1843,7 +1841,6 @@ BigDecimal_mult(VALUE self, VALUE r) { ENTER(5); BDVALUE a, b, c; - size_t mx; GUARD_OBJ(a, GetBDValueMust(self)); if (RB_TYPE_P(r, T_FLOAT)) { @@ -1859,8 +1856,7 @@ BigDecimal_mult(VALUE self, VALUE r) b = bdvalue_nonnullable(b2); } - mx = a.real->Prec + b.real->Prec; - GUARD_OBJ(c, NewZeroWrapLimited(1, (mx + 1) * BASE_FIG)); + GUARD_OBJ(c, NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG)); VpMult(c.real, a.real, b.real); return CheckGetValue(c); } @@ -1941,9 +1937,9 @@ static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod, bool truncate) { ENTER(8); - BDVALUE a, b, c, d, e, res; - ssize_t a_prec, b_prec; - size_t mx; + BDVALUE a, b, dv, md, res; + ssize_t a_exponent, b_exponent; + size_t mx, rx; GUARD_OBJ(a, GetBDValueMust(self)); @@ -1997,39 +1993,36 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE return Qtrue; } - BigDecimal_count_precision_and_scale(self, &a_prec, NULL); - BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); + a_exponent = VpExponent10(a.real); + b_exponent = VpExponent10(b.real); + mx = a_exponent > b_exponent ? a_exponent - b_exponent + 1 : 1; + GUARD_OBJ(dv, NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(mx))); - mx = (a_prec > b_prec) ? a_prec : b_prec; - mx *= 2; + /* res is reused for VpDivd remainder and VpMult result */ + rx = VPDIVD_REM_PREC(a.real, b.real, dv.real); + mx = VPMULT_RESULT_PREC(dv.real, b.real); + GUARD_OBJ(res, NewZeroWrapNolimit(1, Max(rx, mx) * BASE_FIG)); + /* AddSub needs one more prec */ + GUARD_OBJ(md, NewZeroWrapLimited(1, (res.real->MaxPrec + 1) * BASE_FIG)); - if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) - mx = 2*BIGDECIMAL_DOUBLE_FIGURES; + VpDivd(dv.real, res.real, a.real, b.real); + VpMidRound(dv.real, VP_ROUND_DOWN, 0); + VpMult(res.real, dv.real, b.real); + VpAddSub(md.real, a.real, res.real, -1); - GUARD_OBJ(c, NewZeroWrapLimited(1, mx + 2*BASE_FIG)); - GUARD_OBJ(res, NewZeroWrapNolimit(1, mx*2 + 2*BASE_FIG)); - VpDivd(c.real, res.real, a.real, b.real); - - mx = c.real->Prec * BASE_FIG; - GUARD_OBJ(d, NewZeroWrapLimited(1, mx)); - VpActiveRound(d.real, c.real, VP_ROUND_DOWN, 0); - - VpMult(res.real, d.real, b.real); - VpAddSub(c.real, a.real, res.real, -1); - - if (!truncate && !VpIsZero(c.real) && (VpGetSign(a.real) * VpGetSign(b.real) < 0)) { + if (!truncate && !VpIsZero(md.real) && (VpGetSign(a.real) * VpGetSign(b.real) < 0)) { /* result adjustment for negative case */ - res = rbd_reallocate_struct(res, d.real->MaxPrec); - res.real->MaxPrec = d.real->MaxPrec; - VpAddSub(res.real, d.real, VpOne(), -1); - GUARD_OBJ(e, NewZeroWrapLimited(1, GetAddSubPrec(c.real, b.real) * 2*BASE_FIG)); - VpAddSub(e.real, c.real, b.real, 1); - *div = bdvalue_nullable(res); - *mod = bdvalue_nullable(e); + BDVALUE dv2, md2; + GUARD_OBJ(dv2, NewZeroWrapLimited(1, (dv.real->MaxPrec + 1) * BASE_FIG)); + GUARD_OBJ(md2, NewZeroWrapLimited(1, (GetAddSubPrec(md.real, b.real) + 1) * BASE_FIG)); + VpAddSub(dv2.real, dv.real, VpOne(), -1); + VpAddSub(md2.real, md.real, b.real, 1); + *div = bdvalue_nullable(dv2); + *mod = bdvalue_nullable(md2); } else { - *div = bdvalue_nullable(d); - *mod = bdvalue_nullable(c); + *div = bdvalue_nullable(dv); + *mod = bdvalue_nullable(md); } return Qtrue; @@ -2121,7 +2114,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) ENTER(5); SIGNED_VALUE ix; BDVALUE av, bv, cv, res; - size_t mx, pl; + size_t pl; if (NIL_P(n)) { /* div in Float sense */ NULLABLE_BDVALUE div; @@ -2158,11 +2151,9 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) ix = 2 * BIGDECIMAL_DOUBLE_FIGURES; } - // VpDivd needs 2 extra DECDIGs for rounding. - GUARD_OBJ(cv, NewZeroWrapLimited(1, ix + 2 * VpBaseFig())); - - mx = Max(av.real->Prec, bv.real->Prec + cv.real->MaxPrec - 1); - GUARD_OBJ(res, NewZeroWrapNolimit(1, mx * VpBaseFig())); + // Needs to calculate 1 extra digit for rounding. + GUARD_OBJ(cv, NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(ix + 1))); + GUARD_OBJ(res, NewZeroWrapNolimit(1, VPDIVD_REM_PREC(av.real, bv.real, cv.real) * BASE_FIG)); VpDivd(cv.real, res.real, av.real, bv.real); VpSetPrecLimit(pl); if (!VpIsZero(res.real)) { @@ -4221,9 +4212,8 @@ BigDecimal_vpdivd(VALUE self, VALUE r, VALUE cprec) { size_t cn = NUM2INT(cprec); a = GetBDValueMust(self); b = GetBDValueMust(r); - size_t dn = Max(a.real->Prec, b.real->Prec + cn - 1); c = NewZeroWrapLimited(1, cn * BASE_FIG); - d = NewZeroWrapLimited(1, dn * BASE_FIG); + d = NewZeroWrapLimited(1, VPDIVD_REM_PREC(a.real, b.real, c.real) * BASE_FIG); VpDivd(c.real, d.real, a.real, b.real); VpNmlz(c.real); VpNmlz(d.real); @@ -4237,8 +4227,7 @@ BigDecimal_vpmult(VALUE self, VALUE v) { BDVALUE a,b,c; a = GetBDValueMust(self); b = GetBDValueMust(v); - size_t cn = a.real->Prec + b.real->Prec + 1; - c = NewZeroWrapLimited(1, cn * BASE_FIG); + c = NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); VpMult(c.real, a.real, b.real); VpNmlz(c.real); RB_GC_GUARD(a.bigdecimal); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 70acc99a..7a3527b9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1107,6 +1107,14 @@ def test_div_various_precisions end end + def test_div_round_worst_precision_case + x = BigDecimal(5) + y = BigDecimal(9 * BASE / 10) + (BASE_FIG * 2..BASE_FIG * 4).each do |prec| + assert_equal(BigDecimal('0.' + '5' * (prec - 1) + "6E-#{BASE_FIG - 1}"), x.div(y, prec)) + end + end + def test_div_rounding_with_small_remainder BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) assert_equal(BigDecimal('0.12e1'), BigDecimal('1.25').div(BigDecimal("1.#{'0' * 30}1"), 2)) @@ -1210,6 +1218,28 @@ def test_divmod_precision q, r = a.divmod(b) assert_equal((a/b).round(0, :down) - 1, q) assert_equal((a - q*b), r) + + a = BigDecimal('3e100') + b = BigDecimal('-1.7e-100') + q, r = a.divmod(b) + assert_include(0...-b, -r) + assert_equal((a - q*b), r) + + a = BigDecimal('0.32e23') + b = BigDecimal('-0.1999999999e-23') + q, r = a.divmod(b) + assert_include(0...-b, -r) + assert_equal((a - q*b), r) + + a = BigDecimal('199.9999999999999999999999999') + q, r = a.divmod(1) + assert_equal([199, a - 199], [q, r]) + + a = BigDecimal('0.30000000000000000000000000000000000000000000000000000000000000001e91') + b = BigDecimal('0.1e20') + q, r = a.divmod(b) + assert_include(0...b, r) + assert_equal((a - q*b), r) end def test_divmod_error From 073edce9f6a575c3a949ab93bed5a413aac3d698 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 17 Jul 2025 23:14:42 +0900 Subject: [PATCH 416/546] Remove dead code and ineffective optimization path form VpDivd (#379) * Remove vpdivd optimization which is not frequetly used It only speeds up about 3% in the best case. About 99.98% case running the test, it won't enter to this optimization path. * Remove unused carry logic in vpdivd --- ext/bigdecimal/bigdecimal.c | 32 +++++----------------------- test/bigdecimal/test_vp_operation.rb | 2 +- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c7546a7d..76b9a62f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -6014,7 +6014,7 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) size_t i, n, ind_a, ind_b, ind_c, ind_r; size_t nLoop; DECDIG_DBL q, b1, b1p1, b1b2, b1b2p1, r1r2; - DECDIG borrow, borrow1, borrow2; + DECDIG borrow1, borrow2; DECDIG_DBL qb; VpSetNaN(r); @@ -6078,26 +6078,11 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) } /* The first few word digits of r and b is the same and */ /* the first different word digit of w is greater than that */ - /* of b, so quotient is 1 and just subtract b from r. */ - borrow = 0; /* quotient=1, then just r-b */ - ind_b = b->Prec - 1; - ind_r = ind_c + ind_b; - if (ind_r >= word_r) goto space_error; - n = ind_b; - for (i = 0; i <= n; ++i) { - if (r->frac[ind_r] < b->frac[ind_b] + borrow) { - r->frac[ind_r] += (BASE - (b->frac[ind_b] + borrow)); - borrow = 1; - } - else { - r->frac[ind_r] = r->frac[ind_r] - b->frac[ind_b] - borrow; - borrow = 0; - } - --ind_r; - --ind_b; - } + /* of b, so quotient is 1. */ + q = 1; ++c->frac[ind_c]; - goto carry; + ind_r = b->Prec + ind_c - 1; + goto sub_mult; } /* The first two word digits is not the same, */ /* then compare magnitude, and divide actually. */ @@ -6150,13 +6135,6 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) } r->frac[ind_r] -= borrow2; -carry: - ind_r = ind_c; - while (c->frac[ind_r] >= BASE) { - c->frac[ind_r] -= BASE; - --ind_r; - ++c->frac[ind_r]; - } } /* End of operation, now final arrangement */ out_side: diff --git a/test/bigdecimal/test_vp_operation.rb b/test/bigdecimal/test_vp_operation.rb index 1ce3a034..075df0b6 100644 --- a/test/bigdecimal/test_vp_operation.rb +++ b/test/bigdecimal/test_vp_operation.rb @@ -84,7 +84,7 @@ def test_vpdivd_precisions end end - def test_vpdivd_carry_borrow + def test_vpdivd_borrow y_small = BASE / 7 * BASE ** 4 y_large = (4 * BASE_FIG).times.map {|i| i % 9 + 1 }.join.to_i [y_large, y_small].each do |y| From 0d854c49b32e5c39cc6026589c0d0a3c441b8147 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 19 Jul 2025 14:48:59 +0900 Subject: [PATCH 417/546] Implement exp, log, power and ** in ruby (#347) * Implement exp, log, power and ** in ruby * Name 0.3010299956639812 as lg2 Co-authored-by: Kenta Murata <3959+mrkn@users.noreply.github.com> --------- Co-authored-by: Kenta Murata <3959+mrkn@users.noreply.github.com> --- ext/bigdecimal/bigdecimal.c | 769 ----------------------------- ext/bigdecimal/bigdecimal.h | 2 - lib/bigdecimal.rb | 261 ++++++++++ test/bigdecimal/helper.rb | 2 +- test/bigdecimal/test_bigdecimal.rb | 72 ++- test/bigdecimal/test_bigmath.rb | 15 + 6 files changed, 336 insertions(+), 785 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 76b9a62f..33b29475 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -48,7 +48,6 @@ #define EXPONENT_MIN (VP_EXPONENT_MIN * BASE_FIG - (BASE_FIG - 1)) VALUE rb_cBigDecimal; -VALUE rb_mBigMath; static ID id_BigDecimal_exception_mode; static ID id_BigDecimal_rounding_mode; @@ -2817,35 +2816,6 @@ BigDecimal_inspect(VALUE self) return str; } -static VALUE BigMath_s_exp(VALUE, VALUE, VALUE); -static VALUE BigMath_s_log(VALUE, VALUE, VALUE); - -#define BigMath_exp(x, n) BigMath_s_exp(rb_mBigMath, (x), (n)) -#define BigMath_log(x, n) BigMath_s_log(rb_mBigMath, (x), (n)) - -inline static int -is_integer(VALUE x) -{ - return (RB_TYPE_P(x, T_FIXNUM) || RB_TYPE_P(x, T_BIGNUM)); -} - -inline static int -is_negative(VALUE x) -{ - if (FIXNUM_P(x)) { - return FIX2LONG(x) < 0; - } - else if (RB_TYPE_P(x, T_BIGNUM)) { - return FIX2INT(rb_big_cmp(x, INT2FIX(0))) < 0; - } - else if (RB_TYPE_P(x, T_FLOAT)) { - return RFLOAT_VALUE(x) < 0.0; - } - return RTEST(rb_funcall(x, '<', 1, INT2FIX(0))); -} - -#define is_positive(x) (!is_negative(x)) - inline static int is_zero(VALUE x) { @@ -2869,341 +2839,6 @@ is_zero(VALUE x) return RTEST(rb_funcall(x, id_eq, 1, INT2FIX(0))); } -inline static int -is_one(VALUE x) -{ - VALUE num, den; - - switch (TYPE(x)) { - case T_FIXNUM: - return FIX2LONG(x) == 1; - - case T_BIGNUM: - return Qfalse; - - case T_RATIONAL: - num = rb_rational_num(x); - den = rb_rational_den(x); - return FIXNUM_P(den) && FIX2LONG(den) == 1 && - FIXNUM_P(num) && FIX2LONG(num) == 1; - - default: - break; - } - - return RTEST(rb_funcall(x, id_eq, 1, INT2FIX(1))); -} - -inline static int -is_even(VALUE x) -{ - switch (TYPE(x)) { - case T_FIXNUM: - return (FIX2LONG(x) % 2) == 0; - - case T_BIGNUM: - { - unsigned long l; - rb_big_pack(x, &l, 1); - return l % 2 == 0; - } - - default: - break; - } - - return 0; -} - -static VALUE -bigdecimal_power_by_bigdecimal(BDVALUE x, BDVALUE exp, ssize_t const n) -{ - VALUE log_x, multiplied, y; - - if (VpIsZero(exp.real)) { - return CheckGetValue(NewOneWrapLimited(1, n)); - } - - log_x = BigMath_log(x.bigdecimal, SSIZET2NUM(n+1)); - multiplied = BigDecimal_mult2(exp.bigdecimal, log_x, SSIZET2NUM(n+1)); - y = BigMath_exp(multiplied, SSIZET2NUM(n)); - RB_GC_GUARD(exp.bigdecimal); - - return y; -} - -/* call-seq: - * power(n) - * power(n, prec) - * - * Returns the value raised to the power of n. - * - * Note that n must be an Integer. - * - * Also available as the operator **. - */ -static VALUE -BigDecimal_power(int argc, VALUE*argv, VALUE self) -{ - ENTER(5); - VALUE vexp, prec; - NULLABLE_BDVALUE exp = { Qnil, NULL }; - BDVALUE x, y; - ssize_t mp, ma, n; - SIGNED_VALUE int_exp; - double d; - - rb_scan_args(argc, argv, "11", &vexp, &prec); - - GUARD_OBJ(x, GetBDValueMust(self)); - n = NIL_P(prec) ? (ssize_t)(x.real->Prec*VpBaseFig()) : NUM2SSIZET(prec); - - if (VpIsNaN(x.real)) { - y = NewZeroWrapLimited(1, n); - VpSetNaN(y.real); - return CheckGetValue(y); - } - - retry: - switch (TYPE(vexp)) { - case T_FIXNUM: - break; - - case T_BIGNUM: - break; - - case T_FLOAT: - d = RFLOAT_VALUE(vexp); - if (d == round(d)) { - if (FIXABLE(d)) { - vexp = LONG2FIX((long)d); - } - else { - vexp = rb_dbl2big(d); - } - goto retry; - } - if (NIL_P(prec)) { - n += BIGDECIMAL_DOUBLE_FIGURES; - } - exp = bdvalue_nullable(GetBDValueWithPrecMust(vexp, 0)); - break; - - case T_RATIONAL: - if (is_zero(rb_rational_num(vexp))) { - if (is_positive(vexp)) { - vexp = INT2FIX(0); - goto retry; - } - } - else if (is_one(rb_rational_den(vexp))) { - vexp = rb_rational_num(vexp); - goto retry; - } - exp = bdvalue_nullable(GetBDValueWithPrecMust(vexp, n)); - if (NIL_P(prec)) { - n += n; - } - break; - - case T_DATA: - if (is_kind_of_BigDecimal(vexp)) { - VALUE zero = INT2FIX(0); - VALUE rounded = BigDecimal_round(1, &zero, vexp); - if (RTEST(BigDecimal_eq(vexp, rounded))) { - vexp = BigDecimal_to_i(vexp); - goto retry; - } - if (NIL_P(prec)) { - GUARD_OBJ(y, GetBDValueMust(vexp)); - n += y.real->Prec*VpBaseFig(); - } - GUARD_OBJ_OR_NIL(exp, bdvalue_nullable(GetBDValueMust(vexp))); - break; - } - /* fall through */ - default: - rb_raise(rb_eTypeError, - "wrong argument type %"PRIsVALUE" (expected scalar Numeric)", - RB_OBJ_CLASSNAME(vexp)); - } - - if (VpIsZero(x.real)) { - if (is_negative(vexp)) { - y = NewZeroWrapNolimit(1, n); - if (BIGDECIMAL_NEGATIVE_P(x.real)) { - if (is_integer(vexp)) { - if (is_even(vexp)) { - /* (-0) ** (-even_integer) -> Infinity */ - VpSetPosInf(y.real); - } - else { - /* (-0) ** (-odd_integer) -> -Infinity */ - VpSetNegInf(y.real); - } - } - else { - /* (-0) ** (-non_integer) -> Infinity */ - VpSetPosInf(y.real); - } - } - else { - /* (+0) ** (-num) -> Infinity */ - VpSetPosInf(y.real); - } - return CheckGetValue(y); - } - else if (is_zero(vexp)) { - return CheckGetValue(NewOneWrapLimited(1, n)); - } - else { - return CheckGetValue(NewZeroWrapLimited(1, n)); - } - } - - if (is_zero(vexp)) { - return CheckGetValue(NewOneWrapLimited(1, n)); - } - else if (is_one(vexp)) { - return self; - } - - if (VpIsInf(x.real)) { - if (is_negative(vexp)) { - if (BIGDECIMAL_NEGATIVE_P(x.real)) { - if (is_integer(vexp)) { - if (is_even(vexp)) { - /* (-Infinity) ** (-even_integer) -> +0 */ - return CheckGetValue(NewZeroWrapLimited(1, n)); - } - else { - /* (-Infinity) ** (-odd_integer) -> -0 */ - return CheckGetValue(NewZeroWrapLimited(-1, n)); - } - } - else { - /* (-Infinity) ** (-non_integer) -> -0 */ - return CheckGetValue(NewZeroWrapLimited(-1, n)); - } - } - else { - return CheckGetValue(NewZeroWrapLimited(1, n)); - } - } - else { - y = NewZeroWrapLimited(1, n); - if (BIGDECIMAL_NEGATIVE_P(x.real)) { - if (is_integer(vexp)) { - if (is_even(vexp)) { - VpSetPosInf(y.real); - } - else { - VpSetNegInf(y.real); - } - } - else { - /* TODO: support complex */ - rb_raise(rb_eMathDomainError, - "a non-integral exponent for a negative base"); - } - } - else { - VpSetPosInf(y.real); - } - return CheckGetValue(y); - } - } - - if (exp.real_or_null != NULL) { - return bigdecimal_power_by_bigdecimal(x, bdvalue_nonnullable(exp), n); - } - else if (RB_TYPE_P(vexp, T_BIGNUM)) { - VALUE abs_value = BigDecimal_abs(self); - if (is_one(abs_value)) { - return CheckGetValue(NewOneWrapLimited(is_even(vexp) ? 1 : VpGetSign(x.real), n)); - } - else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { - if (is_negative(vexp)) { - y = NewZeroWrapLimited(1, n); - VpSetInf(y.real, is_even(vexp) ? 1 : VpGetSign(x.real)); - return CheckGetValue(y); - } - else if (BIGDECIMAL_NEGATIVE_P(x.real) && !is_even(vexp)) { - return CheckGetValue(NewZeroWrapLimited(-1, n)); - } - else { - return CheckGetValue(NewZeroWrapLimited(1, n)); - } - } - else { - if (is_positive(vexp)) { - y = NewZeroWrapLimited(1, n); - VpSetInf(y.real, is_even(vexp) ? 1 : VpGetSign(x.real)); - return CheckGetValue(y); - } - else if (BIGDECIMAL_NEGATIVE_P(x.real) && !is_even(vexp)) { - return CheckGetValue(NewZeroWrapLimited(-1, n)); - } - else { - return CheckGetValue(NewZeroWrapLimited(1, n)); - } - } - } - - int_exp = FIX2LONG(vexp); - ma = int_exp; - if (ma < 0) ma = -ma; - if (ma == 0) ma = 1; - - if (VpIsDef(x.real)) { - mp = x.real->Prec * (VpBaseFig() + 1); - GUARD_OBJ(y, NewZeroWrapLimited(1, mp * (ma + 1))); - } - else { - GUARD_OBJ(y, NewZeroWrapLimited(1, 1)); - } - VpPowerByInt(y.real, x.real, int_exp); - if (!NIL_P(prec) && VpIsDef(y.real)) { - VpMidRound(y.real, VpGetRoundMode(), n); - } - return CheckGetValue(y); -} - -/* call-seq: - * self ** other -> bigdecimal - * - * Returns the \BigDecimal value of +self+ raised to power +other+: - * - * b = BigDecimal('3.14') - * b ** 2 # => 0.98596e1 - * b ** 2.0 # => 0.98596e1 - * b ** Rational(2, 1) # => 0.98596e1 - * - * Related: BigDecimal#power. - * - */ -static VALUE -BigDecimal_power_op(VALUE self, VALUE exp) -{ - return BigDecimal_power(1, &exp, self); -} - -/* :nodoc: - * - * private method for dup and clone the provided BigDecimal +other+ - */ -static VALUE -BigDecimal_initialize_copy(VALUE self, VALUE other) -{ - Real *pv = rb_check_typeddata(self, &BigDecimal_data_type); - Real *x = rb_check_typeddata(other, &BigDecimal_data_type); - - if (self != other) { - BigDecimal_wrap_struct(self, VpCopy(pv, x)); - } - return self; -} - /* :nodoc: */ static VALUE BigDecimal_clone(VALUE self) @@ -3858,302 +3493,6 @@ BigDecimal_save_limit(VALUE self) return ret; } -/* call-seq: - * BigMath.exp(decimal, numeric) -> BigDecimal - * - * Computes the value of e (the base of natural logarithms) raised to the - * power of +decimal+, to the specified number of digits of precision. - * - * If +decimal+ is infinity, returns Infinity. - * - * If +decimal+ is NaN, returns NaN. - */ -static VALUE -BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) -{ - ssize_t prec, n, i; - BDVALUE vx; - NULLABLE_BDVALUE nullable_vx = { Qnil, NULL }; - VALUE one, d, y; - int negative = 0; - int infinite = 0; - int nan = 0; - double flo; - - prec = NUM2SSIZET(vprec); - if (prec <= 0) { - rb_raise(rb_eArgError, "Zero or negative precision for exp"); - } - - /* TODO: the following switch statement is almost same as one in the - * BigDecimalCmp function. */ - switch (TYPE(x)) { - case T_DATA: - if (!is_kind_of_BigDecimal(x)) break; - vx = GetBDValueMust(x); - nullable_vx = bdvalue_nullable(vx); - negative = BIGDECIMAL_NEGATIVE_P(vx.real); - infinite = VpIsPosInf(vx.real) || VpIsNegInf(vx.real); - nan = VpIsNaN(vx.real); - break; - - case T_FIXNUM: - /* fall through */ - case T_BIGNUM: - nullable_vx = GetBDValue(x); - break; - - case T_FLOAT: - flo = RFLOAT_VALUE(x); - negative = flo < 0; - infinite = isinf(flo); - nan = isnan(flo); - if (!infinite && !nan) { - nullable_vx = GetBDValueWithPrec(x, 0); - } - break; - - case T_RATIONAL: - nullable_vx = GetBDValueWithPrec(x, prec); - break; - - default: - break; - } - if (infinite) { - if (negative) { - return CheckGetValue(GetBDValueWithPrecMust(INT2FIX(0), prec)); - } - else { - BDVALUE y = NewZeroWrapNolimit(1, prec); - VpSetInf(y.real, VP_SIGN_POSITIVE_INFINITE); - return CheckGetValue(y); - } - } - else if (nan) { - BDVALUE y = NewZeroWrapNolimit(1, prec); - VpSetNaN(y.real); - return CheckGetValue(y); - } - else if (nullable_vx.real_or_null == NULL) { - cannot_be_coerced_into_BigDecimal(rb_eArgError, x); - } - vx = bdvalue_nonnullable(nullable_vx); - x = vx.bigdecimal; - - n = prec + BIGDECIMAL_DOUBLE_FIGURES; - negative = BIGDECIMAL_NEGATIVE_P(vx.real); - if (negative) { - VALUE x_zero = INT2NUM(1); - VALUE x_copy = f_BigDecimal(1, &x_zero, klass); - x = BigDecimal_initialize_copy(x_copy, x); - VpSetSign((Real*)DATA_PTR(x), 1); - } - - one = CheckGetValue(NewOneWrapLimited(1, 1)); - y = one; - d = y; - i = 1; - - while (!VpIsZero((Real*)DATA_PTR(d))) { - SIGNED_VALUE const ey = VpExponent10(DATA_PTR(y)); - SIGNED_VALUE const ed = VpExponent10(DATA_PTR(d)); - ssize_t m = n - vabs(ey - ed); - - rb_thread_check_ints(); - - if (m <= 0) { - break; - } - else if ((size_t)m < BIGDECIMAL_DOUBLE_FIGURES) { - m = BIGDECIMAL_DOUBLE_FIGURES; - } - - d = BigDecimal_mult(d, x); /* d <- d * x */ - d = BigDecimal_div2(d, SSIZET2NUM(i), SSIZET2NUM(m)); /* d <- d / i */ - y = BigDecimal_add(y, d); /* y <- y + d */ - ++i; /* i <- i + 1 */ - } - - if (negative) { - return BigDecimal_div2(one, y, vprec); - } - else { - vprec = SSIZET2NUM(prec - VpExponent10(DATA_PTR(y))); - return BigDecimal_round(1, &vprec, y); - } - - RB_GC_GUARD(one); - RB_GC_GUARD(x); - RB_GC_GUARD(y); - RB_GC_GUARD(d); -} - -/* call-seq: - * BigMath.log(decimal, numeric) -> BigDecimal - * - * Computes the natural logarithm of +decimal+ to the specified number of - * digits of precision, +numeric+. - * - * If +decimal+ is zero or negative, raises Math::DomainError. - * - * If +decimal+ is positive infinity, returns Infinity. - * - * If +decimal+ is NaN, returns NaN. - */ -static VALUE -BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) -{ - ssize_t prec, n, i; - SIGNED_VALUE expo; - BDVALUE vx; - NULLABLE_BDVALUE nullable_vx = { Qnil, NULL }; - VALUE vn, one, two, w, x2, y, d; - int zero = 0; - int negative = 0; - int infinite = 0; - int nan = 0; - double flo; - long fix; - - if (!is_integer(vprec)) { - rb_raise(rb_eArgError, "precision must be an Integer"); - } - - prec = NUM2SSIZET(vprec); - if (prec <= 0) { - rb_raise(rb_eArgError, "Zero or negative precision for exp"); - } - - /* TODO: the following switch statement is almost same as one in the - * BigDecimalCmp function. */ - switch (TYPE(x)) { - case T_DATA: - if (!is_kind_of_BigDecimal(x)) break; - vx = GetBDValueMust(x); - nullable_vx = bdvalue_nullable(vx); - zero = VpIsZero(vx.real); - negative = BIGDECIMAL_NEGATIVE_P(vx.real); - infinite = VpIsPosInf(vx.real) || VpIsNegInf(vx.real); - nan = VpIsNaN(vx.real); - break; - - case T_FIXNUM: - fix = FIX2LONG(x); - zero = fix == 0; - negative = fix < 0; - goto get_vp_value; - - case T_BIGNUM: - i = FIX2INT(rb_big_cmp(x, INT2FIX(0))); - zero = i == 0; - negative = i < 0; -get_vp_value: - if (zero || negative) break; - nullable_vx = GetBDValue(x); - break; - - case T_FLOAT: - flo = RFLOAT_VALUE(x); - zero = flo == 0; - negative = flo < 0; - infinite = isinf(flo); - nan = isnan(flo); - if (!zero && !negative && !infinite && !nan) { - nullable_vx = bdvalue_nullable(GetBDValueWithPrecMust(x, 0)); - } - break; - - case T_RATIONAL: - zero = RRATIONAL_ZERO_P(x); - negative = RRATIONAL_NEGATIVE_P(x); - if (zero || negative) break; - nullable_vx = bdvalue_nullable(GetBDValueWithPrecMust(x, prec)); - break; - - case T_COMPLEX: - rb_raise(rb_eMathDomainError, - "Complex argument for BigMath.log"); - - default: - break; - } - if (infinite && !negative) { - BDVALUE y = NewZeroWrapNolimit(1, prec); - VpSetInf(y.real, VP_SIGN_POSITIVE_INFINITE); - return CheckGetValue(y); - } - else if (nan) { - BDVALUE y = NewZeroWrapNolimit(1, prec); - VpSetNaN(y.real); - return CheckGetValue(y); - } - else if (zero || negative) { - rb_raise(rb_eMathDomainError, - "Zero or negative argument for log"); - } - else if (nullable_vx.real_or_null == NULL) { - cannot_be_coerced_into_BigDecimal(rb_eArgError, x); - } - vx = bdvalue_nonnullable(nullable_vx); - x = CheckGetValue(vx); - - one = CheckGetValue(NewOneWrapLimited(1, 1)); - two = CheckGetValue(bdvalue_nonnullable(CreateFromString(1, "2", rb_cBigDecimal, true, true))); - - n = prec + BIGDECIMAL_DOUBLE_FIGURES; - vn = SSIZET2NUM(n); - expo = VpExponent10(vx.real); - if (expo < 0 || expo >= 3) { - char buf[DECIMAL_SIZE_OF_BITS(SIZEOF_VALUE * CHAR_BIT) + 4]; - snprintf(buf, sizeof(buf), "1E%"PRIdVALUE, -expo); - x = BigDecimal_mult2(x, CheckGetValue(bdvalue_nonnullable(CreateFromString(1, buf, rb_cBigDecimal, true, true))), vn); - } - else { - expo = 0; - } - w = BigDecimal_sub(x, one); - x = BigDecimal_div2(w, BigDecimal_add(x, one), vn); - x2 = BigDecimal_mult2(x, x, vn); - y = x; - d = y; - i = 1; - while (!VpIsZero((Real*)DATA_PTR(d))) { - SIGNED_VALUE const ey = VpExponent10(DATA_PTR(y)); - SIGNED_VALUE const ed = VpExponent10(DATA_PTR(d)); - ssize_t m = n - vabs(ey - ed); - if (m <= 0) { - break; - } - else if ((size_t)m < BIGDECIMAL_DOUBLE_FIGURES) { - m = BIGDECIMAL_DOUBLE_FIGURES; - } - - x = BigDecimal_mult2(x2, x, vn); - i += 2; - d = BigDecimal_div2(x, SSIZET2NUM(i), SSIZET2NUM(m)); - y = BigDecimal_add(y, d); - } - - y = BigDecimal_mult(y, two); - if (expo != 0) { - VALUE log10, vexpo, dy; - log10 = BigMath_s_log(klass, INT2FIX(10), vprec); - vexpo = CheckGetValue(GetBDValueMust(SSIZET2NUM(expo))); - dy = BigDecimal_mult(log10, vexpo); - y = BigDecimal_add(y, dy); - } - - RB_GC_GUARD(one); - RB_GC_GUARD(two); - RB_GC_GUARD(vn); - RB_GC_GUARD(x2); - RB_GC_GUARD(y); - RB_GC_GUARD(d); - - return y; -} - static VALUE BIGDECIMAL_NAN = Qnil; static VALUE @@ -4583,8 +3922,6 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "frac", BigDecimal_frac, 0); rb_define_method(rb_cBigDecimal, "floor", BigDecimal_floor, -1); rb_define_method(rb_cBigDecimal, "ceil", BigDecimal_ceil, -1); - rb_define_method(rb_cBigDecimal, "power", BigDecimal_power, -1); - rb_define_method(rb_cBigDecimal, "**", BigDecimal_power_op, 1); rb_define_method(rb_cBigDecimal, "<=>", BigDecimal_comp, 1); rb_define_method(rb_cBigDecimal, "==", BigDecimal_eq, 1); rb_define_method(rb_cBigDecimal, "===", BigDecimal_eq, 1); @@ -4610,10 +3947,6 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "vpmult", BigDecimal_vpmult, 1); #endif /* BIGDECIMAL_USE_VP_TEST_METHODS */ - rb_mBigMath = rb_define_module("BigMath"); - rb_define_singleton_method(rb_mBigMath, "exp", BigMath_s_exp, 2); - rb_define_singleton_method(rb_mBigMath, "log", BigMath_s_log, 2); - #define ROUNDING_MODE(i, name, value) \ id_##name = rb_intern_const(#name); \ rbd_rounding_modes[i].id = id_##name; \ @@ -7376,108 +6709,6 @@ VpFrac(Real *y, Real *x) return; } -/* - * y = x ** n - */ -VP_EXPORT int -VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n) -{ - size_t s, ss; - ssize_t sign; - Real *w1 = NULL; - Real *w2 = NULL; - - if (VpIsZero(x)) { - if (n == 0) { - VpSetOne(y); - goto Exit; - } - sign = VpGetSign(x); - if (n < 0) { - n = -n; - if (sign < 0) sign = (n % 2) ? -1 : 1; - VpSetInf(y, sign); - } - else { - if (sign < 0) sign = (n % 2) ? -1 : 1; - VpSetZero(y,sign); - } - goto Exit; - } - if (VpIsNaN(x)) { - VpSetNaN(y); - goto Exit; - } - if (VpIsInf(x)) { - if (n == 0) { - VpSetOne(y); - goto Exit; - } - if (n > 0) { - VpSetInf(y, (n % 2 == 0 || VpIsPosInf(x)) ? 1 : -1); - goto Exit; - } - VpSetZero(y, (n % 2 == 0 || VpIsPosInf(x)) ? 1 : -1); - goto Exit; - } - - if (x->exponent == 1 && x->Prec == 1 && x->frac[0] == 1) { - /* abs(x) = 1 */ - VpSetOne(y); - if (BIGDECIMAL_POSITIVE_P(x)) goto Exit; - if ((n % 2) == 0) goto Exit; - VpSetSign(y, -1); - goto Exit; - } - - if (n > 0) sign = 1; - else if (n < 0) { - sign = -1; - n = -n; - } - else { - VpSetOne(y); - goto Exit; - } - - /* Allocate working variables */ - /* TODO: reconsider MaxPrec of w1 and w2 */ - w1 = NewZeroNolimit(1, (y->MaxPrec + 2) * BASE_FIG); - w2 = NewZeroNolimit(1, (w1->MaxPrec * 2 + 1) * BASE_FIG); - - /* calculation start */ - - VpAsgn(y, x, 1); - --n; - while (n > 0) { - VpAsgn(w1, x, 1); - s = 1; - while (ss = s, (s += s) <= (size_t)n) { - VpMult(w2, w1, w1); - VpAsgn(w1, w2, 1); - } - n -= (SIGNED_VALUE)ss; - VpMult(w2, y, w1); - VpAsgn(y, w2, 1); - } - if (sign < 0) { - VpDivd(w1, w2, VpConstOne, y); - VpAsgn(y, w1, 1); - } - -Exit: -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VPrint(stdout, "VpPowerByInt y=%\n", y); - VPrint(stdout, "VpPowerByInt x=%\n", x); - printf(" n=%"PRIdVALUE"\n", n); - } -#endif /* BIGDECIMAL_DEBUG */ - rbd_free_struct(w2); - rbd_free_struct(w1); - return 1; -} - #ifdef BIGDECIMAL_DEBUG int VpVarCheck(Real * v) diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 8a007823..0f52d017 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -235,8 +235,6 @@ VP_EXPORT int VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t il); VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf); VP_EXPORT int VpLeftRound(Real *y, unsigned short f, ssize_t nf); VP_EXPORT void VpFrac(Real *y, Real *x); -VP_EXPORT int VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n); -#define VpPower VpPowerByInt /* VP constants */ VP_EXPORT Real *VpOne(void); diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 82b3e1b7..55912e8c 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -3,3 +3,264 @@ else require 'bigdecimal.so' end + +class BigDecimal + + # call-seq: + # self ** other -> bigdecimal + # + # Returns the \BigDecimal value of +self+ raised to power +other+: + # + # b = BigDecimal('3.14') + # b ** 2 # => 0.98596e1 + # b ** 2.0 # => 0.98596e1 + # b ** Rational(2, 1) # => 0.98596e1 + # + # Related: BigDecimal#power. + # + def **(y) + unless y.is_a?(BigDecimal) + case y + when Integer, Float, Rational + y = BigDecimal(y, 0) + when nil + raise TypeError, 'wrong argument type NilClass' + else + x, y = y.coerce(self) + return x**y + end + end + power(y) + end + + # call-seq: + # power(n) + # power(n, prec) + # + # Returns the value raised to the power of n. + # + # Also available as the operator **. + # + def power(y, prec = nil) + BigMath._validate_prec(prec, :power) if prec + x = self + y = BigMath._coerce_to_bigdecimal(y, :power) + + return BigDecimal::NAN if x.nan? || y.nan? + + if x.zero? + return BigDecimal(1) if y.zero? + return BigDecimal(0) if y > 0 + if y.frac.zero? && y % 2 == 1 && x.sign == -1 + return -BigDecimal::INFINITY + else + return BigDecimal::INFINITY + end + elsif x < 0 + if y.frac.zero? + if y % 2 == 0 + return (-x).power(y, prec) + else + return -(-x).power(y, prec) + end + else + raise Math::DomainError, 'Computation results in complex number' + end + elsif x == 1 + return BigDecimal(1) + end + + if y.infinite? + if x < 1 + return y.positive? ? BigDecimal(0) : BigDecimal::INFINITY + else + return y.positive? ? BigDecimal::INFINITY : BigDecimal(0) + end + end + + prec ||= BigDecimal.limit.nonzero? + frac_part = y.frac + + if frac_part.zero? && !prec + # Infinite precision calculation for `x ** int` and `x.power(int)` + int_part = y.fix.to_i + int_part = -int_part if (neg = int_part < 0) + ans = BigDecimal(1) + n = 1 + xn = x + while true + ans *= xn if int_part.allbits?(n) + n <<= 1 + break if n > int_part + xn *= xn + # Detect overflow/underflow before consuming infinite memory + if (xn.exponent.abs - 1) * int_part / n >= 0x7FFFFFFFFFFFFFFF + return ((xn.exponent > 0) ^ neg ? BigDecimal::INFINITY : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1) + end + end + return neg ? BigDecimal(1) / ans : ans + end + + prec ||= [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig + + if y < 0 + inv = x.power(-y, prec) + return BigDecimal(0) if inv.infinite? + return BigDecimal::INFINITY if inv.zero? + return BigDecimal(1).div(inv, prec) + end + + int_part = y.fix.to_i + prec2 = prec + BigDecimal.double_fig + pow_prec = prec2 + (int_part > 0 ? y.exponent : 0) + ans = BigDecimal(1) + n = 1 + xn = x + while true + ans = ans.mult(xn, pow_prec) if int_part.allbits?(n) + n <<= 1 + break if n > int_part + xn = xn.mult(xn, pow_prec) + end + unless frac_part.zero? + ans = ans.mult(BigMath.exp(BigMath.log(x, prec2).mult(frac_part, prec2), prec2), prec2) + end + ans.mult(1, prec) + end +end + +# Core BigMath methods for BigDecimal (log, exp) are defined here. +# Other methods (sin, cos, atan) are defined in 'bigdecimal/math.rb'. +module BigMath + def self._coerce_to_bigdecimal(x, method_name, complex_domain_error = false) + case x + when BigDecimal + return x + when Integer, Float, Rational + return BigDecimal(x, 0) + when Complex + if complex_domain_error + raise Math::DomainError, "Complex argument for BigMath.#{method_name}" + end + end + raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal" + end + + def self._validate_prec(prec, method_name) + raise ArgumentError, 'precision must be an Integer' unless Integer === prec + raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0 + end + + # call-seq: + # BigMath.log(decimal, numeric) -> BigDecimal + # + # Computes the natural logarithm of +decimal+ to the specified number of + # digits of precision, +numeric+. + # + # If +decimal+ is zero or negative, raises Math::DomainError. + # + # If +decimal+ is positive infinity, returns Infinity. + # + # If +decimal+ is NaN, returns NaN. + # + def self.log(x, prec) + _validate_prec(prec, :log) + x = _coerce_to_bigdecimal(x, :log, true) + return BigDecimal::NAN if x.nan? + raise Math::DomainError, 'Zero or negative argument for log' if x <= 0 + return BigDecimal::INFINITY if x.infinite? + return BigDecimal(0) if x == 1 + + if x > 10 || x < 0.1 + log10 = log(BigDecimal(10), prec) + exponent = x.exponent + x = x * BigDecimal("1e#{-x.exponent}") + if x > 3 + x /= 10 + exponent += 1 + end + return log10 * exponent + log(x, prec) + end + + x_minus_one_exponent = (x - 1).exponent + prec += BigDecimal.double_fig + + # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps + sqrt_steps = [2 * Integer.sqrt(prec) + 3 * x_minus_one_exponent, 0].max + + # Reduce sqrt_step until sqrt gets fast + # https://github.com/ruby/bigdecimal/pull/323 + # https://github.com/ruby/bigdecimal/pull/343 + sqrt_steps /= 10 + + lg2 = 0.3010299956639812 + prec2 = prec + [-x_minus_one_exponent, 0].max + (sqrt_steps * lg2).ceil + + sqrt_steps.times do + x = x.sqrt(prec2) + + # Workaround for https://github.com/ruby/bigdecimal/issues/354 + x = x.mult(1, prec2 + BigDecimal.double_fig) + end + + # Taylor series for log(x) around 1 + # log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1) + # log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...) + x = (x - 1).div(x + 1, prec2) + y = x + x2 = x.mult(x, prec) + 1.step do |i| + n = prec + x.exponent - y.exponent + x2.exponent + break if n <= 0 || x.zero? + x = x.mult(x2.round(n - x2.exponent), n) + y = y.add(x.div(2 * i + 1, n), prec) + end + + y.mult(2 ** (sqrt_steps + 1), prec) + end + + # call-seq: + # BigMath.exp(decimal, numeric) -> BigDecimal + # + # Computes the value of e (the base of natural logarithms) raised to the + # power of +decimal+, to the specified number of digits of precision. + # + # If +decimal+ is infinity, returns Infinity. + # + # If +decimal+ is NaN, returns NaN. + # + def self.exp(x, prec) + _validate_prec(prec, :exp) + x = _coerce_to_bigdecimal(x, :exp) + return BigDecimal::NAN if x.nan? + return x.positive? ? BigDecimal::INFINITY : BigDecimal(0) if x.infinite? + return BigDecimal(1) if x.zero? + return BigDecimal(1).div(exp(-x, prec), prec) if x < 0 + + # exp(x * 10**cnt) = exp(x)**(10**cnt) + cnt = x > 1 ? x.exponent : 0 + prec2 = prec + BigDecimal.double_fig + cnt + x *= BigDecimal("1e-#{cnt}") + xn = BigDecimal(1) + y = BigDecimal(1) + + # Taylor series for exp(x) around 0 + 1.step do |i| + n = prec2 + xn.exponent + break if n <= 0 || xn.zero? + x = x.mult(1, n) + xn = xn.mult(x, n).div(i, n) + y = y.add(xn, prec2) + end + + # calculate exp(x * 10**cnt) from exp(x) + # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10 + cnt.times do + y2 = y.mult(y, prec2) + y5 = y2.mult(y2, prec2).mult(y, prec2) + y = y5.mult(y5, prec2) + end + + y.mult(1, prec) + end +end diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index a23cc5c6..5428e46f 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -55,7 +55,7 @@ def _assert_precision(mode) if mode == :fixed_point precision = -(value - expected).exponent elsif mode == :relative - precision = -(value.div(expected, expected.precision) - 1).exponent + precision = 1 - (value.div(expected, expected.precision) - 1).exponent else raise ArgumentError, "Unknown mode: #{mode}" end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 7a3527b9..300caa39 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1664,6 +1664,7 @@ def test_power_of_nan def test_power_with_Bignum BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) + BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) assert_equal(0, BigDecimal(0) ** (2**100)) assert_positive_infinite(BigDecimal(0) ** -(2**100)) @@ -1693,6 +1694,11 @@ def test_power_with_Bignum end end + def test_power_with_intger_infinite_precision + assert_equal(1234 ** 100, (BigDecimal("12.34") ** 100) * BigDecimal("1e200")) + assert_in_delta(1234 ** 100, 1 / (BigDecimal("12.34") ** -100) * BigDecimal("1e200"), 1) + end + def test_power_with_BigDecimal assert_nothing_raised do assert_in_delta(3 ** 3, BigDecimal(3) ** BigDecimal(3)) @@ -1720,12 +1726,9 @@ def test_power_of_finite_with_zero end def test_power_of_three - pend 'precision of power is buggy' if BASE_FIG == 4 - x = BigDecimal(3) assert_equal(81, x ** 4) - assert_equal(1.quo(81), x ** -4) - assert_in_delta(1.0/81, x ** -4) + assert_in_delta(1.quo(81), x ** -4, 1e-32) end def test_power_of_zero @@ -1817,18 +1820,38 @@ def test_power_of_negative_infinity end end - def test_power_without_prec - pend 'precision of power is buggy' if BASE_FIG == 4 + def test_infinite_power + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + assert_positive_infinite(BigDecimal::INFINITY ** BigDecimal::INFINITY) + assert_positive_zero(BigDecimal::INFINITY ** -BigDecimal::INFINITY) + assert_positive_infinite(BigDecimal(3) ** BigDecimal::INFINITY) + assert_positive_zero(BigDecimal(3) ** -BigDecimal::INFINITY) + assert_positive_zero(BigDecimal("0.5") ** BigDecimal::INFINITY) + assert_positive_infinite(BigDecimal("0.5") ** -BigDecimal::INFINITY) + assert_equal(1, BigDecimal(1) ** BigDecimal::INFINITY) + assert_equal(1, BigDecimal(1) ** -BigDecimal::INFINITY) + assert_positive_zero(BigDecimal(0) ** BigDecimal::INFINITY) + assert_positive_infinite(BigDecimal(0) ** -BigDecimal::INFINITY) + assert_positive_zero(BigDecimal(-0) ** BigDecimal::INFINITY) + assert_positive_infinite(BigDecimal(-0) ** -BigDecimal::INFINITY) + assert_raise(Math::DomainError) { BigDecimal(-3) ** BigDecimal::INFINITY } + assert_raise(Math::DomainError) { BigDecimal(-3) ** -BigDecimal::INFINITY } + assert_raise(Math::DomainError) { (-BigDecimal::INFINITY) ** BigDecimal::INFINITY } + assert_raise(Math::DomainError) { (-BigDecimal::INFINITY) ** -BigDecimal::INFINITY } + end + end + def test_power_without_prec pi = BigDecimal("3.14159265358979323846264338327950288419716939937511") e = BigDecimal("2.71828182845904523536028747135266249775724709369996") - pow = BigDecimal("0.2245915771836104547342715220454373502758931513399678438732330680117143493477164265678321738086407229773690574073268002736527e2") + pow = BigDecimal("0.2245915771836104547342715220454373502758931513399678438732330680117e2") assert_equal(pow, pi.power(e)) n = BigDecimal("2222") - assert_equal(BigDecimal("0.5171353084572525892492416e12"), (n ** 3.5)) - assert_equal(BigDecimal("0.517135308457252592e12"), (n ** 3.5r)) - assert_equal(BigDecimal("0.517135308457252589249241582e12"), (n ** BigDecimal("3.5",15))) + assert_equal(BigDecimal("0.51713530845725258924924158304123e12"), (n ** 3.5)) + assert_equal(BigDecimal("0.51713530845725258924924158304123e12"), (n ** 3.5r)) + assert_equal(BigDecimal("0.51713530845725258924924158304123e12"), (n ** BigDecimal("3.5", 100))) end def test_power_with_prec @@ -1838,15 +1861,38 @@ def test_power_with_prec assert_equal(pow, pi.power(e, 20)) b = BigDecimal('1.034482758620689655172413793103448275862068965517241379310344827586206896551724') - assert_equal(BigDecimal('0.114523E1'), b.power(4, 5), '[Bug #8818] [ruby-core:56802]') + assert_equal(BigDecimal('0.11452E1'), b.power(4, 5), '[Bug #8818] [ruby-core:56802]') + + assert_equal(BigDecimal('0.5394221232e-7'), BigDecimal('0.12345').power(8, 10)) + end + + def test_power_precision + x = BigDecimal("1.41421356237309504880168872420969807856967187537695") + y = BigDecimal("3.14159265358979323846264338327950288419716939937511") + small = x * BigDecimal("1e-30") + large = y * BigDecimal("1e+30") + assert_relative_precision {|n| small.power(small, n) } + assert_relative_precision {|n| large.power(small, n) } + assert_relative_precision {|n| x.power(small, n) } + assert_relative_precision {|n| small.power(y, n) } + assert_relative_precision {|n| small.power(small + 1, n) } + assert_relative_precision {|n| x.power(small + 1, n) } + assert_relative_precision {|n| (small + 1).power(small, n) } + assert_relative_precision {|n| (small + 1).power(large, n) } + assert_relative_precision {|n| (small + 1).power(y, n) } + assert_relative_precision {|n| x.power(y, n) } + assert_relative_precision {|n| x.power(-y, n) } + assert_relative_precision {|n| x.power(123, n) } + assert_relative_precision {|n| x.power(-456, n) } + assert_relative_precision {|n| (x + 12).power(y + 34, n) } + assert_relative_precision {|n| (x + 56).power(y - 78, n) } end def test_limit BigDecimal.save_limit do BigDecimal.limit(1) x = BigDecimal("3") - assert_equal(90, x ** 4) # OK? must it be 80? - # 3 * 3 * 3 * 3 = 10 * 3 * 3 = 30 * 3 = 90 ??? + assert_equal(80, x ** 4) assert_raise(ArgumentError) { BigDecimal.limit(-1) } bug7458 = '[ruby-core:50269] [#7458]' diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index e3402211..e4eb570f 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -93,6 +93,20 @@ def test_atan assert_relative_precision {|n| atan(BigDecimal("1e30"), n)} end + def test_exp + [-100, -2, 0.5, 10, 100].each do |x| + assert_in_epsilon(Math.exp(x), BigMath.exp(BigDecimal(x, 0), N)) + end + assert_equal(1, BigMath.exp(BigDecimal("0"), N)) + assert_in_epsilon(BigDecimal("4.48168907033806482260205546011927581900574986836966705677265008278593667446671377298105383138245339138861635065183019577"), + BigMath.exp(BigDecimal("1.5"), 100), BigDecimal("1e-100")) + assert_relative_precision {|n| BigMath.exp(BigDecimal("1"), n) } + assert_relative_precision {|n| BigMath.exp(BigDecimal("-2"), n) } + assert_relative_precision {|n| BigMath.exp(BigDecimal("-34"), n) } + assert_relative_precision {|n| BigMath.exp(BigDecimal("567"), n) } + assert_relative_precision {|n| BigMath.exp(SQRT2, n) } + end + def test_log assert_equal(0, BigMath.log(BigDecimal("1.0"), 10)) assert_in_epsilon(Math.log(10)*1000, BigMath.log(BigDecimal("1e1000"), 10)) @@ -102,6 +116,7 @@ def test_log assert_relative_precision {|n| BigMath.log(BigDecimal("1e-30") + 1, n) } assert_relative_precision {|n| BigMath.log(BigDecimal("1e-30"), n) } assert_relative_precision {|n| BigMath.log(BigDecimal("1e30"), n) } + assert_relative_precision {|n| BigMath.log(SQRT2, n) } assert_raise(Math::DomainError) {BigMath.log(BigDecimal("0"), 10)} assert_raise(Math::DomainError) {BigMath.log(BigDecimal("-1"), 10)} assert_separately(%w[-rbigdecimal], <<-SRC) From 779237cd76dcf1a3e681f25ace1570ff91825084 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 20 Jul 2025 22:01:17 +0900 Subject: [PATCH 418/546] Remove unused "# define" macros (#382) --- ext/bigdecimal/bigdecimal.c | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 33b29475..badb4a59 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -114,29 +114,6 @@ bdvalue_nullable(BDVALUE v) #define HALF_BASE (BASE/2) #define BASE1 (BASE/10) -#ifndef RRATIONAL_ZERO_P -# define RRATIONAL_ZERO_P(x) (FIXNUM_P(rb_rational_num(x)) && \ - FIX2LONG(rb_rational_num(x)) == 0) -#endif - -#ifndef RRATIONAL_NEGATIVE_P -# define RRATIONAL_NEGATIVE_P(x) RTEST(rb_funcall((x), '<', 1, INT2FIX(0))) -#endif - -#ifndef DECIMAL_SIZE_OF_BITS -#define DECIMAL_SIZE_OF_BITS(n) (((n) * 3010 + 9998) / 9999) -/* an approximation of ceil(n * log10(2)), upto 65536 at least */ -#endif - -#ifdef PRIsVALUE -# define RB_OBJ_CLASSNAME(obj) rb_obj_class(obj) -# define RB_OBJ_STRING(obj) (obj) -#else -# define PRIsVALUE "s" -# define RB_OBJ_CLASSNAME(obj) rb_obj_classname(obj) -# define RB_OBJ_STRING(obj) StringValueCStr(obj) -#endif - #ifndef MAYBE_UNUSED # define MAYBE_UNUSED(x) x #endif From 815b804a4096c6ed906705dc257e57f57f2eb35f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 13:33:27 +0000 Subject: [PATCH 419/546] Bump step-security/harden-runner from 2.12.2 to 2.13.0 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.12.2 to 2.13.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/6c439dc8bdf85cadbbce9ed30d1c7b959517bc49...ec9f2d5744a09debf3a187a3f4f675c53b671911) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index ed57baee..9e73446b 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 with: egress-policy: audit From d8bacc6b3df6d6cb0f8ec347358bbcbd5ec2daa9 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 26 Jul 2025 15:00:32 +0900 Subject: [PATCH 420/546] VpFormatSt O(n^2) to O(n) (#384) --- ext/bigdecimal/bigdecimal.c | 36 +++++++++++++++++------------- test/bigdecimal/test_bigdecimal.rb | 2 ++ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index badb4a59..2650233b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -5704,25 +5704,29 @@ VPrint(FILE *fp, const char *cntl_chr, Real *a) static void VpFormatSt(char *psz, size_t fFmt) { - size_t ie, i, nf = 0; - char ch; + size_t iend, idig = 0, iexp = 0, nspaces; + char *p; if (fFmt == 0) return; - ie = strlen(psz); - for (i = 0; i < ie; ++i) { - ch = psz[i]; - if (!ch) break; - if (ISSPACE(ch) || ch=='-' || ch=='+') continue; - if (ch == '.') { nf = 0; continue; } - if (ch == 'E' || ch == 'e') break; - - if (++nf > fFmt) { - memmove(psz + i + 1, psz + i, ie - i + 1); - ++ie; - nf = 0; - psz[i] = ' '; - } + iend = strlen(psz); + + if ((p = strchr(psz, '.'))) { + idig = (p - psz) + 1; + } + if ((p = strchr(psz, 'E')) || (p = strchr(psz, 'e'))) { + iexp = p - psz; + } + if (idig == 0 || idig > iexp) return; + + nspaces = (iexp - idig - 1) / fFmt; + p = psz + iend + 1; + for (size_t i = nspaces; i > 0; i--) { + char *src = psz + idig + i * fFmt; + char *dst = psz + idig + i * (fFmt + 1); + memmove(dst, src, p - src); + dst[-1] = ' '; + p = src; } } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 300caa39..dc12d426 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1601,6 +1601,8 @@ def test_to_s assert_equal('+1000000 0000000.0', BigDecimal('10000000000000').to_s('+7F')) assert_equal('0.1234567890123456789e3', BigDecimal('123.45678901234567890').to_s) assert_equal('0.12345 67890 12345 6789e3', BigDecimal('123.45678901234567890').to_s(5)) + assert_equal('0.123456 789012 345678e3', BigDecimal('123.456789012345678').to_s(6)) + assert_equal('0.123456 789012 345678 9e3', BigDecimal('123.4567890123456789').to_s(6)) end def test_split From 5d85778f89e0c46d423325078c10fa2d636291a7 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 27 Jul 2025 23:51:50 +0900 Subject: [PATCH 421/546] Ignore ndigits passed to BigDecimal(string, ndigits) (#385) Don't allocate huge memory when huge ndigits is passed --- ext/bigdecimal/bigdecimal.c | 4 ++-- test/bigdecimal/test_bigdecimal.rb | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 2650233b..a8e6b6b9 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3224,7 +3224,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) return rb_convert_to_BigDecimal(rb_complex_real(val), digs, raise_exception); } else if (RB_TYPE_P(val, T_STRING)) { - return rb_str_convert_to_BigDecimal(val, digs, raise_exception); + return rb_str_convert_to_BigDecimal(val, 0, raise_exception); } /* TODO: chheck to_d */ @@ -3238,7 +3238,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } return Qnil; } - return rb_str_convert_to_BigDecimal(str, digs, raise_exception); + return rb_str_convert_to_BigDecimal(str, 0, raise_exception); } /* call-seq: diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index dc12d426..f8c84d19 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -113,6 +113,11 @@ def test_BigDecimal end end + def test_BigDecimal_ignore_digits + assert_equal(1, BigDecimal(1, LIMITS["FIXNUM_MAX"])) + assert_equal(1, BigDecimal('1', LIMITS["FIXNUM_MAX"])) + end + def test_BigDecimal_bug7522 bd = BigDecimal("1.12", 1) assert_same(bd, BigDecimal(bd)) From 94b741c3041bdada4ac31c8e8d90ef090d22e4f9 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 28 Jul 2025 04:22:13 +0900 Subject: [PATCH 422/546] Better error message for negative**intinite and zero-converge case fix (#386) 'Computation results in complex number' is not a good error message for `(-2)**INFINITY`. `(-2)**-INFINITY` and `(-0.5)**INFINITY` converge to zero --- lib/bigdecimal.rb | 22 ++++++++++++++-------- test/bigdecimal/test_bigdecimal.rb | 18 +++++++++++++----- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 55912e8c..c23005ed 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -48,6 +48,20 @@ def power(y, prec = nil) return BigDecimal::NAN if x.nan? || y.nan? + if y.infinite? + if x < 0 + return BigDecimal(0) if x < -1 && y.negative? + return BigDecimal(0) if x > -1 && y.positive? + raise Math::DomainError, 'Result undefined for negative base raised to infinite power' + elsif x < 1 + return y.positive? ? BigDecimal(0) : BigDecimal::INFINITY + elsif x == 1 + return BigDecimal(1) + else + return y.positive? ? BigDecimal::INFINITY : BigDecimal(0) + end + end + if x.zero? return BigDecimal(1) if y.zero? return BigDecimal(0) if y > 0 @@ -70,14 +84,6 @@ def power(y, prec = nil) return BigDecimal(1) end - if y.infinite? - if x < 1 - return y.positive? ? BigDecimal(0) : BigDecimal::INFINITY - else - return y.positive? ? BigDecimal::INFINITY : BigDecimal(0) - end - end - prec ||= BigDecimal.limit.nonzero? frac_part = y.frac diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f8c84d19..53b4fc77 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1840,12 +1840,20 @@ def test_infinite_power assert_equal(1, BigDecimal(1) ** -BigDecimal::INFINITY) assert_positive_zero(BigDecimal(0) ** BigDecimal::INFINITY) assert_positive_infinite(BigDecimal(0) ** -BigDecimal::INFINITY) - assert_positive_zero(BigDecimal(-0) ** BigDecimal::INFINITY) - assert_positive_infinite(BigDecimal(-0) ** -BigDecimal::INFINITY) - assert_raise(Math::DomainError) { BigDecimal(-3) ** BigDecimal::INFINITY } - assert_raise(Math::DomainError) { BigDecimal(-3) ** -BigDecimal::INFINITY } + assert_positive_zero(BigDecimal(-0.0) ** BigDecimal::INFINITY) + assert_positive_infinite(BigDecimal(-0.0) ** -BigDecimal::INFINITY) + + # negative_number ** infinite_number converge to zero + assert_positive_zero(BigDecimal(-2) ** -BigDecimal::INFINITY) + assert_positive_zero(BigDecimal(-0.5) ** BigDecimal::INFINITY) + assert_positive_zero((-BigDecimal::INFINITY) ** -BigDecimal::INFINITY) + + # negative_number ** infinite_number that does not converge + assert_raise(Math::DomainError) { BigDecimal(-2) ** BigDecimal::INFINITY } + assert_raise(Math::DomainError) { BigDecimal(-0.5) ** -BigDecimal::INFINITY } + assert_raise(Math::DomainError) { BigDecimal(-1) ** BigDecimal::INFINITY } + assert_raise(Math::DomainError) { BigDecimal(-1) ** -BigDecimal::INFINITY } assert_raise(Math::DomainError) { (-BigDecimal::INFINITY) ** BigDecimal::INFINITY } - assert_raise(Math::DomainError) { (-BigDecimal::INFINITY) ** -BigDecimal::INFINITY } end end From 2bb5167698851078ea50a8753d2af2c34971e381 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 28 Jul 2025 06:42:19 +0900 Subject: [PATCH 423/546] Refactor truncate floor and ceil duplicated part (#387) --- ext/bigdecimal/bigdecimal.c | 95 ++++++++++++------------------------- 1 file changed, 30 insertions(+), 65 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a8e6b6b9..d76f708c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2450,6 +2450,33 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) return CheckGetValue(c); } +static VALUE +BigDecimal_truncate_floor_ceil(int argc, VALUE *argv, VALUE self, unsigned short rounding_mode) +{ + ENTER(5); + BDVALUE c, a; + int iLoc; + VALUE vLoc; + size_t mx, pl = VpSetPrecLimit(0); + + if (rb_scan_args(argc, argv, "01", &vLoc) == 0) { + iLoc = 0; + } + else { + iLoc = NUM2INT(vLoc); + } + + GUARD_OBJ(a, GetBDValueMust(self)); + mx = (a.real->Prec + 1) * BASE_FIG; + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + VpSetPrecLimit(pl); + VpActiveRound(c.real, a.real, rounding_mode, iLoc); + if (argc == 0) { + return BigDecimal_to_i(CheckGetValue(c)); + } + return CheckGetValue(c); +} + /* call-seq: * truncate(n) * @@ -2472,28 +2499,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_truncate(int argc, VALUE *argv, VALUE self) { - ENTER(5); - BDVALUE c, a; - int iLoc; - VALUE vLoc; - size_t mx, pl = VpSetPrecLimit(0); - - if (rb_scan_args(argc, argv, "01", &vLoc) == 0) { - iLoc = 0; - } - else { - iLoc = NUM2INT(vLoc); - } - - GUARD_OBJ(a, GetBDValueMust(self)); - mx = (a.real->Prec + 1) * BASE_FIG; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); - VpSetPrecLimit(pl); - VpActiveRound(c.real, a.real, VP_ROUND_DOWN, iLoc); /* 0: truncate */ - if (argc == 0) { - return BigDecimal_to_i(CheckGetValue(c)); - } - return CheckGetValue(c); + return BigDecimal_truncate_floor_ceil(argc, argv, self, VP_ROUND_DOWN); } /* Return the fractional part of the number, as a BigDecimal. @@ -2532,28 +2538,7 @@ BigDecimal_frac(VALUE self) static VALUE BigDecimal_floor(int argc, VALUE *argv, VALUE self) { - ENTER(5); - BDVALUE c, a; - int iLoc; - VALUE vLoc; - size_t mx, pl = VpSetPrecLimit(0); - - if (rb_scan_args(argc, argv, "01", &vLoc)==0) { - iLoc = 0; - } - else { - iLoc = NUM2INT(vLoc); - } - - GUARD_OBJ(a, GetBDValueMust(self)); - mx = (a.real->Prec + 1) * BASE_FIG; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); - VpSetPrecLimit(pl); - VpActiveRound(c.real, a.real, VP_ROUND_FLOOR, iLoc); - if (argc == 0) { - return BigDecimal_to_i(CheckGetValue(c)); - } - return CheckGetValue(c); + return BigDecimal_truncate_floor_ceil(argc, argv, self, VP_ROUND_FLOOR); } /* call-seq: @@ -2576,27 +2561,7 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_ceil(int argc, VALUE *argv, VALUE self) { - ENTER(5); - BDVALUE c, a; - int iLoc; - VALUE vLoc; - size_t mx, pl = VpSetPrecLimit(0); - - if (rb_scan_args(argc, argv, "01", &vLoc) == 0) { - iLoc = 0; - } else { - iLoc = NUM2INT(vLoc); - } - - GUARD_OBJ(a, GetBDValueMust(self)); - mx = (a.real->Prec + 1) * BASE_FIG; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); - VpSetPrecLimit(pl); - VpActiveRound(c.real, a.real, VP_ROUND_CEIL, iLoc); - if (argc == 0) { - return BigDecimal_to_i(CheckGetValue(c)); - } - return CheckGetValue(c); + return BigDecimal_truncate_floor_ceil(argc, argv, self, VP_ROUND_CEIL); } /* call-seq: From 45b86267731d390f1acfd52a90e6ab58a1bdd78b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 28 Jul 2025 20:41:33 +0900 Subject: [PATCH 424/546] Fix exp log power to raise "Computation results in Infinity/NaN" in EXCEPTION_INFINITY/EXCEPTION_NaN mode (#389) --- lib/bigdecimal.rb | 47 +++-- test/bigdecimal/test_bigdecimal.rb | 298 ++++++++++++++--------------- 2 files changed, 178 insertions(+), 167 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index c23005ed..40d9cffc 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -46,7 +46,8 @@ def power(y, prec = nil) x = self y = BigMath._coerce_to_bigdecimal(y, :power) - return BigDecimal::NAN if x.nan? || y.nan? + return BigMath._nan_computation_result if x.nan? || y.nan? + return BigDecimal(1) if y.zero? if y.infinite? if x < 0 @@ -54,21 +55,27 @@ def power(y, prec = nil) return BigDecimal(0) if x > -1 && y.positive? raise Math::DomainError, 'Result undefined for negative base raised to infinite power' elsif x < 1 - return y.positive? ? BigDecimal(0) : BigDecimal::INFINITY + return y.positive? ? BigDecimal(0) : BigMath._infinity_computation_result elsif x == 1 return BigDecimal(1) else - return y.positive? ? BigDecimal::INFINITY : BigDecimal(0) + return y.positive? ? BigMath._infinity_computation_result : BigDecimal(0) end end + if x.infinite? && y < 0 + # Computation result will be +0 or -0. Avoid overflow. + neg = x < 0 && y.frac.zero? && y % 2 == 1 + return neg ? -BigDecimal(0) : BigDecimal(0) + end + if x.zero? return BigDecimal(1) if y.zero? return BigDecimal(0) if y > 0 if y.frac.zero? && y % 2 == 1 && x.sign == -1 - return -BigDecimal::INFINITY + return -BigMath._infinity_computation_result else - return BigDecimal::INFINITY + return BigMath._infinity_computation_result end elsif x < 0 if y.frac.zero? @@ -101,7 +108,7 @@ def power(y, prec = nil) xn *= xn # Detect overflow/underflow before consuming infinite memory if (xn.exponent.abs - 1) * int_part / n >= 0x7FFFFFFFFFFFFFFF - return ((xn.exponent > 0) ^ neg ? BigDecimal::INFINITY : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1) + return ((xn.exponent > 0) ^ neg ? BigMath._infinity_computation_result : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1) end end return neg ? BigDecimal(1) / ans : ans @@ -112,7 +119,7 @@ def power(y, prec = nil) if y < 0 inv = x.power(-y, prec) return BigDecimal(0) if inv.infinite? - return BigDecimal::INFINITY if inv.zero? + return BigMath._infinity_computation_result if inv.zero? return BigDecimal(1).div(inv, prec) end @@ -138,7 +145,7 @@ def power(y, prec = nil) # Core BigMath methods for BigDecimal (log, exp) are defined here. # Other methods (sin, cos, atan) are defined in 'bigdecimal/math.rb'. module BigMath - def self._coerce_to_bigdecimal(x, method_name, complex_domain_error = false) + def self._coerce_to_bigdecimal(x, method_name, complex_domain_error = false) # :nodoc: case x when BigDecimal return x @@ -152,11 +159,25 @@ def self._coerce_to_bigdecimal(x, method_name, complex_domain_error = false) raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal" end - def self._validate_prec(prec, method_name) + def self._validate_prec(prec, method_name) # :nodoc: raise ArgumentError, 'precision must be an Integer' unless Integer === prec raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0 end + def self._infinity_computation_result # :nodoc: + if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_INFINITY) + raise FloatDomainError, "Computation results in 'Infinity'" + end + BigDecimal::INFINITY + end + + def self._nan_computation_result # :nodoc: + if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_NaN) + raise FloatDomainError, "Computation results to 'NaN'" + end + BigDecimal::NAN + end + # call-seq: # BigMath.log(decimal, numeric) -> BigDecimal # @@ -172,9 +193,9 @@ def self._validate_prec(prec, method_name) def self.log(x, prec) _validate_prec(prec, :log) x = _coerce_to_bigdecimal(x, :log, true) - return BigDecimal::NAN if x.nan? + return _nan_computation_result if x.nan? raise Math::DomainError, 'Zero or negative argument for log' if x <= 0 - return BigDecimal::INFINITY if x.infinite? + return _infinity_computation_result if x.infinite? return BigDecimal(0) if x == 1 if x > 10 || x < 0.1 @@ -238,8 +259,8 @@ def self.log(x, prec) def self.exp(x, prec) _validate_prec(prec, :exp) x = _coerce_to_bigdecimal(x, :exp) - return BigDecimal::NAN if x.nan? - return x.positive? ? BigDecimal::INFINITY : BigDecimal(0) if x.infinite? + return _nan_computation_result if x.nan? + return x.positive? ? _infinity_computation_result : BigDecimal(0) if x.infinite? return BigDecimal(1) if x.zero? return BigDecimal(1).div(exp(-x, prec), prec) if x < 0 diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 53b4fc77..59a39c6d 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -32,6 +32,8 @@ class TestBigDecimal < Test::Unit::TestCase EXPONENT_MAX = LIMITS['INTPTR_MAX'] / BASE_FIG * BASE_FIG EXPONENT_MIN = (LIMITS['INTPTR_MIN'] - 2) / BASE_FIG * BASE_FIG + BASE_FIG + 1 + NEGATIVE_INFINITY = -BigDecimal::INFINITY + ROUNDING_MODE_MAP = [ [ BigDecimal::ROUND_UP, :up], [ BigDecimal::ROUND_DOWN, :down], @@ -60,6 +62,32 @@ def assert_negative_infinite(x) assert_operator(x, :<, 0) end + def assert_infinite_calculation(positive:) + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) + positive ? assert_positive_infinite(yield) : assert_negative_infinite(yield) + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) + assert_raise_with_message(FloatDomainError, /Infinity/) { yield } + end + end + + def assert_positive_infinite_calculation(&block) + assert_infinite_calculation(positive: true, &block) + end + + def assert_negative_infinite_calculation(&block) + assert_infinite_calculation(positive: false, &block) + end + + def assert_nan_calculation(&block) + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + assert_nan(yield) + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) + assert_raise_with_message(FloatDomainError, /NaN/) { yield } + end + end + def assert_positive_zero(x) assert_equal(BigDecimal::SIGN_POSITIVE_ZERO, x.sign, "Expected #{x.inspect} to be positive zero") @@ -1650,38 +1678,35 @@ def test_power_with_nil end def test_power_of_nan - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - assert_nan(BigDecimal::NAN ** 0) - assert_nan(BigDecimal::NAN ** 1) - assert_nan(BigDecimal::NAN ** 42) - assert_nan(BigDecimal::NAN ** -42) - assert_nan(BigDecimal::NAN ** 42.0) - assert_nan(BigDecimal::NAN ** -42.0) - assert_nan(BigDecimal::NAN ** BigDecimal(42)) - assert_nan(BigDecimal::NAN ** BigDecimal(-42)) - assert_nan(BigDecimal::NAN ** BigDecimal::INFINITY) - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) - assert_nan(BigDecimal::NAN ** (-BigDecimal::INFINITY)) - end - end + assert_nan_calculation { BigDecimal::NAN ** 0 } + assert_nan_calculation { BigDecimal::NAN ** 1 } + assert_nan_calculation { BigDecimal::NAN ** 42 } + assert_nan_calculation { BigDecimal::NAN ** -42 } + assert_nan_calculation { BigDecimal::NAN ** 42.0 } + assert_nan_calculation { BigDecimal::NAN ** -42.0 } + assert_nan_calculation { BigDecimal::NAN ** BigDecimal(42) } + assert_nan_calculation { BigDecimal::NAN ** BigDecimal(-42) } + assert_nan_calculation { BigDecimal::NAN ** BigDecimal::INFINITY } + assert_nan_calculation { BigDecimal::NAN ** NEGATIVE_INFINITY } end def test_power_with_Bignum + assert_equal(0, BigDecimal(0) ** (2**100)) + + assert_positive_infinite_calculation { BigDecimal(0) ** -(2**100) } + assert_positive_infinite_calculation { BigDecimal(0) ** -(2**100 + 1) } + assert_positive_infinite_calculation { (-BigDecimal(0)) ** -(2**100) } + assert_negative_infinite_calculation { (-BigDecimal(0)) ** -(2**100 + 1) } + + assert_equal(1, BigDecimal(1) ** (2**100)) + assert_equal(1, BigDecimal(-1) ** (2**100)) + assert_equal(1, BigDecimal(1) ** (2**100 + 1)) + assert_equal(-1, BigDecimal(-1) ** (2**100 + 1)) + BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) - assert_equal(0, BigDecimal(0) ** (2**100)) - - assert_positive_infinite(BigDecimal(0) ** -(2**100)) - assert_positive_infinite((-BigDecimal(0)) ** -(2**100)) - assert_negative_infinite((-BigDecimal(0)) ** -(2**100 + 1)) - - assert_equal(1, BigDecimal(1) ** (2**100)) - assert_equal(1, BigDecimal(-1) ** (2**100)) - assert_equal(1, BigDecimal(1) ** (2**100 + 1)) - assert_equal(-1, BigDecimal(-1) ** (2**100 + 1)) + # TODO: Add test and implementation for underflow and overflow errors assert_positive_infinite(BigDecimal(3) ** (2**100)) assert_positive_zero(BigDecimal(3) ** (-2**100)) @@ -1748,113 +1773,101 @@ def test_power_of_zero assert_equal(1, zero ** 0.quo(1)) assert_equal(1, zero ** 0.0) assert_equal(1, zero ** BigDecimal(0)) - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) - BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) - assert_positive_infinite(zero ** -1) - assert_positive_infinite(zero ** -1.quo(1)) - assert_positive_infinite(zero ** -1.0) - assert_positive_infinite(zero ** BigDecimal(-1)) - m_zero = BigDecimal("-0") - assert_negative_infinite(m_zero ** -1) - assert_negative_infinite(m_zero ** -1.quo(1)) - assert_negative_infinite(m_zero ** -1.0) - assert_negative_infinite(m_zero ** BigDecimal(-1)) - assert_positive_infinite(m_zero ** -2) - assert_positive_infinite(m_zero ** -2.quo(1)) - assert_positive_infinite(m_zero ** -2.0) - assert_positive_infinite(m_zero ** BigDecimal(-2)) - end + assert_positive_infinite_calculation { zero ** -1 } + assert_positive_infinite_calculation { zero ** -1.quo(1) } + assert_positive_infinite_calculation { zero ** -1.0 } + assert_positive_infinite_calculation { zero ** BigDecimal(-1) } + + m_zero = BigDecimal("-0") + assert_negative_infinite_calculation { m_zero ** -1 } + assert_negative_infinite_calculation { m_zero ** -1.quo(1) } + assert_negative_infinite_calculation { m_zero ** -1.0 } + assert_negative_infinite_calculation { m_zero ** BigDecimal(-1) } + assert_positive_infinite_calculation { m_zero ** -2 } + assert_positive_infinite_calculation { m_zero ** -2.quo(1) } + assert_positive_infinite_calculation { m_zero ** -2.0 } + assert_positive_infinite_calculation { m_zero ** BigDecimal(-2) } end def test_power_of_positive_infinity - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) - assert_positive_infinite(BigDecimal::INFINITY ** 3) - assert_positive_infinite(BigDecimal::INFINITY ** 3.quo(1)) - assert_positive_infinite(BigDecimal::INFINITY ** 3.0) - assert_positive_infinite(BigDecimal::INFINITY ** BigDecimal(3)) - assert_positive_infinite(BigDecimal::INFINITY ** 2) - assert_positive_infinite(BigDecimal::INFINITY ** 2.quo(1)) - assert_positive_infinite(BigDecimal::INFINITY ** 2.0) - assert_positive_infinite(BigDecimal::INFINITY ** BigDecimal(2)) - assert_positive_infinite(BigDecimal::INFINITY ** 1) - assert_positive_infinite(BigDecimal::INFINITY ** 1.quo(1)) - assert_positive_infinite(BigDecimal::INFINITY ** 1.0) - assert_positive_infinite(BigDecimal::INFINITY ** BigDecimal(1)) - assert_equal(1, BigDecimal::INFINITY ** 0) - assert_equal(1, BigDecimal::INFINITY ** 0.quo(1)) - assert_equal(1, BigDecimal::INFINITY ** 0.0) - assert_equal(1, BigDecimal::INFINITY ** BigDecimal(0)) - assert_positive_zero(BigDecimal::INFINITY ** -1) - assert_positive_zero(BigDecimal::INFINITY ** -1.quo(1)) - assert_positive_zero(BigDecimal::INFINITY ** -1.0) - assert_positive_zero(BigDecimal::INFINITY ** BigDecimal(-1)) - assert_positive_zero(BigDecimal::INFINITY ** -2) - assert_positive_zero(BigDecimal::INFINITY ** -2.0) - assert_positive_zero(BigDecimal::INFINITY ** BigDecimal(-2)) - end + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 3 } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 3.quo(1) } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 3.0 } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** BigDecimal(3) } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 2 } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 2.quo(1) } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 2.0 } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** BigDecimal(2) } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 1 } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 1.quo(1) } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 1.0 } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** BigDecimal(1) } + assert_equal(1, BigDecimal::INFINITY ** 0) + assert_equal(1, BigDecimal::INFINITY ** 0.quo(1)) + assert_equal(1, BigDecimal::INFINITY ** 0.0) + assert_equal(1, BigDecimal::INFINITY ** BigDecimal(0)) + assert_positive_zero(BigDecimal::INFINITY ** -1) + assert_positive_zero(BigDecimal::INFINITY ** -1.quo(1)) + assert_positive_zero(BigDecimal::INFINITY ** -1.0) + assert_positive_zero(BigDecimal::INFINITY ** BigDecimal(-1)) + assert_positive_zero(BigDecimal::INFINITY ** -2) + assert_positive_zero(BigDecimal::INFINITY ** -2.0) + assert_positive_zero(BigDecimal::INFINITY ** BigDecimal(-2)) end def test_power_of_negative_infinity - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) - assert_negative_infinite((-BigDecimal::INFINITY) ** 3) - assert_negative_infinite((-BigDecimal::INFINITY) ** 3.quo(1)) - assert_negative_infinite((-BigDecimal::INFINITY) ** 3.0) - assert_negative_infinite((-BigDecimal::INFINITY) ** BigDecimal(3)) - assert_positive_infinite((-BigDecimal::INFINITY) ** 2) - assert_positive_infinite((-BigDecimal::INFINITY) ** 2.quo(1)) - assert_positive_infinite((-BigDecimal::INFINITY) ** 2.0) - assert_positive_infinite((-BigDecimal::INFINITY) ** BigDecimal(2)) - assert_negative_infinite((-BigDecimal::INFINITY) ** 1) - assert_negative_infinite((-BigDecimal::INFINITY) ** 1.quo(1)) - assert_negative_infinite((-BigDecimal::INFINITY) ** 1.0) - assert_negative_infinite((-BigDecimal::INFINITY) ** BigDecimal(1)) - assert_equal(1, (-BigDecimal::INFINITY) ** 0) - assert_equal(1, (-BigDecimal::INFINITY) ** 0.quo(1)) - assert_equal(1, (-BigDecimal::INFINITY) ** 0.0) - assert_equal(1, (-BigDecimal::INFINITY) ** BigDecimal(0)) - assert_negative_zero((-BigDecimal::INFINITY) ** -1) - assert_negative_zero((-BigDecimal::INFINITY) ** -1.quo(1)) - assert_negative_zero((-BigDecimal::INFINITY) ** -1.0) - assert_negative_zero((-BigDecimal::INFINITY) ** BigDecimal(-1)) - assert_positive_zero((-BigDecimal::INFINITY) ** -2) - assert_positive_zero((-BigDecimal::INFINITY) ** -2.quo(1)) - assert_positive_zero((-BigDecimal::INFINITY) ** -2.0) - assert_positive_zero((-BigDecimal::INFINITY) ** BigDecimal(-2)) - end + assert_negative_infinite_calculation { NEGATIVE_INFINITY ** 3 } + assert_negative_infinite_calculation { NEGATIVE_INFINITY ** 3.quo(1) } + assert_negative_infinite_calculation { NEGATIVE_INFINITY ** 3.0 } + assert_negative_infinite_calculation { NEGATIVE_INFINITY ** BigDecimal(3) } + assert_positive_infinite_calculation { NEGATIVE_INFINITY ** 2 } + assert_positive_infinite_calculation { NEGATIVE_INFINITY ** 2.quo(1) } + assert_positive_infinite_calculation { NEGATIVE_INFINITY ** 2.0 } + assert_positive_infinite_calculation { NEGATIVE_INFINITY ** BigDecimal(2) } + assert_negative_infinite_calculation { NEGATIVE_INFINITY ** 1 } + assert_negative_infinite_calculation { NEGATIVE_INFINITY ** 1.quo(1) } + assert_negative_infinite_calculation { NEGATIVE_INFINITY ** 1.0 } + assert_negative_infinite_calculation { NEGATIVE_INFINITY ** BigDecimal(1) } + assert_equal(1, NEGATIVE_INFINITY ** 0) + assert_equal(1, NEGATIVE_INFINITY ** 0.quo(1)) + assert_equal(1, NEGATIVE_INFINITY ** 0.0) + assert_equal(1, NEGATIVE_INFINITY ** BigDecimal(0)) + assert_negative_zero(NEGATIVE_INFINITY ** -1) + assert_negative_zero(NEGATIVE_INFINITY ** -1.quo(1)) + assert_negative_zero(NEGATIVE_INFINITY ** -1.0) + assert_negative_zero(NEGATIVE_INFINITY ** BigDecimal(-1)) + assert_positive_zero(NEGATIVE_INFINITY ** -2) + assert_positive_zero(NEGATIVE_INFINITY ** -2.quo(1)) + assert_positive_zero(NEGATIVE_INFINITY ** -2.0) + assert_positive_zero(NEGATIVE_INFINITY ** BigDecimal(-2)) end def test_infinite_power - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) - assert_positive_infinite(BigDecimal::INFINITY ** BigDecimal::INFINITY) - assert_positive_zero(BigDecimal::INFINITY ** -BigDecimal::INFINITY) - assert_positive_infinite(BigDecimal(3) ** BigDecimal::INFINITY) - assert_positive_zero(BigDecimal(3) ** -BigDecimal::INFINITY) - assert_positive_zero(BigDecimal("0.5") ** BigDecimal::INFINITY) - assert_positive_infinite(BigDecimal("0.5") ** -BigDecimal::INFINITY) - assert_equal(1, BigDecimal(1) ** BigDecimal::INFINITY) - assert_equal(1, BigDecimal(1) ** -BigDecimal::INFINITY) - assert_positive_zero(BigDecimal(0) ** BigDecimal::INFINITY) - assert_positive_infinite(BigDecimal(0) ** -BigDecimal::INFINITY) - assert_positive_zero(BigDecimal(-0.0) ** BigDecimal::INFINITY) - assert_positive_infinite(BigDecimal(-0.0) ** -BigDecimal::INFINITY) - - # negative_number ** infinite_number converge to zero - assert_positive_zero(BigDecimal(-2) ** -BigDecimal::INFINITY) - assert_positive_zero(BigDecimal(-0.5) ** BigDecimal::INFINITY) - assert_positive_zero((-BigDecimal::INFINITY) ** -BigDecimal::INFINITY) - - # negative_number ** infinite_number that does not converge - assert_raise(Math::DomainError) { BigDecimal(-2) ** BigDecimal::INFINITY } - assert_raise(Math::DomainError) { BigDecimal(-0.5) ** -BigDecimal::INFINITY } - assert_raise(Math::DomainError) { BigDecimal(-1) ** BigDecimal::INFINITY } - assert_raise(Math::DomainError) { BigDecimal(-1) ** -BigDecimal::INFINITY } - assert_raise(Math::DomainError) { (-BigDecimal::INFINITY) ** BigDecimal::INFINITY } - end + assert_positive_infinite_calculation { BigDecimal::INFINITY ** BigDecimal::INFINITY } + assert_positive_zero(BigDecimal::INFINITY ** NEGATIVE_INFINITY) + assert_positive_infinite_calculation { BigDecimal(3) ** BigDecimal::INFINITY } + assert_positive_zero(BigDecimal(3) ** NEGATIVE_INFINITY) + assert_positive_zero(BigDecimal("0.5") ** BigDecimal::INFINITY) + assert_positive_infinite_calculation { BigDecimal("0.5") ** NEGATIVE_INFINITY } + assert_equal(1, BigDecimal(1) ** BigDecimal::INFINITY) + assert_equal(1, BigDecimal(1) ** NEGATIVE_INFINITY) + assert_positive_zero(BigDecimal(0) ** BigDecimal::INFINITY) + assert_positive_infinite_calculation { BigDecimal(0) ** NEGATIVE_INFINITY } + assert_positive_zero(BigDecimal(-0.0) ** BigDecimal::INFINITY) + assert_positive_infinite_calculation { BigDecimal(-0.0) ** NEGATIVE_INFINITY } + + # negative_number ** infinite_number converge to zero + assert_positive_zero(BigDecimal(-2) ** NEGATIVE_INFINITY) + assert_positive_zero(BigDecimal(-0.5) ** BigDecimal::INFINITY) + assert_positive_zero(NEGATIVE_INFINITY ** NEGATIVE_INFINITY) + + # negative_number ** infinite_number that does not converge + assert_raise(Math::DomainError) { BigDecimal(-2) ** BigDecimal::INFINITY } + assert_raise(Math::DomainError) { BigDecimal(-0.5) ** NEGATIVE_INFINITY } + assert_raise(Math::DomainError) { BigDecimal(-1) ** BigDecimal::INFINITY } + assert_raise(Math::DomainError) { BigDecimal(-1) ** NEGATIVE_INFINITY } + assert_raise(Math::DomainError) { NEGATIVE_INFINITY ** BigDecimal::INFINITY } end def test_power_without_prec @@ -2094,25 +2107,15 @@ def test_exp_with_negative end def test_exp_with_negative_infinite - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) - assert_equal(0, BigMath.exp(-BigDecimal::INFINITY, 20)) - end + assert_equal(0, BigMath.exp(NEGATIVE_INFINITY, 20)) end def test_exp_with_positive_infinite - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) - assert(BigMath.exp(BigDecimal::INFINITY, 20) > 0) - assert_positive_infinite(BigMath.exp(BigDecimal::INFINITY, 20)) - end + assert_positive_infinite_calculation { BigMath.exp(BigDecimal::INFINITY, 20) } end def test_exp_with_nan - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - assert_nan(BigMath.exp(BigDecimal::NAN, 20)) - end + assert_nan_calculation { BigMath.exp(BigDecimal::NAN, 20) } end def test_exp_with_1 @@ -2221,34 +2224,21 @@ def test_BigMath_log_with_negative_precision end def test_BigMath_log_with_negative_infinite - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) - assert_raise(Math::DomainError) do - BigMath.log(-BigDecimal::INFINITY, 20) - end + assert_raise(Math::DomainError) do + BigMath.log(NEGATIVE_INFINITY, 20) end end def test_BigMath_log_with_positive_infinite - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) - assert(BigMath.log(BigDecimal::INFINITY, 20) > 0) - assert_positive_infinite(BigMath.log(BigDecimal::INFINITY, 20)) - end + assert_positive_infinite_calculation { BigMath.log(BigDecimal::INFINITY, 20) } end def test_BigMath_log_with_nan - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - assert_nan(BigMath.log(BigDecimal::NAN, 20)) - end + assert_nan_calculation { BigMath.log(BigDecimal::NAN, 20) } end def test_BigMath_log_with_float_nan - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - assert_nan(BigMath.log(Float::NAN, 20)) - end + assert_nan_calculation { BigMath.log(Float::NAN, 20) } end def test_BigMath_log_with_1 From 81de140741815a010e8f692c3b91a719387e1709 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 28 Jul 2025 21:14:41 +0900 Subject: [PATCH 425/546] Reduce guard obj (#390) * Remove GUARD_OBJ of retval * Remove GUARD_OBJ of self Remove GUARD_OBJ of self and add RB_GC_GUARD. Some BigDecimal_ func are called from another func with non-self object that needs RB_GC_GUARD. * Remove GUARD_OBJ from add, sub, mult and cmp Remove GUARD_OBJ and use GUARD_OBJ(self) and GUARD_OBJ(rhs_value) --- ext/bigdecimal/bigdecimal.c | 229 +++++++++++++++++------------------- 1 file changed, 110 insertions(+), 119 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d76f708c..a1f357e3 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -106,7 +106,6 @@ bdvalue_nullable(BDVALUE v) #endif #define PUSH(x) (vStack[iStack++] = (VALUE)(x)) #define GUARD_OBJ(p, y) ((p)=(y), PUSH((p).bigdecimal)) -#define GUARD_OBJ_OR_NIL(p, y) ((p)=(y), PUSH((p).bigdecimal_or_nil)) #define BASE_FIG BIGDECIMAL_COMPONENT_FIGURES #define BASE BIGDECIMAL_BASE @@ -528,7 +527,6 @@ BigDecimal_double_fig(VALUE self) static VALUE BigDecimal_prec(VALUE self) { - ENTER(1); BDVALUE v; VALUE obj; @@ -536,9 +534,11 @@ BigDecimal_prec(VALUE self) "BigDecimal#precs is deprecated and will be removed in the future; " "use BigDecimal#precision instead."); - GUARD_OBJ(v, GetBDValueMust(self)); + v = GetBDValueMust(self); obj = rb_assoc_new(SIZET2NUM(v.real->Prec*VpBaseFig()), SIZET2NUM(v.real->MaxPrec*VpBaseFig())); + + RB_GC_GUARD(v.bigdecimal); return obj; } @@ -653,10 +653,9 @@ VpCountPrecisionAndScale(Real *p, ssize_t *out_precision, ssize_t *out_scale) static void BigDecimal_count_precision_and_scale(VALUE self, ssize_t *out_precision, ssize_t *out_scale) { - ENTER(1); - BDVALUE v; - GUARD_OBJ(v, GetBDValueMust(self)); + BDVALUE v = GetBDValueMust(self); VpCountPrecisionAndScale(v.real, out_precision, out_scale); + RB_GC_GUARD(v.bigdecimal); } /* @@ -745,9 +744,7 @@ BigDecimal_precision_scale(VALUE self) static VALUE BigDecimal_n_significant_digits(VALUE self) { - ENTER(1); - BDVALUE v; - GUARD_OBJ(v, GetBDValueMust(self)); + BDVALUE v = GetBDValueMust(self); if (VpIsZero(v.real) || !VpIsDef(v.real)) { return INT2FIX(0); } @@ -763,6 +760,7 @@ BigDecimal_n_significant_digits(VALUE self) int ntz = 0; for (x = v.real->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz; + RB_GC_GUARD(v.bigdecimal); ssize_t n_significant_digits = BASE_FIG*n - nlz - ntz; return SSIZET2NUM(n_significant_digits); } @@ -784,17 +782,14 @@ BigDecimal_n_significant_digits(VALUE self) static VALUE BigDecimal_hash(VALUE self) { - ENTER(1); - BDVALUE v; - st_index_t hash; - - GUARD_OBJ(v, GetBDValueMust(self)); - hash = (st_index_t)v.real->sign; + BDVALUE v = GetBDValueMust(self); + st_index_t hash = (st_index_t)v.real->sign; /* hash!=2: the case for 0(1),NaN(0) or +-Infinity(3) is sign itself */ if(hash == 2 || hash == (st_index_t)-2) { hash ^= rb_memhash(v.real->frac, sizeof(DECDIG)*v.real->Prec); hash += v.real->exponent; } + RB_GC_GUARD(v.bigdecimal); return ST2FIX(hash); } @@ -813,7 +808,6 @@ BigDecimal_hash(VALUE self) static VALUE BigDecimal_dump(int argc, VALUE *argv, VALUE self) { - ENTER(5); BDVALUE v; char *psz; VALUE dummy; @@ -821,13 +815,15 @@ BigDecimal_dump(int argc, VALUE *argv, VALUE self) size_t len; rb_scan_args(argc, argv, "01", &dummy); - GUARD_OBJ(v, GetBDValueMust(self)); + v = GetBDValueMust(self); dump = rb_str_new(0, VpNumOfChars(v.real, "E")+50); psz = RSTRING_PTR(dump); snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", v.real->Prec*VpBaseFig()); len = strlen(psz); VpToString(v.real, psz+len, RSTRING_LEN(dump)-len, 0, 0); rb_str_resize(dump, strlen(psz)); + + RB_GC_GUARD(v.bigdecimal); return dump; } @@ -837,7 +833,6 @@ BigDecimal_dump(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_load(VALUE self, VALUE str) { - ENTER(2); BDVALUE v; unsigned char *pch; unsigned char ch; @@ -849,7 +844,7 @@ BigDecimal_load(VALUE self, VALUE str) rb_raise(rb_eTypeError, "load failed: invalid character in the marshaled string"); } } - GUARD_OBJ(v, bdvalue_nonnullable(CreateFromString(0, (char *)pch, self, true, true))); + v = bdvalue_nonnullable(CreateFromString(0, (char *)pch, self, true, true)); return CheckGetValue(v); } @@ -1267,24 +1262,23 @@ static VALUE BigDecimal_split(VALUE self); static VALUE BigDecimal_to_i(VALUE self) { - ENTER(5); ssize_t e, nf; BDVALUE v; + VALUE ret; - GUARD_OBJ(v, GetBDValueMust(self)); + v = GetBDValueMust(self); BigDecimal_check_num(v.real); e = VpExponent10(v.real); if (e <= 0) return INT2FIX(0); nf = VpBaseFig(); if (e <= nf) { - return LONG2NUM((long)(VpGetSign(v.real) * (DECDIG_DBL_SIGNED)v.real->frac[0])); + ret = LONG2NUM((long)(VpGetSign(v.real) * (DECDIG_DBL_SIGNED)v.real->frac[0])); } else { VALUE a = BigDecimal_split(self); VALUE digits = RARRAY_AREF(a, 1); VALUE numerator = rb_funcall(digits, rb_intern("to_i"), 0); - VALUE ret; ssize_t dpower = e - (ssize_t)RSTRING_LEN(digits); if (BIGDECIMAL_NEGATIVE_P(v.real)) { @@ -1303,8 +1297,10 @@ BigDecimal_to_i(VALUE self) if (RB_TYPE_P(ret, T_FLOAT)) { rb_raise(rb_eFloatDomainError, "Infinity"); } - return ret; } + + RB_GC_GUARD(v.bigdecimal); + return ret; } /* Returns a new Float object having approximately the same value as the @@ -1314,14 +1310,13 @@ BigDecimal_to_i(VALUE self) static VALUE BigDecimal_to_f(VALUE self) { - ENTER(1); - BDVALUE v; double d; SIGNED_VALUE e; char *buf; volatile VALUE str; + BDVALUE v = GetBDValueMust(self); + bool negative = BIGDECIMAL_NEGATIVE_P(v.real); - GUARD_OBJ(v, GetBDValueMust(self)); if (VpVtoD(&d, &e, v.real) != 1) return rb_float_new(d); if (e > (SIGNED_VALUE)(DBL_MAX_10_EXP+BASE_FIG)) @@ -1332,6 +1327,9 @@ BigDecimal_to_f(VALUE self) str = rb_str_new(0, VpNumOfChars(v.real, "E")); buf = RSTRING_PTR(str); VpToString(v.real, buf, RSTRING_LEN(str), 0, 0); + + RB_GC_GUARD(v.bigdecimal); + errno = 0; d = strtod(buf, 0); if (errno == ERANGE) { @@ -1342,14 +1340,14 @@ BigDecimal_to_f(VALUE self) overflow: VpException(VP_EXCEPTION_OVERFLOW, "BigDecimal to Float conversion", 0); - if (BIGDECIMAL_NEGATIVE_P(v.real)) + if (negative) return rb_float_new(VpGetDoubleNegInf()); else return rb_float_new(VpGetDoublePosInf()); underflow: VpException(VP_EXCEPTION_UNDERFLOW, "BigDecimal to Float conversion", 0); - if (BIGDECIMAL_NEGATIVE_P(v.real)) + if (negative) return rb_float_new(-0.0); else return rb_float_new(0.0); @@ -1367,9 +1365,10 @@ BigDecimal_to_r(VALUE self) v = GetBDValueMust(self); BigDecimal_check_num(v.real); - sign = VpGetSign(v.real); power = VpExponent10(v.real); + RB_GC_GUARD(v.bigdecimal); + a = BigDecimal_split(self); digits = RARRAY_AREF(a, 1); denomi_power = power - RSTRING_LEN(digits); @@ -1407,26 +1406,20 @@ BigDecimal_to_r(VALUE self) static VALUE BigDecimal_coerce(VALUE self, VALUE other) { - ENTER(2); - VALUE obj; BDVALUE b; if (RB_TYPE_P(other, T_FLOAT)) { - GUARD_OBJ(b, GetBDValueWithPrecMust(other, 0)); - obj = rb_assoc_new(CheckGetValue(b), self); + b = GetBDValueWithPrecMust(other, 0); + } + else if (RB_TYPE_P(other, T_RATIONAL)) { + Real* pv = DATA_PTR(self); + b = GetBDValueWithPrecMust(other, pv->Prec*VpBaseFig()); } else { - if (RB_TYPE_P(other, T_RATIONAL)) { - Real* pv = DATA_PTR(self); - GUARD_OBJ(b, GetBDValueWithPrecMust(other, pv->Prec*VpBaseFig())); - } - else { - GUARD_OBJ(b, GetBDValueMust(other)); - } - obj = rb_assoc_new(b.bigdecimal, self); + b = GetBDValueMust(other); } - return obj; + return rb_assoc_new(CheckGetValue(b), self); } /* @@ -1465,20 +1458,18 @@ BigDecimal_uplus(VALUE self) static VALUE BigDecimal_add(VALUE self, VALUE r) { - ENTER(5); BDVALUE a, b, c; size_t mx; - GUARD_OBJ(a, GetBDValueMust(self)); + a = GetBDValueMust(self); if (RB_TYPE_P(r, T_FLOAT)) { - GUARD_OBJ(b, GetBDValueWithPrecMust(r, 0)); + b = GetBDValueWithPrecMust(r, 0); } else if (RB_TYPE_P(r, T_RATIONAL)) { - GUARD_OBJ(b, GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig())); + b = GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig()); } else { - NULLABLE_BDVALUE b2; - GUARD_OBJ_OR_NIL(b2, GetBDValue(r)); + NULLABLE_BDVALUE b2 = GetBDValue(r); if (!b2.real_or_null) return DoSomeOne(self, r, '+'); b = bdvalue_nonnullable(b2); } @@ -1489,11 +1480,11 @@ BigDecimal_add(VALUE self, VALUE r) mx = GetAddSubPrec(a.real, b.real); if (mx == (size_t)-1L) { /* a or b is inf */ - GUARD_OBJ(c, NewZeroWrapLimited(1, BASE_FIG)); + c = NewZeroWrapLimited(1, BASE_FIG); VpAddSub(c.real, a.real, b.real, 1); } else { - GUARD_OBJ(c, NewZeroWrapLimited(1, (mx + 1) * BASE_FIG)); + c = NewZeroWrapLimited(1, (mx + 1) * BASE_FIG); if (!mx) { VpSetInf(c.real, VpGetSign(a.real)); } @@ -1501,6 +1492,9 @@ BigDecimal_add(VALUE self, VALUE r) VpAddSub(c.real, a.real, b.real, 1); } } + + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal); return CheckGetValue(c); } @@ -1522,20 +1516,18 @@ BigDecimal_add(VALUE self, VALUE r) static VALUE BigDecimal_sub(VALUE self, VALUE r) { - ENTER(5); BDVALUE a, b, c; size_t mx; - GUARD_OBJ(a, GetBDValueMust(self)); + a = GetBDValueMust(self); if (RB_TYPE_P(r, T_FLOAT)) { - GUARD_OBJ(b, GetBDValueWithPrecMust(r, 0)); + b = GetBDValueWithPrecMust(r, 0); } else if (RB_TYPE_P(r, T_RATIONAL)) { - GUARD_OBJ(b, GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig())); + b = GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig()); } else { - NULLABLE_BDVALUE b2; - GUARD_OBJ_OR_NIL(b2, GetBDValue(r)); + NULLABLE_BDVALUE b2 = GetBDValue(r); if (!b2.real_or_null) return DoSomeOne(self, r, '-'); b = bdvalue_nonnullable(b2); } @@ -1546,11 +1538,11 @@ BigDecimal_sub(VALUE self, VALUE r) mx = GetAddSubPrec(a.real, b.real); if (mx == (size_t)-1L) { /* a or b is inf */ - GUARD_OBJ(c, NewZeroWrapLimited(1, BASE_FIG)); + c = NewZeroWrapLimited(1, BASE_FIG); VpAddSub(c.real, a.real, b.real, -1); } else { - GUARD_OBJ(c, NewZeroWrapLimited(1, (mx + 1) * BASE_FIG)); + c = NewZeroWrapLimited(1, (mx + 1) * BASE_FIG); if (!mx) { VpSetInf(c.real, VpGetSign(a.real)); } @@ -1558,17 +1550,19 @@ BigDecimal_sub(VALUE self, VALUE r) VpAddSub(c.real, a.real, b.real, -1); } } + + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal); return CheckGetValue(c); } static VALUE BigDecimalCmp(VALUE self, VALUE r,char op) { - ENTER(5); SIGNED_VALUE e; - BDVALUE a; + BDVALUE a = GetBDValueMust(self); NULLABLE_BDVALUE b = { Qnil, NULL }; - GUARD_OBJ(a, GetBDValueMust(self)); + switch (TYPE(r)) { case T_DATA: if (!is_kind_of_BigDecimal(r)) break; @@ -1576,15 +1570,15 @@ BigDecimalCmp(VALUE self, VALUE r,char op) case T_FIXNUM: /* fall through */ case T_BIGNUM: - GUARD_OBJ_OR_NIL(b, GetBDValue(r)); + b = GetBDValue(r); break; case T_FLOAT: - GUARD_OBJ_OR_NIL(b, GetBDValueWithPrec(r, 0)); + b = GetBDValueWithPrec(r, 0); break; case T_RATIONAL: - GUARD_OBJ_OR_NIL(b, GetBDValueWithPrec(r, a.real->Prec*VpBaseFig())); + b = GetBDValueWithPrec(r, a.real->Prec*VpBaseFig()); break; default: @@ -1620,6 +1614,10 @@ BigDecimalCmp(VALUE self, VALUE r,char op) return rb_num_coerce_relop(self, r, f); } e = VpComp(a.real, b.real_or_null); + + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal_or_nil); + if (e == 999) return (op == '*') ? Qnil : Qfalse; switch (op) { @@ -1793,11 +1791,10 @@ BigDecimal_ge(VALUE self, VALUE r) static VALUE BigDecimal_neg(VALUE self) { - ENTER(5); - BDVALUE c, a; - GUARD_OBJ(a, GetBDValueMust(self)); - GUARD_OBJ(c, NewZeroWrapLimited(1, a.real->Prec * BASE_FIG)); + BDVALUE a = GetBDValueMust(self); + BDVALUE c = NewZeroWrapLimited(1, a.real->Prec * BASE_FIG); VpAsgn(c.real, a.real, -1); + RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); } @@ -1815,25 +1812,26 @@ BigDecimal_neg(VALUE self) static VALUE BigDecimal_mult(VALUE self, VALUE r) { - ENTER(5); BDVALUE a, b, c; - GUARD_OBJ(a, GetBDValueMust(self)); + a = GetBDValueMust(self); if (RB_TYPE_P(r, T_FLOAT)) { - GUARD_OBJ(b, GetBDValueWithPrecMust(r, 0)); + b = GetBDValueWithPrecMust(r, 0); } else if (RB_TYPE_P(r, T_RATIONAL)) { - GUARD_OBJ(b, GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig())); + b = GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig()); } else { - NULLABLE_BDVALUE b2; - GUARD_OBJ_OR_NIL(b2, GetBDValue(r)); + NULLABLE_BDVALUE b2 = GetBDValue(r); if (!b2.real_or_null) return DoSomeOne(self, r, '*'); b = bdvalue_nonnullable(b2); } - GUARD_OBJ(c, NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG)); + c = NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); VpMult(c.real, a.real, b.real); + + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal); return CheckGetValue(c); } @@ -2128,7 +2126,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) } // Needs to calculate 1 extra digit for rounding. - GUARD_OBJ(cv, NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(ix + 1))); + cv = NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(ix + 1)); GUARD_OBJ(res, NewZeroWrapNolimit(1, VPDIVD_REM_PREC(av.real, bv.real, cv.real) * BASE_FIG)); VpDivd(cv.real, res.real, av.real, bv.real); VpSetPrecLimit(pl); @@ -2218,7 +2216,6 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_add2(VALUE self, VALUE b, VALUE n) { - ENTER(2); BDVALUE cv; SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_add(self, b); @@ -2226,7 +2223,7 @@ BigDecimal_add2(VALUE self, VALUE b, VALUE n) size_t pl = VpSetPrecLimit(0); VALUE c = BigDecimal_add(self, b); VpSetPrecLimit(pl); - GUARD_OBJ(cv, GetBDValueMust(c)); + cv = GetBDValueMust(c); VpLeftRound(cv.real, VpGetRoundMode(), mx); return CheckGetValue(cv); } @@ -2248,7 +2245,6 @@ BigDecimal_add2(VALUE self, VALUE b, VALUE n) static VALUE BigDecimal_sub2(VALUE self, VALUE b, VALUE n) { - ENTER(2); BDVALUE cv; SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_sub(self, b); @@ -2256,7 +2252,7 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) size_t pl = VpSetPrecLimit(0); VALUE c = BigDecimal_sub(self, b); VpSetPrecLimit(pl); - GUARD_OBJ(cv, GetBDValueMust(c)); + cv = GetBDValueMust(c); VpLeftRound(cv.real, VpGetRoundMode(), mx); return CheckGetValue(cv); } @@ -2291,7 +2287,6 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) static VALUE BigDecimal_mult2(VALUE self, VALUE b, VALUE n) { - ENTER(2); BDVALUE cv; SIGNED_VALUE mx = check_int_precision(n); if (mx == 0) return BigDecimal_mult(self, b); @@ -2299,7 +2294,7 @@ BigDecimal_mult2(VALUE self, VALUE b, VALUE n) size_t pl = VpSetPrecLimit(0); VALUE c = BigDecimal_mult(self, b); VpSetPrecLimit(pl); - GUARD_OBJ(cv, GetBDValueMust(c)); + cv = GetBDValueMust(c); VpLeftRound(cv.real, VpGetRoundMode(), mx); return CheckGetValue(cv); } @@ -2319,15 +2314,11 @@ BigDecimal_mult2(VALUE self, VALUE b, VALUE n) static VALUE BigDecimal_abs(VALUE self) { - ENTER(5); - BDVALUE c, a; - size_t mx; - - GUARD_OBJ(a, GetBDValueMust(self)); - mx = a.real->Prec * BASE_FIG; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + BDVALUE a = GetBDValueMust(self); + BDVALUE c = NewZeroWrapLimited(1, a.real->Prec * BASE_FIG); VpAsgn(c.real, a.real, 1); VpChangeSign(c.real, 1); + RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); } @@ -2341,18 +2332,19 @@ BigDecimal_abs(VALUE self) static VALUE BigDecimal_sqrt(VALUE self, VALUE nFig) { - ENTER(5); BDVALUE c, a; size_t mx, n; - GUARD_OBJ(a, GetBDValueMust(self)); + a = GetBDValueMust(self); mx = a.real->Prec * (VpBaseFig() + 1); n = check_int_precision(nFig); n += VpDblFig() + VpBaseFig(); if (mx <= n) mx = n; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + c = NewZeroWrapLimited(1, mx); VpSqrt(c.real, a.real); + + RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); } @@ -2361,14 +2353,10 @@ BigDecimal_sqrt(VALUE self, VALUE nFig) static VALUE BigDecimal_fix(VALUE self) { - ENTER(5); - BDVALUE c, a; - size_t mx; - - GUARD_OBJ(a, GetBDValueMust(self)); - mx = (a.real->Prec + 1) * BASE_FIG; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + BDVALUE a = GetBDValueMust(self); + BDVALUE c = NewZeroWrapLimited(1, (a.real->Prec + 1) * BASE_FIG); VpActiveRound(c.real, a.real, VP_ROUND_DOWN, 0); /* 0: round off */ + RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); } @@ -2401,7 +2389,6 @@ BigDecimal_fix(VALUE self) static VALUE BigDecimal_round(int argc, VALUE *argv, VALUE self) { - ENTER(5); BDVALUE c, a; int iLoc = 0; VALUE vLoc; @@ -2439,11 +2426,14 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) } pl = VpSetPrecLimit(0); - GUARD_OBJ(a, GetBDValueMust(self)); + a = GetBDValueMust(self); mx = (a.real->Prec + 1) * BASE_FIG; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + c = NewZeroWrapLimited(1, mx); VpSetPrecLimit(pl); VpActiveRound(c.real, a.real, sw, iLoc); + + RB_GC_GUARD(a.bigdecimal); + if (round_to_int) { return BigDecimal_to_i(CheckGetValue(c)); } @@ -2453,7 +2443,6 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_truncate_floor_ceil(int argc, VALUE *argv, VALUE self, unsigned short rounding_mode) { - ENTER(5); BDVALUE c, a; int iLoc; VALUE vLoc; @@ -2466,11 +2455,14 @@ BigDecimal_truncate_floor_ceil(int argc, VALUE *argv, VALUE self, unsigned short iLoc = NUM2INT(vLoc); } - GUARD_OBJ(a, GetBDValueMust(self)); + a = GetBDValueMust(self); mx = (a.real->Prec + 1) * BASE_FIG; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + c = NewZeroWrapLimited(1, mx); VpSetPrecLimit(pl); VpActiveRound(c.real, a.real, rounding_mode, iLoc); + + RB_GC_GUARD(a.bigdecimal); + if (argc == 0) { return BigDecimal_to_i(CheckGetValue(c)); } @@ -2507,14 +2499,10 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_frac(VALUE self) { - ENTER(5); - BDVALUE c, a; - size_t mx; - - GUARD_OBJ(a, GetBDValueMust(self)); - mx = (a.real->Prec + 1) * BASE_FIG; - GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + BDVALUE a = GetBDValueMust(self); + BDVALUE c = NewZeroWrapLimited(1, (a.real->Prec + 1) * BASE_FIG); VpFrac(c.real, a.real); + RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); } @@ -2600,7 +2588,6 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_to_s(int argc, VALUE *argv, VALUE self) { - ENTER(5); int fmt = 0; /* 0: E format, 1: F format */ int fPlus = 0; /* 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ BDVALUE v; @@ -2611,7 +2598,7 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) SIGNED_VALUE m; VALUE f; - GUARD_OBJ(v, GetBDValueMust(self)); + v = GetBDValueMust(self); if (rb_scan_args(argc, argv, "01", &f) == 1) { if (RB_TYPE_P(f, T_STRING)) { @@ -2665,6 +2652,8 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) VpToString (v.real, psz, RSTRING_LEN(str), mc, fPlus); } rb_str_resize(str, strlen(psz)); + + RB_GC_GUARD(v.bigdecimal); return str; } @@ -2695,13 +2684,12 @@ BigDecimal_to_s(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_split(VALUE self) { - ENTER(5); BDVALUE v; VALUE obj,str; ssize_t e, s; char *psz1; - GUARD_OBJ(v, GetBDValueMust(self)); + v = GetBDValueMust(self); str = rb_str_new(0, VpNumOfChars(v.real, "E")); psz1 = RSTRING_PTR(str); VpSzMantissa(v.real, psz1, RSTRING_LEN(str)); @@ -2721,6 +2709,8 @@ BigDecimal_split(VALUE self) rb_str_resize(str, strlen(psz1)); rb_ary_push(obj, INT2FIX(10)); rb_ary_push(obj, SSIZET2NUM(e)); + + RB_GC_GUARD(v.bigdecimal); return obj; } @@ -2744,17 +2734,18 @@ BigDecimal_exponent(VALUE self) static VALUE BigDecimal_inspect(VALUE self) { - ENTER(5); BDVALUE v; volatile VALUE str; size_t nc; - GUARD_OBJ(v, GetBDValueMust(self)); + v = GetBDValueMust(self); nc = VpNumOfChars(v.real, "E"); str = rb_str_new(0, nc); VpToString(v.real, RSTRING_PTR(str), RSTRING_LEN(str), 0, 0); rb_str_resize(str, strlen(RSTRING_PTR(str))); + + RB_GC_GUARD(v.bigdecimal); return str; } From e7e36b567baec944eb7cab86a6fc8e41b6cc2491 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 29 Jul 2025 21:03:53 +0900 Subject: [PATCH 426/546] Remove ENTER and GUARD_OBJ macro (#391) Replace remaining ENTER/GUARD_OBJ in BigDecimal_DoDivmod and BigDecimal_div2 to standard RB_GC_GUARD --- ext/bigdecimal/bigdecimal.c | 65 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a1f357e3..74e1a1fc 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -98,15 +98,6 @@ bdvalue_nullable(BDVALUE v) return (NULLABLE_BDVALUE) { v.bigdecimal, v.real }; } -/* MACRO's to guard objects from GC by keeping them in stack */ -#ifdef RBIMPL_ATTR_MAYBE_UNUSED -#define ENTER(n) RBIMPL_ATTR_MAYBE_UNUSED() volatile VALUE vStack[n];int iStack=0 -#else -#define ENTER(n) volatile VALUE RB_UNUSED_VAR(vStack[n]);int iStack=0 -#endif -#define PUSH(x) (vStack[iStack++] = (VALUE)(x)) -#define GUARD_OBJ(p, y) ((p)=(y), PUSH((p).bigdecimal)) - #define BASE_FIG BIGDECIMAL_COMPONENT_FIGURES #define BASE BIGDECIMAL_BASE @@ -1910,12 +1901,11 @@ BigDecimal_quo(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod, bool truncate) { - ENTER(8); BDVALUE a, b, dv, md, res; ssize_t a_exponent, b_exponent; size_t mx, rx; - GUARD_OBJ(a, GetBDValueMust(self)); + a = GetBDValueMust(self); VALUE rr = r; if (is_kind_of_BigDecimal(rr)) { @@ -1935,10 +1925,13 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE return Qfalse; } - GUARD_OBJ(b, GetBDValueMust(rr)); + b = GetBDValueMust(rr); - if (VpIsNaN(a.real) || VpIsNaN(b.real)) goto NaN; - if (VpIsInf(a.real) && VpIsInf(b.real)) goto NaN; + if (VpIsNaN(a.real) || VpIsNaN(b.real) || (VpIsInf(a.real) && VpIsInf(b.real))) { + VALUE nan = BigDecimal_nan(); + *div = *mod = (NULLABLE_BDVALUE) { nan, DATA_PTR(nan) }; + goto Done; + } if (VpIsZero(b.real)) { rb_raise(rb_eZeroDivError, "divided by 0"); } @@ -1953,31 +1946,31 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE } VALUE nan = BigDecimal_nan(); *mod = (NULLABLE_BDVALUE) { nan, DATA_PTR(nan) }; - return Qtrue; + goto Done; } if (VpIsInf(b.real)) { VALUE zero = BigDecimal_positive_zero(); *div = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; *mod = bdvalue_nullable(a); - return Qtrue; + goto Done; } if (VpIsZero(a.real)) { VALUE zero = BigDecimal_positive_zero(); *div = *mod = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; - return Qtrue; + goto Done; } a_exponent = VpExponent10(a.real); b_exponent = VpExponent10(b.real); mx = a_exponent > b_exponent ? a_exponent - b_exponent + 1 : 1; - GUARD_OBJ(dv, NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(mx))); + dv = NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(mx)); /* res is reused for VpDivd remainder and VpMult result */ rx = VPDIVD_REM_PREC(a.real, b.real, dv.real); mx = VPMULT_RESULT_PREC(dv.real, b.real); - GUARD_OBJ(res, NewZeroWrapNolimit(1, Max(rx, mx) * BASE_FIG)); + res = NewZeroWrapNolimit(1, Max(rx, mx) * BASE_FIG); /* AddSub needs one more prec */ - GUARD_OBJ(md, NewZeroWrapLimited(1, (res.real->MaxPrec + 1) * BASE_FIG)); + md = NewZeroWrapLimited(1, (res.real->MaxPrec + 1) * BASE_FIG); VpDivd(dv.real, res.real, a.real, b.real); VpMidRound(dv.real, VP_ROUND_DOWN, 0); @@ -1986,25 +1979,26 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE if (!truncate && !VpIsZero(md.real) && (VpGetSign(a.real) * VpGetSign(b.real) < 0)) { /* result adjustment for negative case */ - BDVALUE dv2, md2; - GUARD_OBJ(dv2, NewZeroWrapLimited(1, (dv.real->MaxPrec + 1) * BASE_FIG)); - GUARD_OBJ(md2, NewZeroWrapLimited(1, (GetAddSubPrec(md.real, b.real) + 1) * BASE_FIG)); + BDVALUE dv2 = NewZeroWrapLimited(1, (dv.real->MaxPrec + 1) * BASE_FIG); + BDVALUE md2 = NewZeroWrapLimited(1, (GetAddSubPrec(md.real, b.real) + 1) * BASE_FIG); VpAddSub(dv2.real, dv.real, VpOne(), -1); VpAddSub(md2.real, md.real, b.real, 1); *div = bdvalue_nullable(dv2); *mod = bdvalue_nullable(md2); + RB_GC_GUARD(dv2.bigdecimal); + RB_GC_GUARD(md2.bigdecimal); } else { *div = bdvalue_nullable(dv); *mod = bdvalue_nullable(md); } - return Qtrue; - NaN: - { - VALUE nan = BigDecimal_nan(); - *div = *mod = (NULLABLE_BDVALUE) { nan, DATA_PTR(nan) }; - } +Done: + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal); + RB_GC_GUARD(dv.bigdecimal); + RB_GC_GUARD(md.bigdecimal); + RB_GC_GUARD(res.bigdecimal); return Qtrue; } @@ -2085,7 +2079,6 @@ BigDecimal_divmod(VALUE self, VALUE r) static inline VALUE BigDecimal_div2(VALUE self, VALUE b, VALUE n) { - ENTER(5); SIGNED_VALUE ix; BDVALUE av, bv, cv, res; size_t pl; @@ -2105,15 +2098,15 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) pl = VpSetPrecLimit(0); if (ix == 0) ix = pl; - GUARD_OBJ(av, GetBDValueMust(self)); + av = GetBDValueMust(self); if (RB_FLOAT_TYPE_P(b) && ix > BIGDECIMAL_DOUBLE_FIGURES) { /* TODO: I want to refactor this precision control for a float value later * by introducing an implicit conversion function instead of * GetBDValueWithPrecMust. */ - GUARD_OBJ(bv, GetBDValueWithPrecMust(b, BIGDECIMAL_DOUBLE_FIGURES)); + bv = GetBDValueWithPrecMust(b, BIGDECIMAL_DOUBLE_FIGURES); } else { - GUARD_OBJ(bv, GetBDValueWithPrecMust(b, ix)); + bv = GetBDValueWithPrecMust(b, ix); } if (ix == 0) { @@ -2127,7 +2120,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) // Needs to calculate 1 extra digit for rounding. cv = NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(ix + 1)); - GUARD_OBJ(res, NewZeroWrapNolimit(1, VPDIVD_REM_PREC(av.real, bv.real, cv.real) * BASE_FIG)); + res = NewZeroWrapNolimit(1, VPDIVD_REM_PREC(av.real, bv.real, cv.real) * BASE_FIG); VpDivd(cv.real, res.real, av.real, bv.real); VpSetPrecLimit(pl); if (!VpIsZero(res.real)) { @@ -2140,6 +2133,10 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) if (cv.real->frac[idx] == 0 || cv.real->frac[idx] == HALF_BASE) cv.real->frac[idx]++; } VpLeftRound(cv.real, VpGetRoundMode(), ix); + + RB_GC_GUARD(av.bigdecimal); + RB_GC_GUARD(bv.bigdecimal); + RB_GC_GUARD(res.bigdecimal); return CheckGetValue(cv); } From ec2748aede7595656154932552ef46de5dafe2c4 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 30 Jul 2025 00:51:19 +0900 Subject: [PATCH 427/546] Coerce to bigdecimal refactor (#392) * Remove unused args(digs, raise_exception) from rb_inum_convert_to_BigDecimal * Remove duplicated numeric to bigdecimal coerce codes Many T_FLOAT T_RATIONAL T_BIGNUM T_FIXNUM checks are almost duplicated. It essentially does the same thing. Small difference: bigdecimal.div(float, 18) will use prec=0 instead of prec=16 in float to bigdecimal conversion. BigDecimal(7).div(0.07, 100) #=> before: 0.999999999999999857e2, after: 0.1e3 --- ext/bigdecimal/bigdecimal.c | 182 +++++++---------------------- test/bigdecimal/test_bigdecimal.rb | 1 + 2 files changed, 46 insertions(+), 137 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 74e1a1fc..354956f4 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -392,24 +392,22 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); -static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); +static VALUE rb_inum_convert_to_BigDecimal(VALUE val); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); static VALUE rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static NULLABLE_BDVALUE -GetBDValueWithPrecInternal(VALUE v, long prec, int must) +GetBDValueWithPrecInternal(VALUE v, size_t prec, int must) { - const size_t digs = prec < 0 ? SIZE_MAX : (size_t)prec; - switch(TYPE(v)) { case T_FLOAT: - v = rb_float_convert_to_BigDecimal(v, digs, must); + v = rb_float_convert_to_BigDecimal(v, 0, true); break; case T_RATIONAL: - v = rb_rational_convert_to_BigDecimal(v, digs, must); + v = rb_rational_convert_to_BigDecimal(v, prec, true); break; case T_DATA: @@ -418,10 +416,9 @@ GetBDValueWithPrecInternal(VALUE v, long prec, int must) } break; - case T_FIXNUM: { - char szD[128]; - snprintf(szD, 128, "%ld", FIX2LONG(v)); - v = rb_cstr_convert_to_BigDecimal(szD, VpBaseFig() * 2 + 1, must); + case T_FIXNUM: + case T_BIGNUM: { + v = rb_inum_convert_to_BigDecimal(v); break; } @@ -433,13 +430,6 @@ GetBDValueWithPrecInternal(VALUE v, long prec, int must) } #endif /* ENABLE_NUMERIC_STRING */ - case T_BIGNUM: { - VALUE bg = rb_big2str(v, 10); - v = rb_cstr_convert_to_BigDecimal(RSTRING_PTR(bg), RSTRING_LEN(bg) + VpBaseFig() + 1, must); - RB_GC_GUARD(bg); - break; - } - default: goto SomeOneMayDoIt; } @@ -456,14 +446,14 @@ GetBDValueWithPrecInternal(VALUE v, long prec, int must) } static inline NULLABLE_BDVALUE -GetBDValueWithPrec(VALUE v, long prec) +GetBDValueWithPrec(VALUE v, size_t prec) { return GetBDValueWithPrecInternal(v, prec, 0); } static inline BDVALUE -GetBDValueWithPrecMust(VALUE v, long prec) +GetBDValueWithPrecMust(VALUE v, size_t prec) { return bdvalue_nonnullable(GetBDValueWithPrecInternal(v, prec, 1)); } @@ -472,19 +462,13 @@ GetBDValueWithPrecMust(VALUE v, long prec) static inline Real* GetSelfVpValue(VALUE self) { - return GetBDValueWithPrecMust(self, -1).real; -} - -static inline NULLABLE_BDVALUE -GetBDValue(VALUE v) -{ - return GetBDValueWithPrec(v, -1); + return GetBDValueWithPrecMust(self, 0).real; } static inline BDVALUE GetBDValueMust(VALUE v) { - return GetBDValueWithPrecMust(v, -1); + return GetBDValueWithPrecMust(v, 0); } /* call-seq: @@ -1397,19 +1381,8 @@ BigDecimal_to_r(VALUE self) static VALUE BigDecimal_coerce(VALUE self, VALUE other) { - BDVALUE b; - - if (RB_TYPE_P(other, T_FLOAT)) { - b = GetBDValueWithPrecMust(other, 0); - } - else if (RB_TYPE_P(other, T_RATIONAL)) { - Real* pv = DATA_PTR(self); - b = GetBDValueWithPrecMust(other, pv->Prec*VpBaseFig()); - } - else { - b = GetBDValueMust(other); - } - + Real* pv = DATA_PTR(self); + BDVALUE b = GetBDValueWithPrecMust(other, pv->Prec * BASE_FIG); return rb_assoc_new(CheckGetValue(b), self); } @@ -1450,20 +1423,13 @@ static VALUE BigDecimal_add(VALUE self, VALUE r) { BDVALUE a, b, c; + NULLABLE_BDVALUE b2; size_t mx; a = GetBDValueMust(self); - if (RB_TYPE_P(r, T_FLOAT)) { - b = GetBDValueWithPrecMust(r, 0); - } - else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig()); - } - else { - NULLABLE_BDVALUE b2 = GetBDValue(r); - if (!b2.real_or_null) return DoSomeOne(self, r, '+'); - b = bdvalue_nonnullable(b2); - } + b2 = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); + if (!b2.real_or_null) return DoSomeOne(self, r, '+'); + b = bdvalue_nonnullable(b2); if (VpIsNaN(b.real)) return b.bigdecimal; if (VpIsNaN(a.real)) return a.bigdecimal; @@ -1508,20 +1474,13 @@ static VALUE BigDecimal_sub(VALUE self, VALUE r) { BDVALUE a, b, c; + NULLABLE_BDVALUE b2; size_t mx; a = GetBDValueMust(self); - if (RB_TYPE_P(r, T_FLOAT)) { - b = GetBDValueWithPrecMust(r, 0); - } - else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig()); - } - else { - NULLABLE_BDVALUE b2 = GetBDValue(r); - if (!b2.real_or_null) return DoSomeOne(self, r, '-'); - b = bdvalue_nonnullable(b2); - } + b2 = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); + if (!b2.real_or_null) return DoSomeOne(self, r, '-'); + b = bdvalue_nonnullable(b2); if (VpIsNaN(b.real)) return b.bigdecimal; if (VpIsNaN(a.real)) return a.bigdecimal; @@ -1552,29 +1511,8 @@ BigDecimalCmp(VALUE self, VALUE r,char op) { SIGNED_VALUE e; BDVALUE a = GetBDValueMust(self); - NULLABLE_BDVALUE b = { Qnil, NULL }; + NULLABLE_BDVALUE b = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); - switch (TYPE(r)) { - case T_DATA: - if (!is_kind_of_BigDecimal(r)) break; - /* fall through */ - case T_FIXNUM: - /* fall through */ - case T_BIGNUM: - b = GetBDValue(r); - break; - - case T_FLOAT: - b = GetBDValueWithPrec(r, 0); - break; - - case T_RATIONAL: - b = GetBDValueWithPrec(r, a.real->Prec*VpBaseFig()); - break; - - default: - break; - } if (b.real_or_null == NULL) { ID f = 0; @@ -1804,19 +1742,12 @@ static VALUE BigDecimal_mult(VALUE self, VALUE r) { BDVALUE a, b, c; + NULLABLE_BDVALUE b2; a = GetBDValueMust(self); - if (RB_TYPE_P(r, T_FLOAT)) { - b = GetBDValueWithPrecMust(r, 0); - } - else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetBDValueWithPrecMust(r, a.real->Prec*VpBaseFig()); - } - else { - NULLABLE_BDVALUE b2 = GetBDValue(r); - if (!b2.real_or_null) return DoSomeOne(self, r, '*'); - b = bdvalue_nonnullable(b2); - } + b2 = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); + if (!b2.real_or_null) return DoSomeOne(self, r, '*'); + b = bdvalue_nonnullable(b2); c = NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); VpMult(c.real, a.real, b.real); @@ -1902,30 +1833,15 @@ static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod, bool truncate) { BDVALUE a, b, dv, md, res; + NULLABLE_BDVALUE b2; ssize_t a_exponent, b_exponent; size_t mx, rx; a = GetBDValueMust(self); - VALUE rr = r; - if (is_kind_of_BigDecimal(rr)) { - /* do nothing */ - } - else if (RB_INTEGER_TYPE_P(r)) { - rr = rb_inum_convert_to_BigDecimal(r, 0, true); - } - else if (RB_TYPE_P(r, T_FLOAT)) { - rr = rb_float_convert_to_BigDecimal(r, 0, true); - } - else if (RB_TYPE_P(r, T_RATIONAL)) { - rr = rb_rational_convert_to_BigDecimal(r, a.real->Prec*BASE_FIG, true); - } - - if (!is_kind_of_BigDecimal(rr)) { - return Qfalse; - } - - b = GetBDValueMust(rr); + b2 = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); + if (!b2.real_or_null) return Qfalse; + b = bdvalue_nonnullable(b2); if (VpIsNaN(a.real) || VpIsNaN(b.real) || (VpIsInf(a.real) && VpIsInf(b.real))) { VALUE nan = BigDecimal_nan(); @@ -2099,15 +2015,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) if (ix == 0) ix = pl; av = GetBDValueMust(self); - if (RB_FLOAT_TYPE_P(b) && ix > BIGDECIMAL_DOUBLE_FIGURES) { - /* TODO: I want to refactor this precision control for a float value later - * by introducing an implicit conversion function instead of - * GetBDValueWithPrecMust. */ - bv = GetBDValueWithPrecMust(b, BIGDECIMAL_DOUBLE_FIGURES); - } - else { - bv = GetBDValueWithPrecMust(b, ix); - } + bv = GetBDValueWithPrecMust(b, ix); if (ix == 0) { ssize_t a_prec, b_prec; @@ -2813,7 +2721,7 @@ check_exception(VALUE bd) } static VALUE -rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int raise_exception) +rb_uint64_convert_to_BigDecimal(uint64_t uval) { VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); @@ -2865,10 +2773,10 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r } static VALUE -rb_int64_convert_to_BigDecimal(int64_t ival, size_t digs, int raise_exception) +rb_int64_convert_to_BigDecimal(int64_t ival) { const uint64_t uval = (ival < 0) ? (((uint64_t)-(ival+1))+1) : (uint64_t)ival; - VALUE bd = rb_uint64_convert_to_BigDecimal(uval, digs, raise_exception); + VALUE bd = rb_uint64_convert_to_BigDecimal(uval); if (ival < 0) { Real *vp; TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); @@ -2878,7 +2786,7 @@ rb_int64_convert_to_BigDecimal(int64_t ival, size_t digs, int raise_exception) } static VALUE -rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +rb_big_convert_to_BigDecimal(VALUE val) { assert(RB_TYPE_P(val, T_BIGNUM)); @@ -2890,19 +2798,19 @@ rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_ex } if (size <= sizeof(long)) { if (sign < 0) { - return rb_int64_convert_to_BigDecimal(NUM2LONG(val), digs, raise_exception); + return rb_int64_convert_to_BigDecimal(NUM2LONG(val)); } else { - return rb_uint64_convert_to_BigDecimal(NUM2ULONG(val), digs, raise_exception); + return rb_uint64_convert_to_BigDecimal(NUM2ULONG(val)); } } #if defined(SIZEOF_LONG_LONG) && SIZEOF_LONG < SIZEOF_LONG_LONG else if (size <= sizeof(LONG_LONG)) { if (sign < 0) { - return rb_int64_convert_to_BigDecimal(NUM2LL(val), digs, raise_exception); + return rb_int64_convert_to_BigDecimal(NUM2LL(val)); } else { - return rb_uint64_convert_to_BigDecimal(NUM2ULL(val), digs, raise_exception); + return rb_uint64_convert_to_BigDecimal(NUM2ULL(val)); } } #endif @@ -2921,14 +2829,14 @@ rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_ex } static VALUE -rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +rb_inum_convert_to_BigDecimal(VALUE val) { assert(RB_INTEGER_TYPE_P(val)); if (FIXNUM_P(val)) { - return rb_int64_convert_to_BigDecimal(FIX2LONG(val), digs, raise_exception); + return rb_int64_convert_to_BigDecimal(FIX2LONG(val)); } else { - return rb_big_convert_to_BigDecimal(val, digs, raise_exception); + return rb_big_convert_to_BigDecimal(val); } } @@ -3078,7 +2986,7 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) exp = -exp; } - VALUE bd = rb_inum_convert_to_BigDecimal(inum, SIZE_MAX, raise_exception); + VALUE bd = rb_inum_convert_to_BigDecimal(inum); Real *vp; TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); assert(vp->Prec == prec); @@ -3101,7 +3009,7 @@ rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) CLASS_OF(val)); } - VALUE num = rb_inum_convert_to_BigDecimal(rb_rational_num(val), 0, raise_exception); + VALUE num = rb_inum_convert_to_BigDecimal(rb_rational_num(val)); VALUE d = BigDecimal_div2(num, rb_rational_den(val), SIZET2NUM(digs)); return d; } @@ -3159,7 +3067,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) return check_exception(copy); } else if (RB_INTEGER_TYPE_P(val)) { - return rb_inum_convert_to_BigDecimal(val, digs, raise_exception); + return rb_inum_convert_to_BigDecimal(val); } else if (RB_FLOAT_TYPE_P(val)) { return rb_float_convert_to_BigDecimal(val, digs, raise_exception); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 59a39c6d..d19a5bc3 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1175,6 +1175,7 @@ def test_div_rounding_with_small_remainder def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) + assert_equal(BigDecimal(100), BigDecimal(7).div(0.07, 100)) end def test_div_with_rational From ed9e91a46c85564945fb04636fa3ef33471e2110 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 30 Jul 2025 04:03:57 +0900 Subject: [PATCH 428/546] Coerce rational with the given prec in exp, log and power calculation (#393) --- lib/bigdecimal.rb | 36 ++++++++++++++++-------------- test/bigdecimal/test_bigdecimal.rb | 18 +++++++++++++++ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 40d9cffc..f11c6217 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -19,18 +19,15 @@ class BigDecimal # Related: BigDecimal#power. # def **(y) - unless y.is_a?(BigDecimal) - case y - when Integer, Float, Rational - y = BigDecimal(y, 0) - when nil - raise TypeError, 'wrong argument type NilClass' - else - x, y = y.coerce(self) - return x**y - end + case y + when BigDecimal, Integer, Float, Rational + power(y) + when nil + raise TypeError, 'wrong argument type NilClass' + else + x, y = y.coerce(self) + x**y end - power(y) end # call-seq: @@ -44,7 +41,7 @@ def **(y) def power(y, prec = nil) BigMath._validate_prec(prec, :power) if prec x = self - y = BigMath._coerce_to_bigdecimal(y, :power) + y = BigMath._coerce_to_bigdecimal(y, prec || n_significant_digits, :power) return BigMath._nan_computation_result if x.nan? || y.nan? return BigDecimal(1) if y.zero? @@ -145,12 +142,17 @@ def power(y, prec = nil) # Core BigMath methods for BigDecimal (log, exp) are defined here. # Other methods (sin, cos, atan) are defined in 'bigdecimal/math.rb'. module BigMath - def self._coerce_to_bigdecimal(x, method_name, complex_domain_error = false) # :nodoc: + + # Coerce x to BigDecimal with the specified precision. + # TODO: some methods (example: BigMath.exp) require more precision than specified to coerce. + def self._coerce_to_bigdecimal(x, prec, method_name, complex_domain_error = false) # :nodoc: case x when BigDecimal return x - when Integer, Float, Rational - return BigDecimal(x, 0) + when Integer, Float + return BigDecimal(x) + when Rational + return BigDecimal(x, [prec, 2 * BigDecimal.double_fig].max) when Complex if complex_domain_error raise Math::DomainError, "Complex argument for BigMath.#{method_name}" @@ -192,7 +194,7 @@ def self._nan_computation_result # :nodoc: # def self.log(x, prec) _validate_prec(prec, :log) - x = _coerce_to_bigdecimal(x, :log, true) + x = _coerce_to_bigdecimal(x, prec, :log, true) return _nan_computation_result if x.nan? raise Math::DomainError, 'Zero or negative argument for log' if x <= 0 return _infinity_computation_result if x.infinite? @@ -258,7 +260,7 @@ def self.log(x, prec) # def self.exp(x, prec) _validate_prec(prec, :exp) - x = _coerce_to_bigdecimal(x, :exp) + x = _coerce_to_bigdecimal(x, prec, :exp) return _nan_computation_result if x.nan? return x.positive? ? _infinity_computation_result : BigDecimal(0) if x.infinite? return BigDecimal(1) if x.zero? diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index d19a5bc3..46579514 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1895,6 +1895,19 @@ def test_power_with_prec assert_equal(BigDecimal('0.5394221232e-7'), BigDecimal('0.12345').power(8, 10)) end + def test_power_with_rational + x1 = BigDecimal(2) + x2 = BigDecimal('1.' + '1' * 100) + y = 3 / 7r + z1 = x1.power(BigDecimal(y, 100), 100) + z2 = x2.power(BigDecimal(y, 100), 100) + assert_in_epsilon(z1, x1.power(y, 100), 1e-99) + assert_in_epsilon(z1, x1.power(y), 1e-30) + assert_in_epsilon(z1, x1 ** y, 1e-30) + assert_in_epsilon(z2, x2.power(y), 1e-99) + assert_in_epsilon(z2, x2 ** y, 1e-99) + end + def test_power_precision x = BigDecimal("1.41421356237309504880168872420969807856967187537695") y = BigDecimal("3.14159265358979323846264338327950288419716939937511") @@ -2153,6 +2166,7 @@ def test_BigMath_exp_with_rational assert_in_epsilon(Math.exp(40), BigMath.exp(Rational(80,2), prec)) assert_in_epsilon(Math.exp(-20), BigMath.exp(Rational(-40,2), prec)) assert_in_epsilon(Math.exp(-40), BigMath.exp(Rational(-80,2), prec)) + assert_in_epsilon(BigMath.exp(BigDecimal(3 / 7r, 100), 100), BigMath.exp(3 / 7r, 100), 1e-99) end def test_BigMath_exp_under_gc_stress @@ -2285,6 +2299,10 @@ def test_BigMath_log_with_reciprocal_of_42 assert_in_delta(Math.log(1e-42), BigMath.log(BigDecimal("1e-42"), 20)) end + def test_BigMath_log_with_rational + assert_in_epsilon(BigMath.log(BigDecimal(3 / 7r, 100), 100), BigMath.log(3 / 7r, 100), 1e-99) + end + def test_BigMath_log_under_gc_stress paths = $LOAD_PATH.map{|path| "-I#{path}" } assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, [], []) From d458433935f94c94962797b07a98bdfbc250633e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 30 Jul 2025 04:16:51 +0900 Subject: [PATCH 429/546] Unify coerce prec calculation (#394) Use div's precision calculation because add/sub/mult's calculation coerce Rational with low precision. `a.add(rational, prec)` will now coerce Rational with `Max(prec, 2*BIGDECIMAL_DOUBLE_FIGURES)` precision. Minimum precision(2*BIGDECIMAL_DOUBLE_FIGURES) is the minimum precision of BigDecimal(rational, 0) --- ext/bigdecimal/bigdecimal.c | 122 +++++++++++++++++------------ test/bigdecimal/test_bigdecimal.rb | 11 +++ 2 files changed, 81 insertions(+), 52 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 354956f4..4dff6308 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -284,6 +284,9 @@ static VALUE BigDecimal_positive_infinity(void); static VALUE BigDecimal_negative_infinity(void); static VALUE BigDecimal_positive_zero(void); static VALUE BigDecimal_negative_zero(void); +static VALUE BigDecimal_add_with_coerce(VALUE self, VALUE r, size_t coerce_prec); +static VALUE BigDecimal_sub_with_coerce(VALUE self, VALUE r, size_t coerce_prec); +static VALUE BigDecimal_mult_with_coerce(VALUE self, VALUE r, size_t coerce_prec); static void BigDecimal_delete(void *pv) @@ -1364,6 +1367,14 @@ BigDecimal_to_r(VALUE self) } } +static size_t +GetCoercePrec(Real *a, size_t prec) +{ + if (prec == 0) prec = a->Prec * BASE_FIG; + if (prec < 2 * BIGDECIMAL_DOUBLE_FIGURES) prec = 2 * BIGDECIMAL_DOUBLE_FIGURES; + return prec; +} + /* The coerce method provides support for Ruby type coercion. It is not * enabled by default. * @@ -1382,7 +1393,7 @@ static VALUE BigDecimal_coerce(VALUE self, VALUE other) { Real* pv = DATA_PTR(self); - BDVALUE b = GetBDValueWithPrecMust(other, pv->Prec * BASE_FIG); + BDVALUE b = GetBDValueWithPrecMust(other, GetCoercePrec(pv, 0)); return rb_assoc_new(CheckGetValue(b), self); } @@ -1403,6 +1414,15 @@ BigDecimal_uplus(VALUE self) return self; } +static bool +is_coerceable_to_BigDecimal(VALUE r) +{ + return is_kind_of_BigDecimal(r) || + RB_INTEGER_TYPE_P(r) || + RB_TYPE_P(r, T_FLOAT) || + RB_TYPE_P(r, T_RATIONAL); +} + /* * call-seq: * self + value -> bigdecimal @@ -1421,15 +1441,19 @@ BigDecimal_uplus(VALUE self) static VALUE BigDecimal_add(VALUE self, VALUE r) +{ + if (!is_coerceable_to_BigDecimal(r)) return DoSomeOne(self, r, '+'); + return BigDecimal_add_with_coerce(self, r, 0); +} + +static VALUE +BigDecimal_add_with_coerce(VALUE self, VALUE r, size_t coerce_prec) { BDVALUE a, b, c; - NULLABLE_BDVALUE b2; size_t mx; a = GetBDValueMust(self); - b2 = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); - if (!b2.real_or_null) return DoSomeOne(self, r, '+'); - b = bdvalue_nonnullable(b2); + b = GetBDValueWithPrecMust(r, GetCoercePrec(a.real, coerce_prec)); if (VpIsNaN(b.real)) return b.bigdecimal; if (VpIsNaN(a.real)) return a.bigdecimal; @@ -1472,15 +1496,19 @@ BigDecimal_add(VALUE self, VALUE r) */ static VALUE BigDecimal_sub(VALUE self, VALUE r) +{ + if (!is_coerceable_to_BigDecimal(r)) return DoSomeOne(self, r, '-'); + return BigDecimal_sub_with_coerce(self, r, 0); +} + +static VALUE +BigDecimal_sub_with_coerce(VALUE self, VALUE r, size_t coerce_prec) { BDVALUE a, b, c; - NULLABLE_BDVALUE b2; size_t mx; a = GetBDValueMust(self); - b2 = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); - if (!b2.real_or_null) return DoSomeOne(self, r, '-'); - b = bdvalue_nonnullable(b2); + b = GetBDValueWithPrecMust(r, GetCoercePrec(a.real, coerce_prec)); if (VpIsNaN(b.real)) return b.bigdecimal; if (VpIsNaN(a.real)) return a.bigdecimal; @@ -1511,7 +1539,7 @@ BigDecimalCmp(VALUE self, VALUE r,char op) { SIGNED_VALUE e; BDVALUE a = GetBDValueMust(self); - NULLABLE_BDVALUE b = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); + NULLABLE_BDVALUE b = GetBDValueWithPrec(r, GetCoercePrec(a.real, 0)); if (b.real_or_null == NULL) { ID f = 0; @@ -1737,17 +1765,20 @@ BigDecimal_neg(VALUE self) * * See BigDecimal#mult. */ - static VALUE BigDecimal_mult(VALUE self, VALUE r) +{ + if (!is_coerceable_to_BigDecimal(r)) return DoSomeOne(self, r, '*'); + return BigDecimal_mult_with_coerce(self, r, 0); +} + +static VALUE +BigDecimal_mult_with_coerce(VALUE self, VALUE r, size_t coerce_prec) { BDVALUE a, b, c; - NULLABLE_BDVALUE b2; a = GetBDValueMust(self); - b2 = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); - if (!b2.real_or_null) return DoSomeOne(self, r, '*'); - b = bdvalue_nonnullable(b2); + b = GetBDValueWithPrecMust(r, GetCoercePrec(a.real, coerce_prec)); c = NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); VpMult(c.real, a.real, b.real); @@ -1774,14 +1805,7 @@ static VALUE BigDecimal_div(VALUE self, VALUE r) /* For c = self/r: with round operation */ { - if ( - !is_kind_of_BigDecimal(r) && - !RB_INTEGER_TYPE_P(r) && - !RB_TYPE_P(r, T_FLOAT) && - !RB_TYPE_P(r, T_RATIONAL) - ) { - return DoSomeOne(self, r, '/'); - } + if (!is_coerceable_to_BigDecimal(r)) return DoSomeOne(self, r, '/'); return BigDecimal_div2(self, r, INT2FIX(0)); } @@ -1839,7 +1863,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE a = GetBDValueMust(self); - b2 = GetBDValueWithPrec(r, a.real->Prec * BASE_FIG); + b2 = GetBDValueWithPrec(r, GetCoercePrec(a.real, 0)); if (!b2.real_or_null) return Qfalse; b = bdvalue_nonnullable(b2); @@ -2015,7 +2039,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) if (ix == 0) ix = pl; av = GetBDValueMust(self); - bv = GetBDValueWithPrecMust(b, ix); + bv = GetBDValueWithPrecMust(b, GetCoercePrec(av.real, ix)); if (ix == 0) { ssize_t a_prec, b_prec; @@ -2123,15 +2147,13 @@ BigDecimal_add2(VALUE self, VALUE b, VALUE n) { BDVALUE cv; SIGNED_VALUE mx = check_int_precision(n); - if (mx == 0) return BigDecimal_add(self, b); - else { - size_t pl = VpSetPrecLimit(0); - VALUE c = BigDecimal_add(self, b); - VpSetPrecLimit(pl); - cv = GetBDValueMust(c); - VpLeftRound(cv.real, VpGetRoundMode(), mx); - return CheckGetValue(cv); - } + if (mx == 0) return BigDecimal_add_with_coerce(self, b, 0); + size_t pl = VpSetPrecLimit(0); + VALUE c = BigDecimal_add_with_coerce(self, b, mx); + VpSetPrecLimit(pl); + cv = GetBDValueMust(c); + VpLeftRound(cv.real, VpGetRoundMode(), mx); + return CheckGetValue(cv); } /* call-seq: @@ -2152,15 +2174,13 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) { BDVALUE cv; SIGNED_VALUE mx = check_int_precision(n); - if (mx == 0) return BigDecimal_sub(self, b); - else { - size_t pl = VpSetPrecLimit(0); - VALUE c = BigDecimal_sub(self, b); - VpSetPrecLimit(pl); - cv = GetBDValueMust(c); - VpLeftRound(cv.real, VpGetRoundMode(), mx); - return CheckGetValue(cv); - } + if (mx == 0) return BigDecimal_sub_with_coerce(self, b, 0); + size_t pl = VpSetPrecLimit(0); + VALUE c = BigDecimal_sub_with_coerce(self, b, mx); + VpSetPrecLimit(pl); + cv = GetBDValueMust(c); + VpLeftRound(cv.real, VpGetRoundMode(), mx); + return CheckGetValue(cv); } /* @@ -2194,15 +2214,13 @@ BigDecimal_mult2(VALUE self, VALUE b, VALUE n) { BDVALUE cv; SIGNED_VALUE mx = check_int_precision(n); - if (mx == 0) return BigDecimal_mult(self, b); - else { - size_t pl = VpSetPrecLimit(0); - VALUE c = BigDecimal_mult(self, b); - VpSetPrecLimit(pl); - cv = GetBDValueMust(c); - VpLeftRound(cv.real, VpGetRoundMode(), mx); - return CheckGetValue(cv); - } + if (mx == 0) return BigDecimal_mult_with_coerce(self, b, 0); + size_t pl = VpSetPrecLimit(0); + VALUE c = BigDecimal_mult_with_coerce(self, b, mx); + VpSetPrecLimit(pl); + cv = GetBDValueMust(c); + VpLeftRound(cv.real, VpGetRoundMode(), mx); + return CheckGetValue(cv); } /* diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 46579514..9ced8202 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -979,6 +979,17 @@ def test_coerce end end + def test_coerce_rational + assert_in_epsilon(3.0 / 7.0, BigDecimal(1) / (7/3r), 1e-15) + assert_in_epsilon(10.0 / 3.0, BigDecimal(1) + (7/3r), 1e-15) + assert_in_epsilon(2.0 / 3.0, BigDecimal(3) - (7/3r), 1e-15) + assert_in_epsilon(14.0 / 3.0, BigDecimal(2) * (7/3r), 1e-15) + assert_in_epsilon(BigDecimal(3).div(7, 100), BigDecimal(1).div(7/3r, 100), 1e-99) + assert_in_epsilon(BigDecimal(10).div(3, 100), BigDecimal(1).add(7/3r, 100), 1e-99) + assert_in_epsilon(BigDecimal(2).div(3, 100), BigDecimal(3).sub(7/3r, 100), 1e-99) + assert_in_epsilon(BigDecimal(14).div(3, 100), BigDecimal(2).mult(7/3r, 100), 1e-99) + end + def test_uplus x = BigDecimal("1") assert_equal(x, x.send(:+@)) From fd375a71cf100e0ae04f34f91b2c3bcf74b3f2ef Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 30 Jul 2025 04:34:03 +0900 Subject: [PATCH 430/546] Use bool instead of Qtrue/Qfalse for normal c boolish value (#395) BigDecimal_DoDivmod, a normal c function that returns bool result should use true/false instead of Qtrue(20)/Qfalse(0) --- ext/bigdecimal/bigdecimal.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4dff6308..c7b2b76c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1788,7 +1788,7 @@ BigDecimal_mult_with_coerce(VALUE self, VALUE r, size_t coerce_prec) return CheckGetValue(c); } -static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod, bool truncate); +static bool BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod, bool truncate); /* call-seq: * a / b -> bigdecimal @@ -1853,7 +1853,7 @@ BigDecimal_quo(int argc, VALUE *argv, VALUE self) * div = (a.to_f/b).floor * In truncate mode, use truncate instead of floor. */ -static VALUE +static bool BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod, bool truncate) { BDVALUE a, b, dv, md, res; @@ -1864,7 +1864,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE a = GetBDValueMust(self); b2 = GetBDValueWithPrec(r, GetCoercePrec(a.real, 0)); - if (!b2.real_or_null) return Qfalse; + if (!b2.real_or_null) return false; b = bdvalue_nonnullable(b2); if (VpIsNaN(a.real) || VpIsNaN(b.real) || (VpIsInf(a.real) && VpIsInf(b.real))) { @@ -1939,7 +1939,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE RB_GC_GUARD(dv.bigdecimal); RB_GC_GUARD(md.bigdecimal); RB_GC_GUARD(res.bigdecimal); - return Qtrue; + return true; } /* call-seq: From 46fd1bdf9d91c633b18fc900d17abbc7d2aa55df Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 9 Aug 2025 00:40:31 +0900 Subject: [PATCH 431/546] Fix adjusting x to 0.3..3 in log calculation (#397) --- lib/bigdecimal.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index f11c6217..3caf3b03 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -204,9 +204,9 @@ def self.log(x, prec) log10 = log(BigDecimal(10), prec) exponent = x.exponent x = x * BigDecimal("1e#{-x.exponent}") - if x > 3 - x /= 10 - exponent += 1 + if x < 0.3 + x *= 10 + exponent -= 1 end return log10 * exponent + log(x, prec) end From beb3e1e641adff7310f85d087f71c50c527f3d7b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 10 Aug 2025 14:26:31 +0900 Subject: [PATCH 432/546] missing.h cleanup (#396) * Remove unused ifndef branch and definition from missing.h `# define false ((_Bool)-1)` wrongly defines false as truthy value. `finite(double){return something(n);}` results in compile error. These ifdef branch were never used. isfinite, labs, llabs, vabs are not used anymore. These are never used. * Remove rb_rational_num/den check HAVE_RB_RATIONAL_NUM/DEN checks is to support ruby < 2.2.0. Currently, BigDecimal supports ruby >= 2.5.0 * Remove conditional include of stdlib.h and math.h stdlib.h and math.h are explicitly required in bigdecimal.c --- ext/bigdecimal/extconf.rb | 7 --- ext/bigdecimal/missing.h | 94 +-------------------------------------- 2 files changed, 1 insertion(+), 100 deletions(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index a4825103..0fcc7c90 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -36,17 +36,10 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_func("_BitScanReverse64", "intrin.h") end -have_func("labs", "stdlib.h") -have_func("llabs", "stdlib.h") -have_func("finite", "math.h") -have_func("isfinite", "math.h") - have_header("ruby/atomic.h") have_header("ruby/internal/has/builtin.h") have_header("ruby/internal/static_assert.h") -have_func("rb_rational_num", "ruby.h") -have_func("rb_rational_den", "ruby.h") have_func("rb_complex_real", "ruby.h") have_func("rb_complex_imag", "ruby.h") have_func("rb_opts_exception_p", "ruby.h") diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index 325554b5..e8a8cabf 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -8,14 +8,6 @@ extern "C" { #endif #endif -#ifdef HAVE_STDLIB_H -# include -#endif - -#ifdef HAVE_MATH_H -# include -#endif - #ifndef RB_UNUSED_VAR # if defined(_MSC_VER) && _MSC_VER >= 1911 # define RB_UNUSED_VAR(x) x [[maybe_unused]] @@ -55,97 +47,13 @@ extern "C" { /* bool */ -#if defined(__bool_true_false_are_defined) -# /* Take that. */ - -#elif defined(HAVE_STDBOOL_H) +#ifndef __bool_true_false_are_defined # include - -#else -typedef unsigned char _Bool; -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)-1) -# define __bool_true_false_are_defined -#endif - -/* abs */ - -#ifndef HAVE_LABS -static inline long -labs(long const x) -{ - if (x < 0) return -x; - return x; -} -#endif - -#ifndef HAVE_LLABS -static inline LONG_LONG -llabs(LONG_LONG const x) -{ - if (x < 0) return -x; - return x; -} -#endif - -#ifdef vabs -# undef vabs -#endif -#if SIZEOF_VALUE <= SIZEOF_INT -# define vabs abs -#elif SIZEOF_VALUE <= SIZEOF_LONG -# define vabs labs -#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG -# define vabs llabs -#endif - -/* finite */ - -#ifndef HAVE_FINITE -static int -finite(double) -{ - return !isnan(n) && !isinf(n); -} -#endif - -#ifndef isfinite -# ifndef HAVE_ISFINITE -# define HAVE_ISFINITE 1 -# define isfinite(x) finite(x) -# endif #endif /* dtoa */ char *BigDecimal_dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve); -/* rational */ - -#ifndef HAVE_RB_RATIONAL_NUM -static inline VALUE -rb_rational_num(VALUE rat) -{ -#ifdef RRATIONAL - return RRATIONAL(rat)->num; -#else - return rb_funcall(rat, rb_intern("numerator"), 0); -#endif -} -#endif - -#ifndef HAVE_RB_RATIONAL_DEN -static inline VALUE -rb_rational_den(VALUE rat) -{ -#ifdef RRATIONAL - return RRATIONAL(rat)->den; -#else - return rb_funcall(rat, rb_intern("denominator"), 0); -#endif -} -#endif - /* complex */ #ifndef HAVE_RB_COMPLEX_REAL From 48e32bc8e5bcee7b38caad60fba51cf4593475a3 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 14 Aug 2025 21:57:22 +0900 Subject: [PATCH 433/546] Hide internal method of BigMath into BigDecimal::Internal (#400) Some internal methods (ex: BigMath._validate_prec) are also used from BigDecimal, so we can't make it private. Moves them to an internal module BigDecimal::Internal. --- lib/bigdecimal.rb | 108 +++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 3caf3b03..9460e269 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -5,6 +5,41 @@ end class BigDecimal + module Internal # :nodoc: + + # Coerce x to BigDecimal with the specified precision. + # TODO: some methods (example: BigMath.exp) require more precision than specified to coerce. + def self.coerce_to_bigdecimal(x, prec, method_name) # :nodoc: + case x + when BigDecimal + return x + when Integer, Float + return BigDecimal(x) + when Rational + return BigDecimal(x, [prec, 2 * BigDecimal.double_fig].max) + end + raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal" + end + + def self.validate_prec(prec, method_name) # :nodoc: + raise ArgumentError, 'precision must be an Integer' unless Integer === prec + raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0 + end + + def self.infinity_computation_result # :nodoc: + if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_INFINITY) + raise FloatDomainError, "Computation results in 'Infinity'" + end + BigDecimal::INFINITY + end + + def self.nan_computation_result # :nodoc: + if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_NaN) + raise FloatDomainError, "Computation results to 'NaN'" + end + BigDecimal::NAN + end + end # call-seq: # self ** other -> bigdecimal @@ -39,11 +74,11 @@ def **(y) # Also available as the operator **. # def power(y, prec = nil) - BigMath._validate_prec(prec, :power) if prec + Internal.validate_prec(prec, :power) if prec x = self - y = BigMath._coerce_to_bigdecimal(y, prec || n_significant_digits, :power) + y = Internal.coerce_to_bigdecimal(y, prec || n_significant_digits, :power) - return BigMath._nan_computation_result if x.nan? || y.nan? + return Internal.nan_computation_result if x.nan? || y.nan? return BigDecimal(1) if y.zero? if y.infinite? @@ -52,11 +87,11 @@ def power(y, prec = nil) return BigDecimal(0) if x > -1 && y.positive? raise Math::DomainError, 'Result undefined for negative base raised to infinite power' elsif x < 1 - return y.positive? ? BigDecimal(0) : BigMath._infinity_computation_result + return y.positive? ? BigDecimal(0) : BigDecimal::Internal.infinity_computation_result elsif x == 1 return BigDecimal(1) else - return y.positive? ? BigMath._infinity_computation_result : BigDecimal(0) + return y.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) end end @@ -70,9 +105,9 @@ def power(y, prec = nil) return BigDecimal(1) if y.zero? return BigDecimal(0) if y > 0 if y.frac.zero? && y % 2 == 1 && x.sign == -1 - return -BigMath._infinity_computation_result + return -BigDecimal::Internal.infinity_computation_result else - return BigMath._infinity_computation_result + return BigDecimal::Internal.infinity_computation_result end elsif x < 0 if y.frac.zero? @@ -105,7 +140,7 @@ def power(y, prec = nil) xn *= xn # Detect overflow/underflow before consuming infinite memory if (xn.exponent.abs - 1) * int_part / n >= 0x7FFFFFFFFFFFFFFF - return ((xn.exponent > 0) ^ neg ? BigMath._infinity_computation_result : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1) + return ((xn.exponent > 0) ^ neg ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1) end end return neg ? BigDecimal(1) / ans : ans @@ -116,7 +151,7 @@ def power(y, prec = nil) if y < 0 inv = x.power(-y, prec) return BigDecimal(0) if inv.infinite? - return BigMath._infinity_computation_result if inv.zero? + return BigDecimal::Internal.infinity_computation_result if inv.zero? return BigDecimal(1).div(inv, prec) end @@ -143,43 +178,6 @@ def power(y, prec = nil) # Other methods (sin, cos, atan) are defined in 'bigdecimal/math.rb'. module BigMath - # Coerce x to BigDecimal with the specified precision. - # TODO: some methods (example: BigMath.exp) require more precision than specified to coerce. - def self._coerce_to_bigdecimal(x, prec, method_name, complex_domain_error = false) # :nodoc: - case x - when BigDecimal - return x - when Integer, Float - return BigDecimal(x) - when Rational - return BigDecimal(x, [prec, 2 * BigDecimal.double_fig].max) - when Complex - if complex_domain_error - raise Math::DomainError, "Complex argument for BigMath.#{method_name}" - end - end - raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal" - end - - def self._validate_prec(prec, method_name) # :nodoc: - raise ArgumentError, 'precision must be an Integer' unless Integer === prec - raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0 - end - - def self._infinity_computation_result # :nodoc: - if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_INFINITY) - raise FloatDomainError, "Computation results in 'Infinity'" - end - BigDecimal::INFINITY - end - - def self._nan_computation_result # :nodoc: - if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_NaN) - raise FloatDomainError, "Computation results to 'NaN'" - end - BigDecimal::NAN - end - # call-seq: # BigMath.log(decimal, numeric) -> BigDecimal # @@ -193,11 +191,13 @@ def self._nan_computation_result # :nodoc: # If +decimal+ is NaN, returns NaN. # def self.log(x, prec) - _validate_prec(prec, :log) - x = _coerce_to_bigdecimal(x, prec, :log, true) - return _nan_computation_result if x.nan? + BigDecimal::Internal.validate_prec(prec, :log) + raise Math::DomainError, 'Complex argument for BigMath.log' if Complex === x + + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log) + return BigDecimal::Internal.nan_computation_result if x.nan? raise Math::DomainError, 'Zero or negative argument for log' if x <= 0 - return _infinity_computation_result if x.infinite? + return BigDecimal::Internal.infinity_computation_result if x.infinite? return BigDecimal(0) if x == 1 if x > 10 || x < 0.1 @@ -259,10 +259,10 @@ def self.log(x, prec) # If +decimal+ is NaN, returns NaN. # def self.exp(x, prec) - _validate_prec(prec, :exp) - x = _coerce_to_bigdecimal(x, prec, :exp) - return _nan_computation_result if x.nan? - return x.positive? ? _infinity_computation_result : BigDecimal(0) if x.infinite? + BigDecimal::Internal.validate_prec(prec, :exp) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :exp) + return BigDecimal::Internal.nan_computation_result if x.nan? + return x.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) if x.infinite? return BigDecimal(1) if x.zero? return BigDecimal(1).div(exp(-x, prec), prec) if x < 0 From bbf5ce7b802193dfaa02c559cd65d78e1cf1bd2d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 14 Aug 2025 22:28:29 +0900 Subject: [PATCH 434/546] Remove gc_compaction test (#401) BigDecimal was GC compaction unsafe because of its structure (T_DATA contains back reference to VALUE). Now that the structure is changed and back reference is removed, we don't need this kind of test just like other c-extension libraries. --- test/bigdecimal/test_bigdecimal.rb | 47 ------------------------------ 1 file changed, 47 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 9ced8202..fa69ef43 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2565,53 +2565,6 @@ def test_bsearch_for_bigdecimal } end - def test_gc_compaction_safe - omit if RUBY_VERSION < "3.2" || RUBY_ENGINE == "truffleruby" - - assert_separately(["-rbigdecimal"], "#{<<~"begin;"}\n#{<<~'end;'}") - begin; - x = 1.5 - y = 0.5 - nan = BigDecimal("NaN") - inf = BigDecimal("Infinity") - bx = BigDecimal(x.to_s) - by = BigDecimal(y.to_s) - GC.verify_compaction_references(expand_heap: true, toward: :empty) - - assert_in_delta(x + y, bx + by) - assert_in_delta(x + y, bx.add(by, 10)) - assert_in_delta(x - y, bx - by) - assert_in_delta(x - y, bx.sub(by, 10)) - assert_in_delta(x * y, bx * by) - assert_in_delta(x * y, bx.mult(by, 10)) - assert_in_delta(x / y, bx / by) - assert_in_delta(x / y, bx.div(by, 10)) - assert_in_delta((x / y).floor, bx.div(by)) - assert_in_delta(x % y, bx % by) - assert_in_delta(Math.sqrt(x), bx.sqrt(10)) - assert_equal(x.div(y), bx.div(by)) - assert_equal(x.remainder(y), bx.remainder(by)) - assert_equal(x.divmod(y), bx.divmod(by)) - assert_equal([0, x], bx.divmod(inf)) - assert_in_delta(x, bx.remainder(inf)) - assert((nan + nan).nan?) - assert((nan - nan).nan?) - assert((nan * nan).nan?) - assert((nan / nan).nan?) - assert((nan % nan).nan?) - assert((inf + inf).infinite?) - assert((inf - inf).nan?) - assert((inf * inf).infinite?) - assert((inf / inf).nan?) - assert((inf % inf).nan?) - assert_in_delta(Math.exp(x), BigMath.exp(bx, 10)) - assert_in_delta(x**y, bx**by) - assert_in_delta(x**y, bx.power(by, 10)) - assert_in_delta(Math.exp(x), BigMath.exp(bx, 10)) - assert_in_delta(Math.log(x), BigMath.log(bx, 10)) - end; - end - def assert_no_memory_leak(code, *rest, **opt) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" paths = $LOAD_PATH.map{|path| "-I#{path}" } From 67d38d6b22675906397bba39792cf077d52f008a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 18:06:53 +0000 Subject: [PATCH 435/546] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/push_gem.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6146cf92..f3de621e 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -43,7 +43,7 @@ jobs: - { os: windows-latest , ruby: "3.2" } steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b935355..ae9a3b06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: BIGDECIMAL_USE_VP_TEST_METHODS: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 9e73446b..d2838490 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -31,7 +31,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.2.2 - name: Set up Ruby uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 From 834488de26030e25d1e5acb55012be891280b93b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 20 Aug 2025 05:33:04 +0900 Subject: [PATCH 436/546] Fix VpMult result size calculation (#403) --- ext/bigdecimal/bigdecimal.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c7b2b76c..b40aeaea 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -141,7 +141,7 @@ check_allocation_count_nonzero(void) #endif /* BIGDECIMAL_DEBUG */ /* VpMult VpDivd helpers */ -#define VPMULT_RESULT_PREC(a, b) (a->Prec + b->Prec + 1) +#define VPMULT_RESULT_PREC(a, b) (a->Prec + b->Prec) /* To calculate VpDivd with n-digits precision, quotient needs n+2*BASE_FIG-1 digits space */ /* In the worst precision case 0001_1111_1111 / 9999 = 0000_0001_1112, there are 2*BASE_FIG-1 leading zeros */ #define VPDIVD_QUO_DIGITS(required_digits) ((required_digits) + 2 * BASE_FIG - 1) @@ -5080,7 +5080,7 @@ VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, VP_EXPORT size_t VpMult(Real *c, Real *a, Real *b) { - size_t MxIndA, MxIndB, MxIndAB, MxIndC; + size_t MxIndA, MxIndB, MxIndAB; size_t ind_c, i, ii, nc; size_t ind_as, ind_ae, ind_bs; DECDIG carry; @@ -5112,13 +5112,11 @@ VpMult(Real *c, Real *a, Real *b) w = NULL; MxIndA = a->Prec - 1; MxIndB = b->Prec - 1; - MxIndC = c->MaxPrec - 1; MxIndAB = a->Prec + b->Prec - 1; - if (MxIndC < MxIndAB) { /* The Max. prec. of c < Prec(a)+Prec(b) */ - w = c; - c = NewZeroNolimit(1, (size_t)((MxIndAB + 1) * BASE_FIG)); - MxIndC = MxIndAB; + if (c->MaxPrec < VPMULT_RESULT_PREC(a, b)) { + w = c; + c = NewZeroNolimit(1, VPMULT_RESULT_PREC(a, b) * BASE_FIG); } /* set LHSV c info */ From c2cb664ffc4e01b48d65042852a08c6773b8668c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 21 Aug 2025 01:23:12 +0900 Subject: [PATCH 437/546] Fix GetAddSubPrec calculation (#406) --- ext/bigdecimal/bigdecimal.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b40aeaea..2ab8f84a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1116,22 +1116,10 @@ BigDecimal_mode(int argc, VALUE *argv, VALUE self) static size_t GetAddSubPrec(Real *a, Real *b) { - size_t mxs; - size_t mx = a->Prec; - SIGNED_VALUE d; - if (!VpIsDef(a) || !VpIsDef(b)) return (size_t)-1L; - if (mx < b->Prec) mx = b->Prec; - if (a->exponent != b->exponent) { - mxs = mx; - d = a->exponent - b->exponent; - if (d < 0) d = -d; - mx = mx + (size_t)d; - if (mx < mxs) { - return VpException(VP_EXCEPTION_INFINITY, "Exponent overflow", 0); - } - } - return mx; + ssize_t min_a = a->exponent - a->Prec; + ssize_t min_b = b->exponent - b->Prec; + return Max(a->exponent, b->exponent) - Min(min_a, min_b); } static inline SIGNED_VALUE From 1854328ae08d0df976f6b384156c78ab7a3b78bf Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 21 Aug 2025 01:45:15 +0900 Subject: [PATCH 438/546] Fix PrecLimit not restored on exception (#405) The scope of changing PrecLimit was large. If an exception occurs while chaginc PrecLimit, PrecLimit won't be restored correctly. --- ext/bigdecimal/bigdecimal.c | 182 ++++++++++------------------- ext/bigdecimal/bigdecimal.h | 2 +- test/bigdecimal/test_bigdecimal.rb | 45 +++++++ 3 files changed, 105 insertions(+), 124 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 2ab8f84a..c6083ea2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -284,9 +284,8 @@ static VALUE BigDecimal_positive_infinity(void); static VALUE BigDecimal_negative_infinity(void); static VALUE BigDecimal_positive_zero(void); static VALUE BigDecimal_negative_zero(void); -static VALUE BigDecimal_add_with_coerce(VALUE self, VALUE r, size_t coerce_prec); -static VALUE BigDecimal_sub_with_coerce(VALUE self, VALUE r, size_t coerce_prec); -static VALUE BigDecimal_mult_with_coerce(VALUE self, VALUE r, size_t coerce_prec); +static VALUE BigDecimal_addsub_with_coerce(VALUE self, VALUE r, size_t prec, int operation); +static VALUE BigDecimal_mult_with_coerce(VALUE self, VALUE r, size_t prec); static void BigDecimal_delete(void *pv) @@ -1431,35 +1430,34 @@ static VALUE BigDecimal_add(VALUE self, VALUE r) { if (!is_coerceable_to_BigDecimal(r)) return DoSomeOne(self, r, '+'); - return BigDecimal_add_with_coerce(self, r, 0); + return BigDecimal_addsub_with_coerce(self, r, 0, +1); } static VALUE -BigDecimal_add_with_coerce(VALUE self, VALUE r, size_t coerce_prec) +BigDecimal_addsub_with_coerce(VALUE self, VALUE r, size_t prec, int operation) { BDVALUE a, b, c; size_t mx; a = GetBDValueMust(self); - b = GetBDValueWithPrecMust(r, GetCoercePrec(a.real, coerce_prec)); + b = GetBDValueWithPrecMust(r, GetCoercePrec(a.real, prec)); - if (VpIsNaN(b.real)) return b.bigdecimal; - if (VpIsNaN(a.real)) return a.bigdecimal; + if (VpIsNaN(a.real)) return CheckGetValue(a); + if (VpIsNaN(b.real)) return CheckGetValue(b); mx = GetAddSubPrec(a.real, b.real); if (mx == (size_t)-1L) { /* a or b is inf */ c = NewZeroWrapLimited(1, BASE_FIG); - VpAddSub(c.real, a.real, b.real, 1); + VpAddSub(c.real, a.real, b.real, operation); } else { + size_t pl = VpGetPrecLimit(); + if (prec) VpSetPrecLimit(prec); c = NewZeroWrapLimited(1, (mx + 1) * BASE_FIG); - if (!mx) { - VpSetInf(c.real, VpGetSign(a.real)); - } - else { - VpAddSub(c.real, a.real, b.real, 1); - } + // Let VpAddSub round the result + VpAddSub(c.real, a.real, b.real, operation); + if (prec) VpSetPrecLimit(pl); } RB_GC_GUARD(a.bigdecimal); @@ -1486,40 +1484,7 @@ static VALUE BigDecimal_sub(VALUE self, VALUE r) { if (!is_coerceable_to_BigDecimal(r)) return DoSomeOne(self, r, '-'); - return BigDecimal_sub_with_coerce(self, r, 0); -} - -static VALUE -BigDecimal_sub_with_coerce(VALUE self, VALUE r, size_t coerce_prec) -{ - BDVALUE a, b, c; - size_t mx; - - a = GetBDValueMust(self); - b = GetBDValueWithPrecMust(r, GetCoercePrec(a.real, coerce_prec)); - - if (VpIsNaN(b.real)) return b.bigdecimal; - if (VpIsNaN(a.real)) return a.bigdecimal; - - mx = GetAddSubPrec(a.real, b.real); - if (mx == (size_t)-1L) { - /* a or b is inf */ - c = NewZeroWrapLimited(1, BASE_FIG); - VpAddSub(c.real, a.real, b.real, -1); - } - else { - c = NewZeroWrapLimited(1, (mx + 1) * BASE_FIG); - if (!mx) { - VpSetInf(c.real, VpGetSign(a.real)); - } - else { - VpAddSub(c.real, a.real, b.real, -1); - } - } - - RB_GC_GUARD(a.bigdecimal); - RB_GC_GUARD(b.bigdecimal); - return CheckGetValue(c); + return BigDecimal_addsub_with_coerce(self, r, 0, -1); } static VALUE @@ -1761,15 +1726,21 @@ BigDecimal_mult(VALUE self, VALUE r) } static VALUE -BigDecimal_mult_with_coerce(VALUE self, VALUE r, size_t coerce_prec) +BigDecimal_mult_with_coerce(VALUE self, VALUE r, size_t prec) { BDVALUE a, b, c; a = GetBDValueMust(self); - b = GetBDValueWithPrecMust(r, GetCoercePrec(a.real, coerce_prec)); + b = GetBDValueWithPrecMust(r, GetCoercePrec(a.real, prec)); - c = NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); + c = NewZeroWrapNolimit(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); VpMult(c.real, a.real, b.real); + if (prec) { + VpLeftRound(c.real, VpGetRoundMode(), prec); + } + else { + VpLimitRound(c.real, 0); + } RB_GC_GUARD(a.bigdecimal); RB_GC_GUARD(b.bigdecimal); @@ -2009,7 +1980,6 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) { SIGNED_VALUE ix; BDVALUE av, bv, cv, res; - size_t pl; if (NIL_P(n)) { /* div in Float sense */ NULLABLE_BDVALUE div; @@ -2023,8 +1993,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) /* div in BigDecimal sense */ ix = check_int_precision(n); - pl = VpSetPrecLimit(0); - if (ix == 0) ix = pl; + if (ix == 0) ix = VpGetPrecLimit(); av = GetBDValueMust(self); bv = GetBDValueWithPrecMust(b, GetCoercePrec(av.real, ix)); @@ -2039,10 +2008,10 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) } // Needs to calculate 1 extra digit for rounding. - cv = NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(ix + 1)); + cv = NewZeroWrapNolimit(1, VPDIVD_QUO_DIGITS(ix + 1)); res = NewZeroWrapNolimit(1, VPDIVD_REM_PREC(av.real, bv.real, cv.real) * BASE_FIG); VpDivd(cv.real, res.real, av.real, bv.real); - VpSetPrecLimit(pl); + if (!VpIsZero(res.real)) { // Remainder value affects rounding result. // ROUND_UP cv = 0.1e0 with idx=10 will be: @@ -2133,15 +2102,7 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_add2(VALUE self, VALUE b, VALUE n) { - BDVALUE cv; - SIGNED_VALUE mx = check_int_precision(n); - if (mx == 0) return BigDecimal_add_with_coerce(self, b, 0); - size_t pl = VpSetPrecLimit(0); - VALUE c = BigDecimal_add_with_coerce(self, b, mx); - VpSetPrecLimit(pl); - cv = GetBDValueMust(c); - VpLeftRound(cv.real, VpGetRoundMode(), mx); - return CheckGetValue(cv); + return BigDecimal_addsub_with_coerce(self, b, check_int_precision(n), +1); } /* call-seq: @@ -2160,15 +2121,7 @@ BigDecimal_add2(VALUE self, VALUE b, VALUE n) static VALUE BigDecimal_sub2(VALUE self, VALUE b, VALUE n) { - BDVALUE cv; - SIGNED_VALUE mx = check_int_precision(n); - if (mx == 0) return BigDecimal_sub_with_coerce(self, b, 0); - size_t pl = VpSetPrecLimit(0); - VALUE c = BigDecimal_sub_with_coerce(self, b, mx); - VpSetPrecLimit(pl); - cv = GetBDValueMust(c); - VpLeftRound(cv.real, VpGetRoundMode(), mx); - return CheckGetValue(cv); + return BigDecimal_addsub_with_coerce(self, b, check_int_precision(n), -1); } /* @@ -2200,15 +2153,7 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) static VALUE BigDecimal_mult2(VALUE self, VALUE b, VALUE n) { - BDVALUE cv; - SIGNED_VALUE mx = check_int_precision(n); - if (mx == 0) return BigDecimal_mult_with_coerce(self, b, 0); - size_t pl = VpSetPrecLimit(0); - VALUE c = BigDecimal_mult_with_coerce(self, b, mx); - VpSetPrecLimit(pl); - cv = GetBDValueMust(c); - VpLeftRound(cv.real, VpGetRoundMode(), mx); - return CheckGetValue(cv); + return BigDecimal_mult_with_coerce(self, b, check_int_precision(n)); } /* @@ -2305,7 +2250,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) VALUE vLoc; VALUE vRound; int round_to_int = 0; - size_t mx, pl; + size_t mx; unsigned short sw = VpGetRoundMode(); @@ -2336,11 +2281,10 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) break; } - pl = VpSetPrecLimit(0); a = GetBDValueMust(self); mx = (a.real->Prec + 1) * BASE_FIG; - c = NewZeroWrapLimited(1, mx); - VpSetPrecLimit(pl); + c = NewZeroWrapNolimit(1, mx); + VpActiveRound(c.real, a.real, sw, iLoc); RB_GC_GUARD(a.bigdecimal); @@ -2357,7 +2301,7 @@ BigDecimal_truncate_floor_ceil(int argc, VALUE *argv, VALUE self, unsigned short BDVALUE c, a; int iLoc; VALUE vLoc; - size_t mx, pl = VpSetPrecLimit(0); + size_t mx; if (rb_scan_args(argc, argv, "01", &vLoc) == 0) { iLoc = 0; @@ -2368,8 +2312,7 @@ BigDecimal_truncate_floor_ceil(int argc, VALUE *argv, VALUE self, unsigned short a = GetBDValueMust(self); mx = (a.real->Prec + 1) * BASE_FIG; - c = NewZeroWrapLimited(1, mx); - VpSetPrecLimit(pl); + c = NewZeroWrapNolimit(1, mx); VpActiveRound(c.real, a.real, rounding_mode, iLoc); RB_GC_GUARD(a.bigdecimal); @@ -3391,31 +3334,28 @@ BigDecimal_literal(const char *str) #ifdef BIGDECIMAL_USE_VP_TEST_METHODS VALUE BigDecimal_vpdivd(VALUE self, VALUE r, VALUE cprec) { - BDVALUE a,b,c,d; - size_t cn = NUM2INT(cprec); - a = GetBDValueMust(self); - b = GetBDValueMust(r); - c = NewZeroWrapLimited(1, cn * BASE_FIG); - d = NewZeroWrapLimited(1, VPDIVD_REM_PREC(a.real, b.real, c.real) * BASE_FIG); - VpDivd(c.real, d.real, a.real, b.real); - VpNmlz(c.real); - VpNmlz(d.real); - RB_GC_GUARD(a.bigdecimal); - RB_GC_GUARD(b.bigdecimal); - return rb_assoc_new(c.bigdecimal, d.bigdecimal); + BDVALUE a,b,c,d; + size_t cn = NUM2INT(cprec); + a = GetBDValueMust(self); + b = GetBDValueMust(r); + c = NewZeroWrapNolimit(1, cn * BASE_FIG); + d = NewZeroWrapNolimit(1, VPDIVD_REM_PREC(a.real, b.real, c.real) * BASE_FIG); + VpDivd(c.real, d.real, a.real, b.real); + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal); + return rb_assoc_new(c.bigdecimal, d.bigdecimal); } VALUE BigDecimal_vpmult(VALUE self, VALUE v) { - BDVALUE a,b,c; - a = GetBDValueMust(self); - b = GetBDValueMust(v); - c = NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); - VpMult(c.real, a.real, b.real); - VpNmlz(c.real); - RB_GC_GUARD(a.bigdecimal); - RB_GC_GUARD(b.bigdecimal); - return c.bigdecimal; + BDVALUE a,b,c; + a = GetBDValueMust(self); + b = GetBDValueMust(v); + c = NewZeroWrapNolimit(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); + VpMult(c.real, a.real, b.real); + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal); + return c.bigdecimal; } #endif /* BIGDECIMAL_USE_VP_TEST_METHODS */ @@ -3941,12 +3881,10 @@ VpGetPrecLimit(void) return NUM2SIZET(vlimit); } -VP_EXPORT size_t +VP_EXPORT void VpSetPrecLimit(size_t n) { - size_t const s = VpGetPrecLimit(); bigdecimal_set_thread_local_precision_limit(n); - return s; } /* @@ -4626,7 +4564,7 @@ VpAsgn(Real *c, Real *a, int isw) c->Prec = n; memcpy(c->frac, a->frac, n * sizeof(DECDIG)); /* Needs round ? */ - if (isw != 10) { + if (isw != 10 && isw != -10) { /* Not in ActiveRound */ if(c->Prec < a->Prec) { VpInternalRound(c, n, (n>0) ? a->frac[n-1] : 0, a->frac[n]); @@ -5084,11 +5022,11 @@ VpMult(Real *c, Real *a, Real *b) } if (VpIsOne(a)) { - VpAsgn(c, b, VpGetSign(a)); + VpAsgn(c, b, 10 * VpGetSign(a)); goto Exit; } if (VpIsOne(b)) { - VpAsgn(c, a, VpGetSign(b)); + VpAsgn(c, a, 10 * VpGetSign(b)); goto Exit; } if (b->Prec > a->Prec) { @@ -5102,6 +5040,7 @@ VpMult(Real *c, Real *a, Real *b) MxIndB = b->Prec - 1; MxIndAB = a->Prec + b->Prec - 1; + // Only VpSqrt calls VpMult with insufficient precision if (c->MaxPrec < VPMULT_RESULT_PREC(a, b)) { w = c; c = NewZeroNolimit(1, VPMULT_RESULT_PREC(a, b) * BASE_FIG); @@ -5161,15 +5100,12 @@ VpMult(Real *c, Real *a, Real *b) } } } + VpNmlz(c); if (w != NULL) { /* free work variable */ - VpNmlz(c); - VpAsgn(w, c, 1); + VpAsgn(w, c, 10); rbd_free_struct(c); c = w; } - else { - VpLimitRound(c,0); - } Exit: return c->Prec*BASE_FIG; diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 0f52d017..a981ede5 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -205,7 +205,7 @@ VP_EXPORT double VpGetDoubleNegZero(void); /* These 2 functions added at v1.1.7 */ VP_EXPORT size_t VpGetPrecLimit(void); -VP_EXPORT size_t VpSetPrecLimit(size_t n); +VP_EXPORT void VpSetPrecLimit(size_t n); /* Round mode */ VP_EXPORT int VpIsRoundMode(unsigned short n); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index fa69ef43..daa2ff9d 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -214,6 +214,51 @@ def test_BigDecimal_with_rational assert_raise_with_message(ArgumentError, "can't omit precision for a Rational.") { BigDecimal(42.quo(7)) } end + def test_restore_prec_limit_on_exception + assert_limit_restored = ->(&block) do + BigDecimal.save_exception_mode do + BigDecimal.mode BigDecimal::EXCEPTION_ALL, true + BigDecimal.save_limit do + BigDecimal.limit 3 + begin + block.call + assert(false, "Expected an exception to be raised") + rescue FloatDomainError, TypeError + end + assert_equal(3, BigDecimal.limit) + end + end + end + + x = BigDecimal(1) + nan = BigDecimal::NAN + obj = Object.new + prec = 10 + assert_limit_restored.call { x + nan } + assert_limit_restored.call { x + obj } + assert_limit_restored.call { x - nan } + assert_limit_restored.call { x - obj } + assert_limit_restored.call { x * nan } + assert_limit_restored.call { x * obj } + assert_limit_restored.call { x / nan } + assert_limit_restored.call { x / obj } + assert_limit_restored.call { x % nan } + assert_limit_restored.call { x % obj } + assert_limit_restored.call { x.add(nan, prec) } + assert_limit_restored.call { x.add(obj, prec) } + assert_limit_restored.call { x.sub(nan, prec) } + assert_limit_restored.call { x.sub(obj, prec) } + assert_limit_restored.call { x.mult(nan, prec) } + assert_limit_restored.call { x.mult(obj, prec) } + assert_limit_restored.call { x.div(nan) } + assert_limit_restored.call { x.div(nan, prec) } + assert_limit_restored.call { x.div(obj, prec) } + assert_limit_restored.call { x.modulo(nan) } + assert_limit_restored.call { x.modulo(obj) } + assert_limit_restored.call { x.remainder(nan) } + assert_limit_restored.call { x.remainder(obj) } + end + def test_BigDecimal_with_float assert_equal(BigDecimal("0.1235"), BigDecimal(0.1234567, 4)) assert_equal(BigDecimal("-0.1235"), BigDecimal(-0.1234567, 4)) From 97ff6497722b291cba8d4cba165ec0e56cf43ebe Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 22 Aug 2025 00:36:05 +0900 Subject: [PATCH 439/546] Fix div,modulo,remainder and divmod precision when prec limit is specified (#408) BigDecimal_DoDivmod rounds in an inconsistent way. Quotient is rounded in preclimit+(0..17) digits, Remainder might be several orders of magnitude larger than divisor. Stop rounding in DoDivmod for now and make the result at least consistent. --- ext/bigdecimal/bigdecimal.c | 15 ++++++++++----- test/bigdecimal/test_bigdecimal.rb | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c6083ea2..0722e150 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1818,7 +1818,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE BDVALUE a, b, dv, md, res; NULLABLE_BDVALUE b2; ssize_t a_exponent, b_exponent; - size_t mx, rx; + size_t mx, rx, pl; a = GetBDValueMust(self); @@ -1862,26 +1862,31 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE a_exponent = VpExponent10(a.real); b_exponent = VpExponent10(b.real); mx = a_exponent > b_exponent ? a_exponent - b_exponent + 1 : 1; - dv = NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(mx)); + dv = NewZeroWrapNolimit(1, VPDIVD_QUO_DIGITS(mx)); /* res is reused for VpDivd remainder and VpMult result */ rx = VPDIVD_REM_PREC(a.real, b.real, dv.real); mx = VPMULT_RESULT_PREC(dv.real, b.real); res = NewZeroWrapNolimit(1, Max(rx, mx) * BASE_FIG); /* AddSub needs one more prec */ - md = NewZeroWrapLimited(1, (res.real->MaxPrec + 1) * BASE_FIG); + md = NewZeroWrapNolimit(1, (res.real->MaxPrec + 1) * BASE_FIG); VpDivd(dv.real, res.real, a.real, b.real); VpMidRound(dv.real, VP_ROUND_DOWN, 0); VpMult(res.real, dv.real, b.real); + pl = VpGetPrecLimit(); + VpSetPrecLimit(0); VpAddSub(md.real, a.real, res.real, -1); + VpSetPrecLimit(pl); if (!truncate && !VpIsZero(md.real) && (VpGetSign(a.real) * VpGetSign(b.real) < 0)) { /* result adjustment for negative case */ - BDVALUE dv2 = NewZeroWrapLimited(1, (dv.real->MaxPrec + 1) * BASE_FIG); - BDVALUE md2 = NewZeroWrapLimited(1, (GetAddSubPrec(md.real, b.real) + 1) * BASE_FIG); + BDVALUE dv2 = NewZeroWrapNolimit(1, (dv.real->MaxPrec + 1) * BASE_FIG); + BDVALUE md2 = NewZeroWrapNolimit(1, (GetAddSubPrec(md.real, b.real) + 1) * BASE_FIG); + VpSetPrecLimit(0); VpAddSub(dv2.real, dv.real, VpOne(), -1); VpAddSub(md2.real, md.real, b.real, 1); + VpSetPrecLimit(pl); *div = bdvalue_nullable(dv2); *mod = bdvalue_nullable(md2); RB_GC_GUARD(dv2.bigdecimal); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index daa2ff9d..636d2622 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2031,6 +2031,26 @@ def test_arithmetic_operation_with_limit end end + def test_div_mod_rem_operation_with_limit + x = -(9 ** 100) + y = 7 ** 100 + div_int = x.div(y) + div = BigDecimal(div_int) + mod = BigDecimal(x.modulo(y)) + rem = BigDecimal(x.remainder(y)) + big_x = BigDecimal(x) + big_y = BigDecimal(y) + + BigDecimal.save_limit do + BigDecimal.limit(3) + assert_equal(div_int, big_x.div(big_y)) + assert_equal(mod, big_x.modulo(big_y)) + assert_equal(rem, big_x.remainder(big_y)) + assert_equal([div, mod], big_x.divmod(big_y)) + assert_equal(3, BigDecimal.limit) + end + end + def test_sign BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) From 7b900312dcc570e21c99f7d685ff0daeb6ab75a6 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 22 Aug 2025 19:15:14 +0900 Subject: [PATCH 440/546] Fix x.fix and x.frac affected by prec limit, Stop -x and x.abs round with prec limit (#409) --- ext/bigdecimal/bigdecimal.c | 12 ++++++------ test/bigdecimal/test_bigdecimal.rb | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0722e150..010a783f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1702,8 +1702,8 @@ static VALUE BigDecimal_neg(VALUE self) { BDVALUE a = GetBDValueMust(self); - BDVALUE c = NewZeroWrapLimited(1, a.real->Prec * BASE_FIG); - VpAsgn(c.real, a.real, -1); + BDVALUE c = NewZeroWrapNolimit(1, a.real->Prec * BASE_FIG); + VpAsgn(c.real, a.real, -10); RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); } @@ -2176,8 +2176,8 @@ static VALUE BigDecimal_abs(VALUE self) { BDVALUE a = GetBDValueMust(self); - BDVALUE c = NewZeroWrapLimited(1, a.real->Prec * BASE_FIG); - VpAsgn(c.real, a.real, 1); + BDVALUE c = NewZeroWrapNolimit(1, a.real->Prec * BASE_FIG); + VpAsgn(c.real, a.real, 10); VpChangeSign(c.real, 1); RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); @@ -2215,7 +2215,7 @@ static VALUE BigDecimal_fix(VALUE self) { BDVALUE a = GetBDValueMust(self); - BDVALUE c = NewZeroWrapLimited(1, (a.real->Prec + 1) * BASE_FIG); + BDVALUE c = NewZeroWrapNolimit(1, (a.real->Prec + 1) * BASE_FIG); VpActiveRound(c.real, a.real, VP_ROUND_DOWN, 0); /* 0: round off */ RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); @@ -2359,7 +2359,7 @@ static VALUE BigDecimal_frac(VALUE self) { BDVALUE a = GetBDValueMust(self); - BDVALUE c = NewZeroWrapLimited(1, (a.real->Prec + 1) * BASE_FIG); + BDVALUE c = NewZeroWrapNolimit(1, (a.real->Prec + 1) * BASE_FIG); VpFrac(c.real, a.real); RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 636d2622..0fdfd7c9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2051,6 +2051,31 @@ def test_div_mod_rem_operation_with_limit end end + def test_sign_operator_ignores_limit + plus_x = BigDecimal('7' * 100) + minus_x = -plus_x + BigDecimal.save_limit do + BigDecimal.limit(3) + assert_equal(plus_x, +plus_x) + assert_equal(minus_x, +minus_x) + assert_equal(minus_x, -plus_x) + assert_equal(plus_x, -minus_x) + assert_equal(plus_x, minus_x.abs) + assert_equal(plus_x, plus_x.abs) + end + end + + def test_fix_frac_ignores_limit + fix = BigDecimal("#{'4' * 56}") + frac = BigDecimal("0.#{'7' * 89}") + x = fix + frac + BigDecimal.save_limit do + BigDecimal.limit(3) + assert_equal(fix, x.fix) + assert_equal(frac, x.frac) + end + end + def test_sign BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) From 73c1caff1fec498240eebd051ab0dfd5e4d8a820 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 22 Aug 2025 21:58:40 +0900 Subject: [PATCH 441/546] Don't use ZeroWrapLimited. Use unlimited version instead. (#410) ZeroWrapLimited turned out to be useless in most case. In a few case that ZeroWrapLimited is needed, explicitly calculate precision is enough. --- ext/bigdecimal/bigdecimal.c | 290 +++++++++-------------------- ext/bigdecimal/bigdecimal.h | 2 +- test/bigdecimal/test_bigdecimal.rb | 1 + 3 files changed, 85 insertions(+), 208 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 010a783f..a742de9b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -149,7 +149,7 @@ check_allocation_count_nonzero(void) #define VPDIVD_REM_PREC(a, b, c) Max(a->Prec, b->Prec + c->MaxPrec - 1) static NULLABLE_BDVALUE -CreateFromString(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception); +CreateFromString(const char *str, VALUE klass, bool strict_p, bool raise_exception); PUREFUNC(static inline size_t rbd_struct_size(size_t const)); @@ -170,32 +170,12 @@ rbd_allocate_struct(size_t const internal_digits) return real; } -static size_t -rbd_calculate_internal_digits(size_t const digits, bool limit_precision) -{ - size_t const len = roomof(digits, BASE_FIG); - if (limit_precision) { - size_t const prec_limit = VpGetPrecLimit(); - if (prec_limit > 0) { - /* NOTE: 2 more digits for rounding and division */ - size_t const max_len = roomof(prec_limit, BASE_FIG) + 2; - if (len > max_len) - return max_len; - } - } - - return len; -} - static inline Real * -rbd_allocate_struct_decimal_digits(size_t const decimal_digits, bool limit_precision) +rbd_allocate_struct_decimal_digits(size_t const decimal_digits) { - size_t const internal_digits = rbd_calculate_internal_digits(decimal_digits, limit_precision); - return rbd_allocate_struct(internal_digits); + return rbd_allocate_struct(roomof(decimal_digits, BASE_FIG)); } -static VALUE BigDecimal_wrap_struct(VALUE obj, Real *vp); - static void rbd_free_struct(Real *real) { @@ -206,58 +186,29 @@ rbd_free_struct(Real *real) } } +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero(int sign, size_t const digits)); #define NewZero rbd_allocate_struct_zero -static Real * -rbd_allocate_struct_zero(int sign, size_t const digits, bool limit_precision) +static inline Real * +rbd_allocate_struct_zero(int sign, size_t const digits) { - Real *real = rbd_allocate_struct_decimal_digits(digits, limit_precision); + Real *real = rbd_allocate_struct_decimal_digits(digits); VpSetZero(real, sign); return real; } -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_limited(int sign, size_t const digits)); -#define NewZeroLimited rbd_allocate_struct_zero_limited -static inline Real * -rbd_allocate_struct_zero_limited(int sign, size_t const digits) -{ - return rbd_allocate_struct_zero(sign, digits, true); -} - -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_nolimit(int sign, size_t const digits)); -#define NewZeroNolimit rbd_allocate_struct_zero_nolimit +// Only used in VpSqrt through BigDecimal_sqrt +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit(int sign, size_t const digits)); +#define NewOneNolimit rbd_allocate_struct_one_nolimit static inline Real * -rbd_allocate_struct_zero_nolimit(int sign, size_t const digits) -{ - return rbd_allocate_struct_zero(sign, digits, false); -} - -#define NewOne rbd_allocate_struct_one -static Real * -rbd_allocate_struct_one(int sign, size_t const digits, bool limit_precision) +rbd_allocate_struct_one_nolimit(int sign, size_t const digits) { - Real *real = rbd_allocate_struct_decimal_digits(digits, limit_precision); + Real *real = rbd_allocate_struct_decimal_digits(digits); VpSetOne(real); if (sign < 0) VpSetSign(real, VP_SIGN_NEGATIVE_FINITE); return real; } -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_limited(int sign, size_t const digits)); -#define NewOneLimited rbd_allocate_struct_one_limited -static inline Real * -rbd_allocate_struct_one_limited(int sign, size_t const digits) -{ - return rbd_allocate_struct_one(sign, digits, true); -} - -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit(int sign, size_t const digits)); -#define NewOneNolimit rbd_allocate_struct_one_nolimit -static inline Real * -rbd_allocate_struct_one_nolimit(int sign, size_t const digits) -{ - return rbd_allocate_struct_one(sign, digits, false); -} - /* * ================== Ruby Interface part ========================== */ @@ -313,60 +264,35 @@ static const rb_data_type_t BigDecimal_data_type = { #endif }; -static NULLABLE_BDVALUE -rbd_allocate_struct_zero_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) +static VALUE +BigDecimal_wrap_struct(VALUE klass, Real *real) { - VALUE obj = Qnil; - Real *real = rbd_allocate_struct_zero(sign, digits, limit_precision); - if (real != NULL) { - obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); - BigDecimal_wrap_struct(obj, real); - } - return (NULLABLE_BDVALUE) { obj, real }; + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, real); + RB_OBJ_FREEZE(obj); + return obj; } -MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits)); -#define NewZeroWrapLimited rbd_allocate_struct_zero_limited_wrap -static inline BDVALUE -rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits) +MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_wrap(int sign, size_t const digits)); +#define NewZeroWrap rbd_allocate_struct_zero_wrap +static BDVALUE +rbd_allocate_struct_zero_wrap(int sign, size_t const digits) { - return bdvalue_nonnullable(rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, true)); + Real *real = rbd_allocate_struct_zero(sign, digits); + return (BDVALUE) { BigDecimal_wrap_struct(rb_cBigDecimal, real), real }; } -MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits)); -#define NewZeroWrapNolimit rbd_allocate_struct_zero_nolimit_wrap +// Only used in BigDecimal_sqrt +MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits)); +#define NewZeroWrapLimited rbd_allocate_struct_zero_limited_wrap static inline BDVALUE -rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits) -{ - return bdvalue_nonnullable(rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, false)); -} - -static NULLABLE_BDVALUE -rbd_allocate_struct_one_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) +rbd_allocate_struct_zero_limited_wrap(int sign, size_t digits) { - VALUE obj = Qnil; - Real *real = rbd_allocate_struct_one(sign, digits, limit_precision); - if (real != NULL) { - obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); - BigDecimal_wrap_struct(obj, real); + size_t prec_limit = VpGetPrecLimit(); + if (prec_limit) { + prec_limit += 2 * BASE_FIG; /* 2 more digits for rounding and division */ + if (prec_limit < digits) digits = prec_limit; } - return (NULLABLE_BDVALUE) { obj, real }; -} - -MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits)); -#define NewOneWrapLimited rbd_allocate_struct_one_limited_wrap -static inline BDVALUE -rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits) -{ - return bdvalue_nonnullable(rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, true)); -} - -MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits)); -#define NewOneWrapNolimit rbd_allocate_struct_one_nolimit_wrap -static inline BDVALUE -rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits) -{ - return bdvalue_nonnullable(rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, false)); + return rbd_allocate_struct_zero_wrap(sign, digits); } static inline int @@ -397,7 +323,7 @@ static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); static VALUE rb_inum_convert_to_BigDecimal(VALUE val); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); -static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); +static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, int raise_exception); static VALUE rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static NULLABLE_BDVALUE @@ -427,7 +353,7 @@ GetBDValueWithPrecInternal(VALUE v, size_t prec, int must) #ifdef ENABLE_NUMERIC_STRING case T_STRING: { const char *c_str = StringValueCStr(v); - v = rb_cstr_convert_to_BigDecimal(c_str, RSTRING_LEN(v) + VpBaseFig() + 1, must); + v = rb_cstr_convert_to_BigDecimal(c_str, must); break; } #endif /* ENABLE_NUMERIC_STRING */ @@ -821,7 +747,7 @@ BigDecimal_load(VALUE self, VALUE str) rb_raise(rb_eTypeError, "load failed: invalid character in the marshaled string"); } } - v = bdvalue_nonnullable(CreateFromString(0, (char *)pch, self, true, true)); + v = bdvalue_nonnullable(CreateFromString((char *)pch, self, true, true)); return CheckGetValue(v); } @@ -1138,31 +1064,12 @@ check_int_precision(VALUE v) return n; } -static VALUE -BigDecimal_wrap_struct(VALUE obj, Real *vp) -{ - assert(is_kind_of_BigDecimal(obj)); - assert(vp != NULL); - - if (RTYPEDDATA_DATA(obj) == vp) - return obj; - - assert(RTYPEDDATA_DATA(obj) == NULL); - - RTYPEDDATA_DATA(obj) = vp; - RB_OBJ_FREEZE(obj); - return obj; -} - static NULLABLE_BDVALUE -CreateFromString(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception) +CreateFromString(const char *str, VALUE klass, bool strict_p, bool raise_exception) { - VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); - Real *pv = VpAlloc(mx, str, strict_p, raise_exception); - if (!pv) - return (NULLABLE_BDVALUE) { Qnil, NULL }; - BigDecimal_wrap_struct(obj, pv); - return (NULLABLE_BDVALUE) { obj, pv }; + Real *pv = VpAlloc(str, strict_p, raise_exception); + if (!pv) return (NULLABLE_BDVALUE) { Qnil, NULL }; + return (NULLABLE_BDVALUE) { BigDecimal_wrap_struct(klass, pv), pv }; } static Real * @@ -1448,13 +1355,13 @@ BigDecimal_addsub_with_coerce(VALUE self, VALUE r, size_t prec, int operation) mx = GetAddSubPrec(a.real, b.real); if (mx == (size_t)-1L) { /* a or b is inf */ - c = NewZeroWrapLimited(1, BASE_FIG); + c = NewZeroWrap(1, BASE_FIG); VpAddSub(c.real, a.real, b.real, operation); } else { + c = NewZeroWrap(1, (mx + 1) * BASE_FIG); size_t pl = VpGetPrecLimit(); if (prec) VpSetPrecLimit(prec); - c = NewZeroWrapLimited(1, (mx + 1) * BASE_FIG); // Let VpAddSub round the result VpAddSub(c.real, a.real, b.real, operation); if (prec) VpSetPrecLimit(pl); @@ -1702,7 +1609,7 @@ static VALUE BigDecimal_neg(VALUE self) { BDVALUE a = GetBDValueMust(self); - BDVALUE c = NewZeroWrapNolimit(1, a.real->Prec * BASE_FIG); + BDVALUE c = NewZeroWrap(1, a.real->Prec * BASE_FIG); VpAsgn(c.real, a.real, -10); RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); @@ -1733,7 +1640,7 @@ BigDecimal_mult_with_coerce(VALUE self, VALUE r, size_t prec) a = GetBDValueMust(self); b = GetBDValueWithPrecMust(r, GetCoercePrec(a.real, prec)); - c = NewZeroWrapNolimit(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); + c = NewZeroWrap(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); VpMult(c.real, a.real, b.real); if (prec) { VpLeftRound(c.real, VpGetRoundMode(), prec); @@ -1862,14 +1769,14 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE a_exponent = VpExponent10(a.real); b_exponent = VpExponent10(b.real); mx = a_exponent > b_exponent ? a_exponent - b_exponent + 1 : 1; - dv = NewZeroWrapNolimit(1, VPDIVD_QUO_DIGITS(mx)); + dv = NewZeroWrap(1, VPDIVD_QUO_DIGITS(mx)); /* res is reused for VpDivd remainder and VpMult result */ rx = VPDIVD_REM_PREC(a.real, b.real, dv.real); mx = VPMULT_RESULT_PREC(dv.real, b.real); - res = NewZeroWrapNolimit(1, Max(rx, mx) * BASE_FIG); + res = NewZeroWrap(1, Max(rx, mx) * BASE_FIG); /* AddSub needs one more prec */ - md = NewZeroWrapNolimit(1, (res.real->MaxPrec + 1) * BASE_FIG); + md = NewZeroWrap(1, (res.real->MaxPrec + 1) * BASE_FIG); VpDivd(dv.real, res.real, a.real, b.real); VpMidRound(dv.real, VP_ROUND_DOWN, 0); @@ -1881,8 +1788,8 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE if (!truncate && !VpIsZero(md.real) && (VpGetSign(a.real) * VpGetSign(b.real) < 0)) { /* result adjustment for negative case */ - BDVALUE dv2 = NewZeroWrapNolimit(1, (dv.real->MaxPrec + 1) * BASE_FIG); - BDVALUE md2 = NewZeroWrapNolimit(1, (GetAddSubPrec(md.real, b.real) + 1) * BASE_FIG); + BDVALUE dv2 = NewZeroWrap(1, (dv.real->MaxPrec + 1) * BASE_FIG); + BDVALUE md2 = NewZeroWrap(1, (GetAddSubPrec(md.real, b.real) + 1) * BASE_FIG); VpSetPrecLimit(0); VpAddSub(dv2.real, dv.real, VpOne(), -1); VpAddSub(md2.real, md.real, b.real, 1); @@ -2013,8 +1920,8 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) } // Needs to calculate 1 extra digit for rounding. - cv = NewZeroWrapNolimit(1, VPDIVD_QUO_DIGITS(ix + 1)); - res = NewZeroWrapNolimit(1, VPDIVD_REM_PREC(av.real, bv.real, cv.real) * BASE_FIG); + cv = NewZeroWrap(1, VPDIVD_QUO_DIGITS(ix + 1)); + res = NewZeroWrap(1, VPDIVD_REM_PREC(av.real, bv.real, cv.real) * BASE_FIG); VpDivd(cv.real, res.real, av.real, bv.real); if (!VpIsZero(res.real)) { @@ -2176,7 +2083,7 @@ static VALUE BigDecimal_abs(VALUE self) { BDVALUE a = GetBDValueMust(self); - BDVALUE c = NewZeroWrapNolimit(1, a.real->Prec * BASE_FIG); + BDVALUE c = NewZeroWrap(1, a.real->Prec * BASE_FIG); VpAsgn(c.real, a.real, 10); VpChangeSign(c.real, 1); RB_GC_GUARD(a.bigdecimal); @@ -2215,7 +2122,7 @@ static VALUE BigDecimal_fix(VALUE self) { BDVALUE a = GetBDValueMust(self); - BDVALUE c = NewZeroWrapNolimit(1, (a.real->Prec + 1) * BASE_FIG); + BDVALUE c = NewZeroWrap(1, (a.real->Prec + 1) * BASE_FIG); VpActiveRound(c.real, a.real, VP_ROUND_DOWN, 0); /* 0: round off */ RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); @@ -2288,7 +2195,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) a = GetBDValueMust(self); mx = (a.real->Prec + 1) * BASE_FIG; - c = NewZeroWrapNolimit(1, mx); + c = NewZeroWrap(1, mx); VpActiveRound(c.real, a.real, sw, iLoc); @@ -2317,7 +2224,7 @@ BigDecimal_truncate_floor_ceil(int argc, VALUE *argv, VALUE self, unsigned short a = GetBDValueMust(self); mx = (a.real->Prec + 1) * BASE_FIG; - c = NewZeroWrapNolimit(1, mx); + c = NewZeroWrap(1, mx); VpActiveRound(c.real, a.real, rounding_mode, iLoc); RB_GC_GUARD(a.bigdecimal); @@ -2359,7 +2266,7 @@ static VALUE BigDecimal_frac(VALUE self) { BDVALUE a = GetBDValueMust(self); - BDVALUE c = NewZeroWrapNolimit(1, (a.real->Prec + 1) * BASE_FIG); + BDVALUE c = NewZeroWrap(1, (a.real->Prec + 1) * BASE_FIG); VpFrac(c.real, a.real); RB_GC_GUARD(a.bigdecimal); return CheckGetValue(c); @@ -2677,12 +2584,9 @@ check_exception(VALUE bd) static VALUE rb_uint64_convert_to_BigDecimal(uint64_t uval) { - VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); - Real *vp; if (uval == 0) { vp = rbd_allocate_struct(1); - vp->MaxPrec = 1; vp->Prec = 1; vp->exponent = 1; VpSetZero(vp, 1); @@ -2690,7 +2594,6 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval) } else if (uval < BASE) { vp = rbd_allocate_struct(1); - vp->MaxPrec = 1; vp->Prec = 1; vp->exponent = 1; VpSetSign(vp, 1); @@ -2716,14 +2619,13 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval) const size_t exp = len + ntz; vp = rbd_allocate_struct(len); - vp->MaxPrec = len; vp->Prec = len; vp->exponent = exp; VpSetSign(vp, 1); MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - len, DECDIG, len); } - return BigDecimal_wrap_struct(obj, vp); + return BigDecimal_wrap_struct(rb_cBigDecimal, vp); } static VALUE @@ -2771,7 +2673,6 @@ rb_big_convert_to_BigDecimal(VALUE val) else { VALUE str = rb_big2str(val, 10); BDVALUE v = bdvalue_nonnullable(CreateFromString( - RSTRING_LEN(str) + BASE_FIG + 1, RSTRING_PTR(str), rb_cBigDecimal, true, @@ -2969,21 +2870,18 @@ rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } static VALUE -rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception) +rb_cstr_convert_to_BigDecimal(const char *c_str, int raise_exception) { - if (digs == SIZE_MAX) - digs = 0; - - NULLABLE_BDVALUE v = CreateFromString(digs, c_str, rb_cBigDecimal, true, raise_exception); + NULLABLE_BDVALUE v = CreateFromString(c_str, rb_cBigDecimal, true, raise_exception); if (v.bigdecimal_or_nil == Qnil) return Qnil; return CheckGetValue(bdvalue_nonnullable(v)); } static inline VALUE -rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +rb_str_convert_to_BigDecimal(VALUE val, int raise_exception) { const char *c_str = StringValueCStr(val); - return rb_cstr_convert_to_BigDecimal(c_str, digs, raise_exception); + return rb_cstr_convert_to_BigDecimal(c_str, raise_exception); } static VALUE @@ -3013,11 +2911,11 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) Real *vp; TypedData_Get_Struct(val, Real, &BigDecimal_data_type, vp); - - VALUE copy = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); vp = VpCopy(NULL, vp); + RB_GC_GUARD(val); + + VALUE copy = BigDecimal_wrap_struct(rb_cBigDecimal, vp); /* TODO: rounding */ - BigDecimal_wrap_struct(copy, vp); return check_exception(copy); } else if (RB_INTEGER_TYPE_P(val)) { @@ -3039,7 +2937,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) return rb_convert_to_BigDecimal(rb_complex_real(val), digs, raise_exception); } else if (RB_TYPE_P(val, T_STRING)) { - return rb_str_convert_to_BigDecimal(val, 0, raise_exception); + return rb_str_convert_to_BigDecimal(val, raise_exception); } /* TODO: chheck to_d */ @@ -3053,7 +2951,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } return Qnil; } - return rb_str_convert_to_BigDecimal(str, 0, raise_exception); + return rb_str_convert_to_BigDecimal(str, raise_exception); } /* call-seq: @@ -3143,7 +3041,7 @@ static VALUE BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) { char const *c_str = StringValueCStr(str); - NULLABLE_BDVALUE v = CreateFromString(0, c_str, klass, false, true); + NULLABLE_BDVALUE v = CreateFromString(c_str, klass, false, true); if (v.bigdecimal_or_nil == Qnil) return Qnil; else @@ -3343,8 +3241,8 @@ BigDecimal_vpdivd(VALUE self, VALUE r, VALUE cprec) { size_t cn = NUM2INT(cprec); a = GetBDValueMust(self); b = GetBDValueMust(r); - c = NewZeroWrapNolimit(1, cn * BASE_FIG); - d = NewZeroWrapNolimit(1, VPDIVD_REM_PREC(a.real, b.real, c.real) * BASE_FIG); + c = NewZeroWrap(1, cn * BASE_FIG); + d = NewZeroWrap(1, VPDIVD_REM_PREC(a.real, b.real, c.real) * BASE_FIG); VpDivd(c.real, d.real, a.real, b.real); RB_GC_GUARD(a.bigdecimal); RB_GC_GUARD(b.bigdecimal); @@ -3356,7 +3254,7 @@ BigDecimal_vpmult(VALUE self, VALUE v) { BDVALUE a,b,c; a = GetBDValueMust(self); b = GetBDValueMust(v); - c = NewZeroWrapNolimit(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); + c = NewZeroWrap(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); VpMult(c.real, a.real, b.real); RB_GC_GUARD(a.bigdecimal); RB_GC_GUARD(b.bigdecimal); @@ -4181,10 +4079,12 @@ VpInit(DECDIG BaseVal) VpGetDoubleNegZero(); /* Const 1.0 */ - VpConstOne = NewOneNolimit(1, 1); + VpConstOne = NewZero(1, 1); + VpSetOne(VpConstOne); /* Const 0.5 */ - VpConstPt5 = NewOneNolimit(1, 1); + VpConstPt5 = NewZero(1, 1); + VpSetSign(VpConstPt5, 1); VpConstPt5->exponent = 0; VpConstPt5->frac[0] = 5*BASE1; @@ -4255,7 +4155,6 @@ bigdecimal_parse_special_string(const char *str) while (*p && ISSPACE(*p)) ++p; if (*p == '\0') { Real *vp = rbd_allocate_struct(1); - vp->MaxPrec = 1; switch (table[i].sign) { default: UNREACHABLE; break; @@ -4320,38 +4219,22 @@ protected_VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size /* * Allocates variable. * [Input] - * mx ... The number of decimal digits to be allocated, if zero then mx is determined by szVal. - * The mx will be the number of significant digits can to be stored. - * szVal ... The value assigned(char). If szVal==NULL, then zero is assumed. - * If szVal[0]=='#' then MaxPrec is not affected by the precision limit - * so that the full precision specified by szVal is allocated. + * szVal ... The value assigned(char). * * [Returns] * Pointer to the newly allocated variable, or * NULL be returned if memory allocation is failed,or any error. */ VP_EXPORT Real * -VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) +VpAlloc(const char *szVal, int strict_p, int exc) { const char *orig_szVal = szVal; size_t i, j, ni, ipf, nf, ipe, ne, exp_seen, nalloc; - size_t len; char v, *psz; int sign=1; Real *vp = NULL; VALUE buf; - if (szVal == NULL) { - return_zero: - /* necessary to be able to store */ - /* at least mx digits. */ - /* szVal==NULL ==> allocate zero value. */ - vp = rbd_allocate_struct(mx); - vp->MaxPrec = rbd_calculate_internal_digits(mx, false); /* Must false */ - VpSetZero(vp, 1); /* initialize vp to zero. */ - return vp; - } - /* Skipping leading spaces */ while (ISSPACE(*szVal)) szVal++; @@ -4360,14 +4243,11 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) return vp; } - /* Processing the leading one `#` */ - if (*szVal != '#') { - len = rbd_calculate_internal_digits(mx, true); - } - else { - len = rbd_calculate_internal_digits(mx, false); - ++szVal; - } + /* Skip leading `#`. + * It used to be a mark to indicate that an extra MaxPrec should be allocated, + * but now it has no effect. + */ + if (*szVal == '#') ++szVal; /* Scanning digits */ @@ -4514,7 +4394,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) VALUE str; invalid_value: if (!strict_p) { - goto return_zero; + return NewZero(1, 1); } if (!exc) { return NULL; @@ -4525,11 +4405,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) nalloc = (ni + nf + BASE_FIG - 1) / BASE_FIG + 1; /* set effective allocation */ /* units for szVal[] */ - if (len == 0) len = 1; - nalloc = Max(nalloc, len); - len = nalloc; - vp = rbd_allocate_struct(len); - vp->MaxPrec = len; /* set max precision */ + vp = rbd_allocate_struct(nalloc); VpSetZero(vp, sign); protected_VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne, true); rb_str_resize(buf, 0); @@ -5048,7 +4924,7 @@ VpMult(Real *c, Real *a, Real *b) // Only VpSqrt calls VpMult with insufficient precision if (c->MaxPrec < VPMULT_RESULT_PREC(a, b)) { w = c; - c = NewZeroNolimit(1, VPMULT_RESULT_PREC(a, b) * BASE_FIG); + c = NewZero(1, VPMULT_RESULT_PREC(a, b) * BASE_FIG); } /* set LHSV c info */ diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index a981ede5..9441c56d 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -215,7 +215,7 @@ VP_EXPORT unsigned short VpSetRoundMode(unsigned short n); VP_EXPORT int VpException(unsigned short f,const char *str,int always); VP_EXPORT size_t VpNumOfChars(Real *vp,const char *pszFmt); VP_EXPORT size_t VpInit(DECDIG BaseVal); -VP_EXPORT Real *VpAlloc(size_t mx, const char *szVal, int strict_p, int exc); +VP_EXPORT Real *VpAlloc(const char *szVal, int strict_p, int exc); VP_EXPORT size_t VpAsgn(Real *c, Real *a, int isw); VP_EXPORT size_t VpAddSub(Real *c,Real *a,Real *b,int operation); VP_EXPORT size_t VpMult(Real *c,Real *a,Real *b); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 0fdfd7c9..fe3a64db 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -150,6 +150,7 @@ def test_BigDecimal_bug7522 bd = BigDecimal("1.12", 1) assert_same(bd, BigDecimal(bd)) assert_same(bd, BigDecimal(bd, exception: false)) + assert_equal(bd, BigDecimal(bd, 1)) assert_not_same(bd, BigDecimal(bd, 1)) assert_not_same(bd, BigDecimal(bd, 1, exception: false)) end From c18bacb4000d2240dd234d3c2d9269b8ffadb91d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 22 Aug 2025 23:45:16 +0900 Subject: [PATCH 442/546] Fix `x / y` precision when prec limit is huge (#412) When prec limit set to 10000, BigDecimal(2)/3 was calculated in 10000 digits. Fixes it to calcualte in 32 digits. Prec limit is just a limit. --- ext/bigdecimal/bigdecimal.c | 5 ++--- test/bigdecimal/test_bigdecimal.rb | 9 +++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a742de9b..3b1b879f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1905,18 +1905,17 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) /* div in BigDecimal sense */ ix = check_int_precision(n); - if (ix == 0) ix = VpGetPrecLimit(); - av = GetBDValueMust(self); bv = GetBDValueWithPrecMust(b, GetCoercePrec(av.real, ix)); if (ix == 0) { - ssize_t a_prec, b_prec; + ssize_t a_prec, b_prec, limit = VpGetPrecLimit(); VpCountPrecisionAndScale(av.real, &a_prec, NULL); VpCountPrecisionAndScale(bv.real, &b_prec, NULL); ix = ((a_prec > b_prec) ? a_prec : b_prec) + BIGDECIMAL_DOUBLE_FIGURES; if (2 * BIGDECIMAL_DOUBLE_FIGURES > ix) ix = 2 * BIGDECIMAL_DOUBLE_FIGURES; + if (limit && limit < ix) ix = limit; } // Needs to calculate 1 extra digit for rounding. diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index fe3a64db..3f4706ad 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2032,6 +2032,15 @@ def test_arithmetic_operation_with_limit end end + def test_div_with_huge_limit + BigDecimal.save_limit do + x, y = BigDecimal(2), BigDecimal(3) + div = x / y + BigDecimal.limit(1000) + assert_equal(div, x / y) + end + end + def test_div_mod_rem_operation_with_limit x = -(9 ** 100) y = 7 ** 100 From f6bfd2f755cd9ee7fe27c985204dcdb00d507413 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 22 Aug 2025 23:45:44 +0900 Subject: [PATCH 443/546] Calculate exp, log, pow with the given prec even if prec limit is set (#411) --- lib/bigdecimal.rb | 126 +++++++++++++++-------------- test/bigdecimal/test_bigdecimal.rb | 19 +++++ 2 files changed, 85 insertions(+), 60 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 9460e269..68fe1677 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -200,52 +200,55 @@ def self.log(x, prec) return BigDecimal::Internal.infinity_computation_result if x.infinite? return BigDecimal(0) if x == 1 - if x > 10 || x < 0.1 - log10 = log(BigDecimal(10), prec) - exponent = x.exponent - x = x * BigDecimal("1e#{-x.exponent}") - if x < 0.3 - x *= 10 - exponent -= 1 + BigDecimal.save_limit do + BigDecimal.limit(0) + if x > 10 || x < 0.1 + log10 = log(BigDecimal(10), prec) + exponent = x.exponent + x *= BigDecimal("1e#{-x.exponent}") + if x < 0.3 + x *= 10 + exponent -= 1 + end + return log10 * exponent + log(x, prec) end - return log10 * exponent + log(x, prec) - end - x_minus_one_exponent = (x - 1).exponent - prec += BigDecimal.double_fig + x_minus_one_exponent = (x - 1).exponent + prec += BigDecimal.double_fig - # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps - sqrt_steps = [2 * Integer.sqrt(prec) + 3 * x_minus_one_exponent, 0].max + # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps + sqrt_steps = [2 * Integer.sqrt(prec) + 3 * x_minus_one_exponent, 0].max - # Reduce sqrt_step until sqrt gets fast - # https://github.com/ruby/bigdecimal/pull/323 - # https://github.com/ruby/bigdecimal/pull/343 - sqrt_steps /= 10 + # Reduce sqrt_step until sqrt gets fast + # https://github.com/ruby/bigdecimal/pull/323 + # https://github.com/ruby/bigdecimal/pull/343 + sqrt_steps /= 10 - lg2 = 0.3010299956639812 - prec2 = prec + [-x_minus_one_exponent, 0].max + (sqrt_steps * lg2).ceil + lg2 = 0.3010299956639812 + prec2 = prec + [-x_minus_one_exponent, 0].max + (sqrt_steps * lg2).ceil - sqrt_steps.times do - x = x.sqrt(prec2) + sqrt_steps.times do + x = x.sqrt(prec2) - # Workaround for https://github.com/ruby/bigdecimal/issues/354 - x = x.mult(1, prec2 + BigDecimal.double_fig) - end + # Workaround for https://github.com/ruby/bigdecimal/issues/354 + x = x.mult(1, prec2 + BigDecimal.double_fig) + end - # Taylor series for log(x) around 1 - # log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1) - # log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...) - x = (x - 1).div(x + 1, prec2) - y = x - x2 = x.mult(x, prec) - 1.step do |i| - n = prec + x.exponent - y.exponent + x2.exponent - break if n <= 0 || x.zero? - x = x.mult(x2.round(n - x2.exponent), n) - y = y.add(x.div(2 * i + 1, n), prec) - end + # Taylor series for log(x) around 1 + # log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1) + # log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...) + x = (x - 1).div(x + 1, prec2) + y = x + x2 = x.mult(x, prec) + 1.step do |i| + n = prec + x.exponent - y.exponent + x2.exponent + break if n <= 0 || x.zero? + x = x.mult(x2.round(n - x2.exponent), n) + y = y.add(x.div(2 * i + 1, n), prec) + end - y.mult(2 ** (sqrt_steps + 1), prec) + y.mult(2 ** (sqrt_steps + 1), prec) + end end # call-seq: @@ -266,30 +269,33 @@ def self.exp(x, prec) return BigDecimal(1) if x.zero? return BigDecimal(1).div(exp(-x, prec), prec) if x < 0 - # exp(x * 10**cnt) = exp(x)**(10**cnt) - cnt = x > 1 ? x.exponent : 0 - prec2 = prec + BigDecimal.double_fig + cnt - x *= BigDecimal("1e-#{cnt}") - xn = BigDecimal(1) - y = BigDecimal(1) - - # Taylor series for exp(x) around 0 - 1.step do |i| - n = prec2 + xn.exponent - break if n <= 0 || xn.zero? - x = x.mult(1, n) - xn = xn.mult(x, n).div(i, n) - y = y.add(xn, prec2) - end + BigDecimal.save_limit do + BigDecimal.limit(0) + # exp(x * 10**cnt) = exp(x)**(10**cnt) + cnt = x > 1 ? x.exponent : 0 + prec2 = prec + BigDecimal.double_fig + cnt + x *= BigDecimal("1e-#{cnt}") + xn = BigDecimal(1) + y = BigDecimal(1) + + # Taylor series for exp(x) around 0 + 1.step do |i| + n = prec2 + xn.exponent + break if n <= 0 || xn.zero? + x = x.mult(1, n) + xn = xn.mult(x, n).div(i, n) + y = y.add(xn, prec2) + end - # calculate exp(x * 10**cnt) from exp(x) - # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10 - cnt.times do - y2 = y.mult(y, prec2) - y5 = y2.mult(y2, prec2).mult(y, prec2) - y = y5.mult(y5, prec2) - end + # calculate exp(x * 10**cnt) from exp(x) + # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10 + cnt.times do + y2 = y.mult(y, prec2) + y5 = y2.mult(y2, prec2).mult(y, prec2) + y = y5.mult(y5, prec2) + end - y.mult(1, prec) + y.mult(1, prec) + end end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 3f4706ad..a113c734 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2429,6 +2429,25 @@ def test_BigMath_log_under_gc_stress EOS end + def test_exp_log_pow_with_limit + prec = 100 + limit = 10 + x = BigDecimal(123).div(7, prec) + y = BigDecimal(456).div(11, prec) + exp = BigMath.exp(x, prec) + log = BigMath.log(x, prec) + pow = x.power(y, prec) + pow_lim = x.power(y, limit) + BigDecimal.save_limit do + BigDecimal.limit(limit) + assert_equal(exp, BigMath.exp(x, prec)) + assert_equal(log, BigMath.log(x, prec)) + assert_equal(pow, x.power(y, prec)) + assert_equal(pow_lim, x**y) + assert_equal(limit, BigDecimal.limit) + end + end + def test_frozen_p x = BigDecimal(1) assert(x.frozen?) From 4fe723c62437fbc98acd8b2fb52748d7f0038f23 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 25 Aug 2025 20:17:51 +0900 Subject: [PATCH 444/546] Simplify to_i logic (#413) `x = x.fix` before converting to string --- ext/bigdecimal/bigdecimal.c | 42 ++++++++++++------------------ test/bigdecimal/test_bigdecimal.rb | 6 +++-- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 3b1b879f..6c008fc1 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1125,6 +1125,7 @@ BigDecimal_check_num(Real *p) VpCheckException(p, true); } +static VALUE BigDecimal_fix(VALUE self); static VALUE BigDecimal_split(VALUE self); /* Returns the value as an Integer. @@ -1134,41 +1135,32 @@ static VALUE BigDecimal_split(VALUE self); static VALUE BigDecimal_to_i(VALUE self) { - ssize_t e, nf; BDVALUE v; VALUE ret; v = GetBDValueMust(self); BigDecimal_check_num(v.real); - e = VpExponent10(v.real); - if (e <= 0) return INT2FIX(0); - nf = VpBaseFig(); - if (e <= nf) { + if (v.real->exponent <= 0) return INT2FIX(0); + if (v.real->exponent == 1) { ret = LONG2NUM((long)(VpGetSign(v.real) * (DECDIG_DBL_SIGNED)v.real->frac[0])); } else { - VALUE a = BigDecimal_split(self); - VALUE digits = RARRAY_AREF(a, 1); - VALUE numerator = rb_funcall(digits, rb_intern("to_i"), 0); - ssize_t dpower = e - (ssize_t)RSTRING_LEN(digits); + VALUE fix = (ssize_t)v.real->Prec > v.real->exponent ? BigDecimal_fix(self) : self; + VALUE digits = RARRAY_AREF(BigDecimal_split(fix), 1); + ssize_t dpower = VpExponent10(v.real) - (ssize_t)RSTRING_LEN(digits); + ret = rb_funcall(digits, rb_intern("to_i"), 0); - if (BIGDECIMAL_NEGATIVE_P(v.real)) { - numerator = rb_funcall(numerator, '*', 1, INT2FIX(-1)); - } - if (dpower < 0) { - ret = rb_funcall(numerator, rb_intern("div"), 1, - rb_funcall(INT2FIX(10), rb_intern("**"), 1, - INT2FIX(-dpower))); - } - else { - ret = rb_funcall(numerator, '*', 1, - rb_funcall(INT2FIX(10), rb_intern("**"), 1, - INT2FIX(dpower))); - } - if (RB_TYPE_P(ret, T_FLOAT)) { - rb_raise(rb_eFloatDomainError, "Infinity"); - } + if (BIGDECIMAL_NEGATIVE_P(v.real)) { + ret = rb_funcall(ret, '*', 1, INT2FIX(-1)); + } + if (dpower) { + VALUE pow10 = rb_funcall(INT2FIX(10), rb_intern("**"), 1, SSIZET2NUM(dpower)); + // In Ruby < 3.4, int**int may return Float::INFINITY + if (RB_TYPE_P(pow10, T_FLOAT)) rb_raise(rb_eFloatDomainError, "Infinity"); + + ret = rb_funcall(ret, '*', 1, pow10); + } } RB_GC_GUARD(v.bigdecimal); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a113c734..78ead2a8 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -928,8 +928,10 @@ def test_to_i assert_raise(FloatDomainError) {( 0 / x).to_i} x = BigDecimal("1") assert_equal(1, x.to_i) - x = BigDecimal((2**100).to_s) - assert_equal(2**100, x.to_i) + + assert_equal(2**100, BigDecimal(2**100).to_i) + assert_equal(2**100 / 10**50, BigDecimal("#{2**100}e-50").to_i) + assert_equal(2**100 * 10**50, BigDecimal("#{2**100}e50").to_i) end def test_to_f From 99cc2d56c3fa8fc8d5e91822cda8050a1259993a Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 25 Aug 2025 20:19:44 +0900 Subject: [PATCH 445/546] Add BigMath::E and BigMath::PI precision test (#414) --- test/bigdecimal/test_bigmath.rb | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index e4eb570f..7a3dcb0a 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -13,9 +13,20 @@ class TestBigMath < Test::Unit::TestCase MINF = BigDecimal("-Infinity") NAN = BigDecimal("NaN") - def test_const - assert_in_delta(Math::PI, PI(N)) - assert_in_delta(Math::E, E(N)) + def test_pi + assert_equal( + BigDecimal("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068"), + PI(100).round(100) + ) + assert_relative_precision {|n| PI(n) } + end + + def test_e + assert_equal( + BigDecimal("2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427"), + E(100) + ) + assert_relative_precision {|n| E(n) } end def test_sqrt From 3d8b9be58694b0fe5eabfebe0fa55b9ad8b27c91 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 2 Sep 2025 22:48:54 +0900 Subject: [PATCH 446/546] Rewrite BigDecimal#sqrt in ruby with improved Newton's method (#381) * Rewrite BigDecimal#sqrt in ruby with improved Newton's method * Use common prec validation also in BigDecimal#sqrt * Use common EXCEPTION_INFINITY check in sqrt * Limit sqrt result when prec limit is set --- ext/bigdecimal/bigdecimal.c | 251 +---------------------------- ext/bigdecimal/bigdecimal.h | 3 - lib/bigdecimal.rb | 46 +++++- test/bigdecimal/test_bigdecimal.rb | 38 ++++- 4 files changed, 72 insertions(+), 266 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6c008fc1..70cc4196 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -196,19 +196,6 @@ rbd_allocate_struct_zero(int sign, size_t const digits) return real; } -// Only used in VpSqrt through BigDecimal_sqrt -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit(int sign, size_t const digits)); -#define NewOneNolimit rbd_allocate_struct_one_nolimit -static inline Real * -rbd_allocate_struct_one_nolimit(int sign, size_t const digits) -{ - Real *real = rbd_allocate_struct_decimal_digits(digits); - VpSetOne(real); - if (sign < 0) - VpSetSign(real, VP_SIGN_NEGATIVE_FINITE); - return real; -} - /* * ================== Ruby Interface part ========================== */ @@ -281,20 +268,6 @@ rbd_allocate_struct_zero_wrap(int sign, size_t const digits) return (BDVALUE) { BigDecimal_wrap_struct(rb_cBigDecimal, real), real }; } -// Only used in BigDecimal_sqrt -MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits)); -#define NewZeroWrapLimited rbd_allocate_struct_zero_limited_wrap -static inline BDVALUE -rbd_allocate_struct_zero_limited_wrap(int sign, size_t digits) -{ - size_t prec_limit = VpGetPrecLimit(); - if (prec_limit) { - prec_limit += 2 * BASE_FIG; /* 2 more digits for rounding and division */ - if (prec_limit < digits) digits = prec_limit; - } - return rbd_allocate_struct_zero_wrap(sign, digits); -} - static inline int is_kind_of_BigDecimal(VALUE const v) { @@ -2081,32 +2054,6 @@ BigDecimal_abs(VALUE self) return CheckGetValue(c); } -/* call-seq: - * sqrt(n) - * - * Returns the square root of the value. - * - * Result has at least n significant digits. - */ -static VALUE -BigDecimal_sqrt(VALUE self, VALUE nFig) -{ - BDVALUE c, a; - size_t mx, n; - - a = GetBDValueMust(self); - mx = a.real->Prec * (VpBaseFig() + 1); - - n = check_int_precision(nFig); - n += VpDblFig() + VpBaseFig(); - if (mx <= n) mx = n; - c = NewZeroWrapLimited(1, mx); - VpSqrt(c.real, a.real); - - RB_GC_GUARD(a.bigdecimal); - return CheckGetValue(c); -} - /* Return the integer part of the number, as a BigDecimal. */ static VALUE @@ -3594,7 +3541,6 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "dup", BigDecimal_clone, 0); rb_define_method(rb_cBigDecimal, "to_f", BigDecimal_to_f, 0); rb_define_method(rb_cBigDecimal, "abs", BigDecimal_abs, 0); - rb_define_method(rb_cBigDecimal, "sqrt", BigDecimal_sqrt, 1); rb_define_method(rb_cBigDecimal, "fix", BigDecimal_fix, 0); rb_define_method(rb_cBigDecimal, "round", BigDecimal_round, -1); rb_define_method(rb_cBigDecimal, "frac", BigDecimal_frac, 0); @@ -3666,9 +3612,6 @@ static int gfDebug = 1; /* Debug switch */ #endif /* BIGDECIMAL_DEBUG */ static Real *VpConstOne; /* constant 1.0 */ -static Real *VpConstPt5; /* constant 0.5 */ -#define maxnr 100UL /* Maximum iterations for calculating sqrt. */ - /* used in VpSqrt() */ enum op_sw { OP_SW_ADD = 1, /* + */ @@ -4073,12 +4016,6 @@ VpInit(DECDIG BaseVal) VpConstOne = NewZero(1, 1); VpSetOne(VpConstOne); - /* Const 0.5 */ - VpConstPt5 = NewZero(1, 1); - VpSetSign(VpConstPt5, 1); - VpConstPt5->exponent = 0; - VpConstPt5->frac[0] = 5*BASE1; - #ifdef BIGDECIMAL_DEBUG gnAlloc = 0; #endif /* BIGDECIMAL_DEBUG */ @@ -4883,7 +4820,6 @@ VpMult(Real *c, Real *a, Real *b) size_t ind_as, ind_ae, ind_bs; DECDIG carry; DECDIG_DBL s; - Real *w; if (!VpIsDefOP(c, a, b, OP_SW_MULT)) return 0; /* No significant digit */ @@ -4903,29 +4839,19 @@ VpMult(Real *c, Real *a, Real *b) } if (b->Prec > a->Prec) { /* Adjust so that digits(a)>digits(b) */ - w = a; + Real *w = a; a = b; b = w; } - w = NULL; MxIndA = a->Prec - 1; MxIndB = b->Prec - 1; MxIndAB = a->Prec + b->Prec - 1; - // Only VpSqrt calls VpMult with insufficient precision - if (c->MaxPrec < VPMULT_RESULT_PREC(a, b)) { - w = c; - c = NewZero(1, VPMULT_RESULT_PREC(a, b) * BASE_FIG); - } - /* set LHSV c info */ c->exponent = a->exponent; /* set exponent */ VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ - if (!AddExponent(c, b->exponent)) { - if (w) rbd_free_struct(c); - return 0; - } + if (!AddExponent(c, b->exponent)) return 0; carry = 0; nc = ind_c = MxIndAB; memset(c->frac, 0, (nc + 1) * sizeof(DECDIG)); /* Initialize c */ @@ -4973,11 +4899,6 @@ VpMult(Real *c, Real *a, Real *b) } } VpNmlz(c); - if (w != NULL) { /* free work variable */ - VpAsgn(w, c, 10); - rbd_free_struct(c); - c = w; - } Exit: return c->Prec*BASE_FIG; @@ -5879,174 +5800,6 @@ VpVtoD(double *d, SIGNED_VALUE *e, Real *m) return f; } -/* - * m <- d - */ -VP_EXPORT void -VpDtoV(Real *m, double d) -{ - size_t ind_m, mm; - SIGNED_VALUE ne; - DECDIG i; - double val, val2; - - if (isnan(d)) { - VpSetNaN(m); - goto Exit; - } - if (isinf(d)) { - if (d > 0.0) VpSetPosInf(m); - else VpSetNegInf(m); - goto Exit; - } - - if (d == 0.0) { - VpSetZero(m, 1); - goto Exit; - } - val = (d > 0.) ? d : -d; - ne = 0; - if (val >= 1.0) { - while (val >= 1.0) { - val /= (double)BASE; - ++ne; - } - } - else { - val2 = 1.0 / (double)BASE; - while (val < val2) { - val *= (double)BASE; - --ne; - } - } - /* Now val = 0.xxxxx*BASE**ne */ - - mm = m->MaxPrec; - memset(m->frac, 0, mm * sizeof(DECDIG)); - for (ind_m = 0; val > 0.0 && ind_m < mm; ind_m++) { - val *= (double)BASE; - i = (DECDIG)val; - val -= (double)i; - m->frac[ind_m] = i; - } - if (ind_m >= mm) ind_m = mm - 1; - VpSetSign(m, (d > 0.0) ? 1 : -1); - m->Prec = ind_m + 1; - m->exponent = ne; - - VpInternalRound(m, 0, (m->Prec > 0) ? m->frac[m->Prec-1] : 0, - (DECDIG)(val*(double)BASE)); - -Exit: - return; -} - -/* - * y = SQRT(x), y*y - x =>0 - */ -VP_EXPORT int -VpSqrt(Real *y, Real *x) -{ - Real *f = NULL; - Real *r = NULL; - size_t y_prec; - SIGNED_VALUE n, e; - ssize_t nr; - double val; - - /* Zero or +Infinity ? */ - if (VpIsZero(x) || VpIsPosInf(x)) { - VpAsgn(y,x,1); - goto Exit; - } - - /* Negative ? */ - if (BIGDECIMAL_NEGATIVE_P(x)) { - VpSetNaN(y); - return VpException(VP_EXCEPTION_OP, "sqrt of negative value", 0); - } - - /* NaN ? */ - if (VpIsNaN(x)) { - VpSetNaN(y); - return VpException(VP_EXCEPTION_OP, "sqrt of 'NaN'(Not a Number)", 0); - } - - /* One ? */ - if (VpIsOne(x)) { - VpSetOne(y); - goto Exit; - } - - n = (SIGNED_VALUE)y->MaxPrec; - if (x->MaxPrec > (size_t)n) n = (ssize_t)x->MaxPrec; - - /* allocate temporally variables */ - /* TODO: reconsider MaxPrec of f and r */ - f = NewOneNolimit(1, y->MaxPrec * (BASE_FIG + 2)); - r = NewOneNolimit(1, (n + n) * (BASE_FIG + 2)); - - nr = 0; - y_prec = y->MaxPrec; - - VpVtoD(&val, &e, x); /* val <- x */ - e /= (SIGNED_VALUE)BASE_FIG; - n = e / 2; - if (e - n * 2 != 0) { - val /= BASE; - n = (e + 1) / 2; - } - VpDtoV(y, sqrt(val)); /* y <- sqrt(val) */ - y->exponent += n; - n = (SIGNED_VALUE)roomof(BIGDECIMAL_DOUBLE_FIGURES, BASE_FIG); - y->MaxPrec = Min((size_t)n , y_prec); - f->MaxPrec = y->MaxPrec + 1; - n = (SIGNED_VALUE)(y_prec * BASE_FIG); - if (n > (SIGNED_VALUE)maxnr) n = (SIGNED_VALUE)maxnr; - - /* - * Perform: y_{n+1} = (y_n - x/y_n) / 2 - */ - do { - y->MaxPrec *= 2; - if (y->MaxPrec > y_prec) y->MaxPrec = y_prec; - f->MaxPrec = y->MaxPrec; - VpDivd(f, r, x, y); /* f = x/y */ - VpAddSub(r, f, y, -1); /* r = f - y */ - VpMult(f, VpConstPt5, r); /* f = 0.5*r */ - if (y_prec == y->MaxPrec && VpIsZero(f)) - goto converge; - VpAddSub(r, f, y, 1); /* r = y + f */ - VpAsgn(y, r, 1); /* y = r */ - } while (++nr < n); - -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - printf("ERROR(VpSqrt): did not converge within %ld iterations.\n", nr); - } -#endif /* BIGDECIMAL_DEBUG */ - y->MaxPrec = y_prec; - -converge: - VpChangeSign(y, 1); -#ifdef BIGDECIMAL_DEBUG - if (gfDebug) { - VpMult(r, y, y); - VpAddSub(f, x, r, -1); - printf("VpSqrt: iterations = %"PRIdSIZE"\n", nr); - VPrint(stdout, " y =% \n", y); - VPrint(stdout, " x =% \n", x); - VPrint(stdout, " x-y*y = % \n", f); - } -#endif /* BIGDECIMAL_DEBUG */ - y->MaxPrec = y_prec; - -Exit: - rbd_free_struct(f); - rbd_free_struct(r); - return 1; -} - /* * Round relatively from the decimal point. * f: rounding mode diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 9441c56d..82c88a2a 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -195,7 +195,6 @@ typedef struct { */ #define VpBaseFig() BIGDECIMAL_COMPONENT_FIGURES -#define VpDblFig() BIGDECIMAL_DOUBLE_FIGURES /* Zero,Inf,NaN (isinf(),isnan() used to check) */ VP_EXPORT double VpGetDoubleNaN(void); @@ -229,8 +228,6 @@ VP_EXPORT void VpToString(Real *a, char *buf, size_t bufsize, size_t fFmt, int f VP_EXPORT void VpToFString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus); VP_EXPORT int VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne); VP_EXPORT int VpVtoD(double *d, SIGNED_VALUE *e, Real *m); -VP_EXPORT void VpDtoV(Real *m,double d); -VP_EXPORT int VpSqrt(Real *y,Real *x); VP_EXPORT int VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t il); VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf); VP_EXPORT int VpLeftRound(Real *y, unsigned short f, ssize_t nf); diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 68fe1677..e1accd47 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -21,9 +21,13 @@ def self.coerce_to_bigdecimal(x, prec, method_name) # :nodoc: raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal" end - def self.validate_prec(prec, method_name) # :nodoc: + def self.validate_prec(prec, method_name, accept_zero: false) # :nodoc: raise ArgumentError, 'precision must be an Integer' unless Integer === prec - raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0 + if accept_zero + raise ArgumentError, "Negative precision for #{method_name}" if prec < 0 + else + raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0 + end end def self.infinity_computation_result # :nodoc: @@ -172,6 +176,37 @@ def power(y, prec = nil) end ans.mult(1, prec) end + + # Returns the square root of the value. + # + # Result has at least prec significant digits. + # + def sqrt(prec) + Internal.validate_prec(prec, :sqrt, accept_zero: true) + return Internal.infinity_computation_result if infinite? == 1 + + raise FloatDomainError, 'sqrt of negative value' if self < 0 + raise FloatDomainError, "sqrt of 'NaN'(Not a Number)" if nan? + return self if zero? + + limit = BigDecimal.limit.nonzero? if prec == 0 + + # BigDecimal#sqrt calculates at least n_significant_digits precision. + # This feature maybe problematic for some cases. + n_digits = n_significant_digits + prec = [prec, n_digits].max + + ex = exponent / 2 + x = self.mult(BigDecimal("1e#{-ex * 2}"), n_significant_digits) + y = BigDecimal(Math.sqrt(x.to_f)) + precs = [prec + BigDecimal.double_fig] + precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig + precs.reverse_each do |p| + y = y.add(x.div(y, p), p).div(2, p) + end + y = y.mult(1, limit) if limit + y.mult(BigDecimal("1e#{ex}"), precs.first) + end end # Core BigMath methods for BigDecimal (log, exp) are defined here. @@ -217,12 +252,7 @@ def self.log(x, prec) prec += BigDecimal.double_fig # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps - sqrt_steps = [2 * Integer.sqrt(prec) + 3 * x_minus_one_exponent, 0].max - - # Reduce sqrt_step until sqrt gets fast - # https://github.com/ruby/bigdecimal/pull/323 - # https://github.com/ruby/bigdecimal/pull/343 - sqrt_steps /= 10 + sqrt_steps = [Integer.sqrt(prec) + 3 * x_minus_one_exponent, 0].max lg2 = 0.3010299956639812 prec2 = prec + [-x_minus_one_exponent, 0].max + (sqrt_steps * lg2).ceil diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 78ead2a8..09f4e95f 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1443,15 +1443,23 @@ def test_sqrt_bigdecimal assert_in_delta(BigDecimal("4.0000000000000000000125"), BigDecimal("16.0000000000000000001").sqrt(100), BigDecimal("1e-40")) - BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) - BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - assert_raise_with_message(FloatDomainError, "sqrt of 'NaN'(Not a Number)") { BigDecimal("NaN").sqrt(1) } - assert_raise_with_message(FloatDomainError, "sqrt of negative value") { BigDecimal("-Infinity").sqrt(1) } + assert_raise_with_message(FloatDomainError, "sqrt of 'NaN'(Not a Number)") { BigDecimal::NAN.sqrt(1) } + assert_raise_with_message(FloatDomainError, "sqrt of negative value") { NEGATIVE_INFINITY.sqrt(1) } assert_equal(0, BigDecimal("0").sqrt(1)) assert_equal(0, BigDecimal("-0").sqrt(1)) assert_equal(1, BigDecimal("1").sqrt(1)) - assert_positive_infinite(BigDecimal("Infinity").sqrt(1)) + assert_positive_infinite_calculation { BigDecimal::INFINITY.sqrt(1) } + + # Out of float range + assert_equal(BigDecimal('12e1024'), BigDecimal('144e2048').sqrt(10)) + assert_equal(BigDecimal('12e-1024'), BigDecimal('144e-2048').sqrt(10)) + + sqrt2_300 = BigDecimal(2).sqrt(300) + (250..270).each do |prec| + sqrt_prec = prec + BigDecimal.double_fig - 1 + assert_in_delta(sqrt2_300, BigDecimal(2).sqrt(prec), BigDecimal("1e#{-sqrt_prec}")) + end end def test_sqrt_5266 @@ -1468,6 +1476,20 @@ def test_sqrt_5266 x.sqrt(109).to_s(109).split(' ')[0]) end + def test_sqrt_minimum_precision + x = BigDecimal((2**200).to_s) + assert_equal(2**100, x.sqrt(1)) + + x = BigDecimal('1' * 60 + '.' + '1' * 40) + assert_in_delta(BigDecimal('3' * 30 + '.' + '3' * 70), x.sqrt(1), BigDecimal('1e-70')) + + x = BigDecimal('1' * 40 + '.' + '1' * 60) + assert_in_delta(BigDecimal('3' * 20 + '.' + '3' * 80), x.sqrt(1), BigDecimal('1e-80')) + + x = BigDecimal('0.' + '0' * 50 + '1' * 100) + assert_in_delta(BigDecimal('0.' + '0' * 25 + '3' * 100), x.sqrt(1), BigDecimal('1e-125')) + end + def test_fix x = BigDecimal("1.1") assert_equal(1, x.fix) @@ -2431,17 +2453,21 @@ def test_BigMath_log_under_gc_stress EOS end - def test_exp_log_pow_with_limit + def test_sqrt_exp_log_pow_with_limit prec = 100 limit = 10 x = BigDecimal(123).div(7, prec) y = BigDecimal(456).div(11, prec) + sqrt = BigMath.sqrt(x, prec) + sqrt_lim = sqrt.mult(1, limit) exp = BigMath.exp(x, prec) log = BigMath.log(x, prec) pow = x.power(y, prec) pow_lim = x.power(y, limit) BigDecimal.save_limit do BigDecimal.limit(limit) + assert_equal(sqrt, BigMath.sqrt(x, prec)) + assert_equal(sqrt_lim, BigMath.sqrt(x, 0)) assert_equal(exp, BigMath.exp(x, prec)) assert_equal(log, BigMath.log(x, prec)) assert_equal(pow, x.power(y, prec)) From 6253bef053777b455e5f08843f0d746014a1f0e7 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 2 Sep 2025 23:47:21 +0900 Subject: [PATCH 447/546] Update bigdecimal version used in benchmark from 3.0.0 to 3.1.1 (#416) GitHub actions macos-latest has been updated to macos-15, and `gem install bigdecimal -v 3.0.0` began to fail. --- .github/workflows/benchmark.yml | 2 +- benchmark/from_float.yml | 2 +- benchmark/from_large_integer.yml | 2 +- benchmark/from_small_integer.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index f3de621e..a0531481 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -53,7 +53,7 @@ jobs: - name: Install dependencies run: | bundle install - gem install bigdecimal -v 3.0.0 + gem install bigdecimal -v 3.1.1 - run: rake compile diff --git a/benchmark/from_float.yml b/benchmark/from_float.yml index 25b362e8..0131d49f 100644 --- a/benchmark/from_float.yml +++ b/benchmark/from_float.yml @@ -2,7 +2,7 @@ loop_count: 100000 contexts: - gems: - bigdecimal: 3.0.0 + bigdecimal: 3.1.1 - name: "master" prelude: |- $LOAD_PATH.unshift(File.expand_path("lib")) diff --git a/benchmark/from_large_integer.yml b/benchmark/from_large_integer.yml index 58437fe0..64b9eff7 100644 --- a/benchmark/from_large_integer.yml +++ b/benchmark/from_large_integer.yml @@ -2,7 +2,7 @@ loop_count: 1000 contexts: - gems: - bigdecimal: 3.0.0 + bigdecimal: 3.1.1 - name: "master" prelude: |- $LOAD_PATH.unshift(File.expand_path("lib")) diff --git a/benchmark/from_small_integer.yml b/benchmark/from_small_integer.yml index 26ce4107..8219161a 100644 --- a/benchmark/from_small_integer.yml +++ b/benchmark/from_small_integer.yml @@ -2,7 +2,7 @@ loop_count: 100000 contexts: - gems: - bigdecimal: 3.0.0 + bigdecimal: 3.1.1 - name: "master" prelude: |- $LOAD_PATH.unshift(File.expand_path("lib")) From b946e79a3d4598076a3875603655f82ab375c852 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 2 Sep 2025 23:57:28 +0900 Subject: [PATCH 448/546] Implement BigDecimal#_decimal_shift for internal use (#324) * Implement BigDecimal#_decimal_shift for internal use * Use _decimal_shift in sqrt, log and exp calculation --- ext/bigdecimal/bigdecimal.c | 60 +++++++++++++++++++++++++++++- lib/bigdecimal.rb | 55 +++++++++++++-------------- test/bigdecimal/test_bigdecimal.rb | 17 +++++++++ 3 files changed, 102 insertions(+), 30 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 70cc4196..b63c6735 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -207,6 +207,7 @@ rbd_allocate_struct_zero(int sign, size_t const digits) static unsigned short VpGetException(void); static void VpSetException(unsigned short f); static void VpCheckException(Real *p, bool always); +static int AddExponent(Real *a, SIGNED_VALUE n); static VALUE CheckGetValue(BDVALUE v); static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); @@ -2453,6 +2454,63 @@ BigDecimal_inspect(VALUE self) return str; } +/* Returns self * 10**v without changing the precision. + * This method is currently for internal use. + * + * BigDecimal("0.123e10")._decimal_shift(20) #=> "0.123e30" + * BigDecimal("0.123e10")._decimal_shift(-20) #=> "0.123e-10" + */ +static VALUE +BigDecimal_decimal_shift(VALUE self, VALUE v) +{ + BDVALUE a, c; + ssize_t shift, exponentShift; + bool shiftDown; + size_t prec; + DECDIG ex, iex; + + a = GetBDValueMust(self); + shift = NUM2SSIZET(rb_to_int(v)); + + if (VpIsZero(a.real) || VpIsNaN(a.real) || VpIsInf(a.real) || shift == 0) return CheckGetValue(a); + + exponentShift = shift > 0 ? shift / BASE_FIG : (shift + 1) / BASE_FIG - 1; + shift -= exponentShift * BASE_FIG; + ex = 1; + for (int i = 0; i < shift; i++) ex *= 10; + shiftDown = a.real->frac[0] * (DECDIG_DBL)ex >= BASE; + iex = BASE / ex; + + prec = a.real->Prec + shiftDown; + c = NewZeroWrap(1, prec * BASE_FIG); + if (shift == 0) { + VpAsgn(c.real, a.real, 1); + } else if (shiftDown) { + DECDIG carry = 0; + exponentShift++; + for (size_t i = 0; i < a.real->Prec; i++) { + DECDIG v = a.real->frac[i]; + c.real->frac[i] = carry * ex + v / iex; + carry = v % iex; + } + c.real->frac[a.real->Prec] = carry * ex; + } else { + DECDIG carry = 0; + for (ssize_t i = a.real->Prec - 1; i >= 0; i--) { + DECDIG v = a.real->frac[i]; + c.real->frac[i] = v % iex * ex + carry; + carry = v / iex; + } + } + while (c.real->frac[prec - 1] == 0) prec--; + c.real->Prec = prec; + c.real->sign = a.real->sign; + c.real->exponent = a.real->exponent; + AddExponent(c.real, exponentShift); + RB_GC_GUARD(a.bigdecimal); + return CheckGetValue(c); +} + inline static int is_zero(VALUE x) { @@ -3564,6 +3622,7 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "infinite?", BigDecimal_IsInfinite, 0); rb_define_method(rb_cBigDecimal, "finite?", BigDecimal_IsFinite, 0); rb_define_method(rb_cBigDecimal, "truncate", BigDecimal_truncate, -1); + rb_define_method(rb_cBigDecimal, "_decimal_shift", BigDecimal_decimal_shift, 1); rb_define_method(rb_cBigDecimal, "_dump", BigDecimal_dump, -1); #ifdef BIGDECIMAL_USE_VP_TEST_METHODS @@ -3621,7 +3680,6 @@ enum op_sw { }; static int VpIsDefOP(Real *c, Real *a, Real *b, enum op_sw sw); -static int AddExponent(Real *a, SIGNED_VALUE n); static DECDIG VpAddAbs(Real *a,Real *b,Real *c); static DECDIG VpSubAbs(Real *a,Real *b,Real *c); static size_t VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, DECDIG *av, DECDIG *bv); diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index e1accd47..c8a00d77 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -197,7 +197,7 @@ def sqrt(prec) prec = [prec, n_digits].max ex = exponent / 2 - x = self.mult(BigDecimal("1e#{-ex * 2}"), n_significant_digits) + x = _decimal_shift(-2 * ex) y = BigDecimal(Math.sqrt(x.to_f)) precs = [prec + BigDecimal.double_fig] precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig @@ -205,7 +205,7 @@ def sqrt(prec) y = y.add(x.div(y, p), p).div(2, p) end y = y.mult(1, limit) if limit - y.mult(BigDecimal("1e#{ex}"), precs.first) + y._decimal_shift(ex) end end @@ -240,7 +240,7 @@ def self.log(x, prec) if x > 10 || x < 0.1 log10 = log(BigDecimal(10), prec) exponent = x.exponent - x *= BigDecimal("1e#{-x.exponent}") + x = x._decimal_shift(-exponent) if x < 0.3 x *= 10 exponent -= 1 @@ -299,33 +299,30 @@ def self.exp(x, prec) return BigDecimal(1) if x.zero? return BigDecimal(1).div(exp(-x, prec), prec) if x < 0 - BigDecimal.save_limit do - BigDecimal.limit(0) - # exp(x * 10**cnt) = exp(x)**(10**cnt) - cnt = x > 1 ? x.exponent : 0 - prec2 = prec + BigDecimal.double_fig + cnt - x *= BigDecimal("1e-#{cnt}") - xn = BigDecimal(1) - y = BigDecimal(1) - - # Taylor series for exp(x) around 0 - 1.step do |i| - n = prec2 + xn.exponent - break if n <= 0 || xn.zero? - x = x.mult(1, n) - xn = xn.mult(x, n).div(i, n) - y = y.add(xn, prec2) - end - - # calculate exp(x * 10**cnt) from exp(x) - # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10 - cnt.times do - y2 = y.mult(y, prec2) - y5 = y2.mult(y2, prec2).mult(y, prec2) - y = y5.mult(y5, prec2) - end + # exp(x * 10**cnt) = exp(x)**(10**cnt) + cnt = x > 1 ? x.exponent : 0 + prec2 = prec + BigDecimal.double_fig + cnt + x = x._decimal_shift(-cnt) + xn = BigDecimal(1) + y = BigDecimal(1) + + # Taylor series for exp(x) around 0 + 1.step do |i| + n = prec2 + xn.exponent + break if n <= 0 || xn.zero? + x = x.mult(1, n) + xn = xn.mult(x, n).div(i, n) + y = y.add(xn, prec2) + end - y.mult(1, prec) + # calculate exp(x * 10**cnt) from exp(x) + # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10 + cnt.times do + y2 = y.mult(y, prec2) + y5 = y2.mult(y2, prec2).mult(y, prec2) + y = y5.mult(y5, prec2) end + + y.mult(1, prec) end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 09f4e95f..77f5d278 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1490,6 +1490,23 @@ def test_sqrt_minimum_precision assert_in_delta(BigDecimal('0.' + '0' * 25 + '3' * 100), x.sqrt(1), BigDecimal('1e-125')) end + def test_internal_use_decimal_shift + assert_positive_infinite_calculation { BigDecimal::INFINITY._decimal_shift(10) } + assert_negative_infinite_calculation { NEGATIVE_INFINITY._decimal_shift(10) } + assert_nan_calculation { BigDecimal::NAN._decimal_shift(10) } + assert_equal(BigDecimal(0), BigDecimal(0)._decimal_shift(10)) + [ + BigDecimal('-1234.56789'), + BigDecimal('123456789.012345678987654321'), + BigDecimal('123456789012345.678987654321') + ].each do |num| + (0..20).each do |shift| + assert_equal(num * 10**shift, num._decimal_shift(shift)) + assert_equal(num / 10**shift, num._decimal_shift(-shift)) + end + end + end + def test_fix x = BigDecimal("1.1") assert_equal(1, x.fix) From 6177f46251e0346736258a47ae70157bd6b6d8c4 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 3 Sep 2025 16:14:07 +0900 Subject: [PATCH 449/546] In JRuby, don't add sqrt, exp, log, power implemented in ruby (#417) Ruby-implemented sqrt, exp, log, power depends on new feature only implemented in bigdecimal.c: `BigDecimal(float_without_prec)` and `BigDecimal#_decimal_shift` --- lib/bigdecimal.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index c8a00d77..41f3890b 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -1,5 +1,6 @@ if RUBY_ENGINE == 'jruby' JRuby::Util.load_ext("org.jruby.ext.bigdecimal.BigDecimalLibrary") + return else require 'bigdecimal.so' end From b458f941ef5106012ce995e5956a3aa7edfc08cb Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 3 Sep 2025 17:10:09 +0900 Subject: [PATCH 450/546] Add JRuby minimum ci (#418) --- .github/workflows/jruby_test.yml | 40 +++++++++++++++++++++++ test/bigdecimal/test_jruby.rb | 54 ++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 .github/workflows/jruby_test.yml create mode 100644 test/bigdecimal/test_jruby.rb diff --git a/.github/workflows/jruby_test.yml b/.github/workflows/jruby_test.yml new file mode 100644 index 00000000..c82ed5ba --- /dev/null +++ b/.github/workflows/jruby_test.yml @@ -0,0 +1,40 @@ +name: JRuby-test + +on: + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + - reopened + +jobs: + host: + name: ${{ matrix.ruby }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ruby: + - jruby + - jruby-head + + steps: + - uses: actions/checkout@v5 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - run: bundle install + + - run: rake compile + + - run: rake test TEST=test/bigdecimal/test_jruby.rb + + - run: rake build + + - run: gem install pkg/*.gem diff --git a/test/bigdecimal/test_jruby.rb b/test/bigdecimal/test_jruby.rb new file mode 100644 index 00000000..24ce6e91 --- /dev/null +++ b/test/bigdecimal/test_jruby.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: false +require_relative 'helper' +require 'bigdecimal/math' + +class TestJRuby < Test::Unit::TestCase + # JRuby uses its own native BigDecimal implementation + # but uses the same BigMath module as CRuby. + # These are test to ensure BigMath works correctly with JRuby's BigDecimal. + # Also run on CRuby to ensure compatibility. + + N = 20 + + def test_sqrt + sqrt2 = BigDecimal(2).sqrt(N) + assert_in_delta(Math.sqrt(2), sqrt2) + assert_in_delta(2, sqrt2 * sqrt2) + end + + def test_exp + assert_in_delta(Math.exp(2), BigMath.exp(BigDecimal(2), N)) + assert_in_delta(Math.exp(2), BigMath.exp(2, N)) + assert_in_delta(Math.exp(2.5), BigMath.exp(2.5, N)) + assert_in_delta(Math.exp(2.5), BigMath.exp(2.5r, N)) + end + + def test_log + assert_in_delta(Math.log(2), BigMath.log(BigDecimal(2), N)) + assert_in_delta(Math.log(2), BigMath.log(2, N)) + assert_in_delta(Math.log(2.5), BigMath.log(2.5, N)) + assert_in_delta(Math.log(2.5), BigMath.log(2.5r, N)) + end + + def test_power + x = BigDecimal(2) + expected = 2 ** 2.5 + assert_in_delta(expected, x ** BigDecimal('2.5')) + assert_in_delta(expected, x.sqrt(N) ** 5) + # assert_in_delta(expected, x ** 2.5) + assert_in_delta(expected, x ** 2.5r) + assert_in_delta(expected, x.power(BigDecimal('2.5'), N)) + # assert_in_delta(expected, x.power(2.5, N)) + assert_in_delta(expected, x.sqrt(N).power(5, N)) + assert_in_delta(expected, x.power(2.5r, N)) + end + + def test_bigmath + assert_in_delta(Math.sqrt(2), BigMath.sqrt(BigDecimal(2), N)) + assert_in_delta(Math.sin(1), BigMath.sin(BigDecimal(1), N)) + assert_in_delta(Math.cos(1), BigMath.cos(BigDecimal(1), N)) + assert_in_delta(Math.atan(1), BigMath.atan(BigDecimal(1), N)) + assert_in_delta(Math::PI, BigMath.PI(N)) + assert_in_delta(Math::E, BigMath.E(N)) + end +end From dd7738ccab67996105d5998b55258d50f5531290 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 3 Sep 2025 21:24:53 +0900 Subject: [PATCH 451/546] Bump version to 3.2.3 (#419) * Add 3.2.3 changes * Bump version to 3.2.3 --- CHANGES.md | 16 ++++++++++++++++ ext/bigdecimal/bigdecimal.c | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index cdb6d36f..52948639 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,21 @@ # CHANGES +## 3.2.3 + +* Allow BigDecimal accept Float without precision [GH-314] + + **@mrzasa** + +* Ruby implementation pow, log, exp and sqrt [GH-347] [GH-381] + + **@tompng** + +* Update document [GH-348] [GH-360] [GH-365] + + **@timcraft** **@dduugg** **@mame** + +* Lots of bug fixes and refactoring + ## 3.2.2 * Make precision calculation in bigdecimal.div(value, 0) gc-compaction safe. [GH-340] diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b63c6735..4621d22b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.2.2" +#define BIGDECIMAL_VERSION "3.2.3" /* #define ENABLE_NUMERIC_STRING */ From d9af2783e48f5e4c3840640dcc23c6a10be96402 Mon Sep 17 00:00:00 2001 From: Felipe Sateler Date: Tue, 9 Sep 2025 08:36:07 -0300 Subject: [PATCH 452/546] Allow calling Rational#to_d without arguments (#421) Then the precision would be 0, just like with Float. Apply the same change to Complex, which had a different validation --- lib/bigdecimal/util.rb | 29 +++++++++++++------------ test/bigdecimal/test_bigdecimal_util.rb | 7 +++++- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index cb514435..7c5f32eb 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -119,8 +119,11 @@ class Rational < Numeric # # Returns the value as a BigDecimal. # - # The required +precision+ parameter is used to determine the number of - # significant digits for the result. + # The +precision+ parameter is used to determine the number of + # significant digits for the result. When +precision+ is set to +0+, + # the number of digits to represent the float being converted is determined + # automatically. + # The default +precision+ is +0+. # # require 'bigdecimal' # require 'bigdecimal/util' @@ -129,7 +132,7 @@ class Rational < Numeric # # See also Kernel.BigDecimal. # - def to_d(precision) + def to_d(precision=0) BigDecimal(self, precision) end end @@ -141,29 +144,27 @@ class Complex < Numeric # cmp.to_d(precision) -> bigdecimal # # Returns the value as a BigDecimal. + # If the imaginary part is not +0+, an error is raised # - # The +precision+ parameter is required for a rational complex number. - # This parameter is used to determine the number of significant digits - # for the result. + # The +precision+ parameter is used to determine the number of + # significant digits for the result. When +precision+ is set to +0+, + # the number of digits to represent the float being converted is determined + # automatically. + # The default +precision+ is +0+. # # require 'bigdecimal' # require 'bigdecimal/util' # # Complex(0.1234567, 0).to_d(4) # => 0.1235e0 # Complex(Rational(22, 7), 0).to_d(3) # => 0.314e1 + # Complex(1, 1).to_d # raises ArgumentError # # See also Kernel.BigDecimal. # - def to_d(*args) + def to_d(precision=0) BigDecimal(self) unless self.imag.zero? # to raise error - if args.length == 0 - case self.real - when Rational - BigDecimal(self.real) # to raise error - end - end - self.real.to_d(*args) + BigDecimal(self.real, precision) end end diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 2f27163e..19790287 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -84,6 +84,11 @@ def test_Rational_to_d assert(355.quo(113).to_d(digits).frozen?) end + def test_Rational_to_d_without_precision + assert_equal(BigDecimal("1.25"), Rational(5, 4).to_d) + assert_equal(BigDecimal(355.quo(113), 0), 355.quo(113).to_d) + end + def test_Rational_to_d_with_zero_precision assert_equal(BigDecimal(355.quo(113), 0), 355.quo(113).to_d(0)) end @@ -102,7 +107,7 @@ def test_Complex_to_d assert_equal(BigDecimal("0.1234567"), Complex(0.1234567, 0).to_d) assert_equal(BigDecimal("0.1235"), Complex(0.1234567, 0).to_d(4)) - assert_raise_with_message(ArgumentError, "can't omit precision for a Rational.") { Complex(1.quo(3), 0).to_d } + assert_equal(BigDecimal("0.5"), Complex(1.quo(2), 0).to_d) assert_raise_with_message(ArgumentError, "Unable to make a BigDecimal from non-zero imaginary number") { Complex(1, 1).to_d } end From 021c554360a6d9c07227fe30c0c54ccb3a8cd7f0 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 9 Sep 2025 21:09:36 +0900 Subject: [PATCH 453/546] Fix test_no_memory_leak failure (#424) Avoid memory leak when TypedData_Wrap_Struct fail. First, prepare TypedData_Wrap_Struct with NULL. Next, allocate Real. Finally, bind VALUE and Real. --- ext/bigdecimal/bigdecimal.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4621d22b..1eca3ef2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -252,10 +252,21 @@ static const rb_data_type_t BigDecimal_data_type = { #endif }; +// TypedData_Wrap_Struct may fail if there is no memory, or GC.add_stress_to_class(BigDecimal) is set. +// We need to first allocate empty struct, allocate Real struct, and then set the data pointer. +typedef struct { VALUE _obj; } NULL_WRAPPED_VALUE; +static NULL_WRAPPED_VALUE +BigDecimal_alloc_empty_struct(VALUE klass) +{ + return (NULL_WRAPPED_VALUE) { TypedData_Wrap_Struct(klass, &BigDecimal_data_type, NULL) }; +} + static VALUE -BigDecimal_wrap_struct(VALUE klass, Real *real) +BigDecimal_wrap_struct(NULL_WRAPPED_VALUE v, Real *real) { - VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, real); + VALUE obj = v._obj; + assert(RTYPEDDATA_DATA(obj) == NULL); + RTYPEDDATA_DATA(obj) = real; RB_OBJ_FREEZE(obj); return obj; } @@ -265,8 +276,9 @@ MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_wrap(int sign, size_ static BDVALUE rbd_allocate_struct_zero_wrap(int sign, size_t const digits) { + NULL_WRAPPED_VALUE null_wrapped = BigDecimal_alloc_empty_struct(rb_cBigDecimal); Real *real = rbd_allocate_struct_zero(sign, digits); - return (BDVALUE) { BigDecimal_wrap_struct(rb_cBigDecimal, real), real }; + return (BDVALUE) { BigDecimal_wrap_struct(null_wrapped, real), real }; } static inline int @@ -1041,9 +1053,10 @@ check_int_precision(VALUE v) static NULLABLE_BDVALUE CreateFromString(const char *str, VALUE klass, bool strict_p, bool raise_exception) { + NULL_WRAPPED_VALUE null_wrapped = BigDecimal_alloc_empty_struct(klass); Real *pv = VpAlloc(str, strict_p, raise_exception); if (!pv) return (NULLABLE_BDVALUE) { Qnil, NULL }; - return (NULLABLE_BDVALUE) { BigDecimal_wrap_struct(klass, pv), pv }; + return (NULLABLE_BDVALUE) { BigDecimal_wrap_struct(null_wrapped, pv), pv }; } static Real * @@ -2580,6 +2593,7 @@ check_exception(VALUE bd) static VALUE rb_uint64_convert_to_BigDecimal(uint64_t uval) { + NULL_WRAPPED_VALUE null_wrapped = BigDecimal_alloc_empty_struct(rb_cBigDecimal); Real *vp; if (uval == 0) { vp = rbd_allocate_struct(1); @@ -2621,7 +2635,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval) MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - len, DECDIG, len); } - return BigDecimal_wrap_struct(rb_cBigDecimal, vp); + return BigDecimal_wrap_struct(null_wrapped, vp); } static VALUE @@ -2905,12 +2919,13 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) if (digs == SIZE_MAX) return check_exception(val); + NULL_WRAPPED_VALUE null_wrapped = BigDecimal_alloc_empty_struct(rb_cBigDecimal); Real *vp; TypedData_Get_Struct(val, Real, &BigDecimal_data_type, vp); vp = VpCopy(NULL, vp); RB_GC_GUARD(val); - VALUE copy = BigDecimal_wrap_struct(rb_cBigDecimal, vp); + VALUE copy = BigDecimal_wrap_struct(null_wrapped, vp); /* TODO: rounding */ return check_exception(copy); } From 514d36c647612c74ad25059b71b4f4d6283d76ae Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 11 Sep 2025 22:28:18 +0900 Subject: [PATCH 454/546] BigMath.sin and cos now always calculate in relative precision (#422) --- lib/bigdecimal/math.rb | 78 ++++++++++++++------------------- test/bigdecimal/helper.rb | 16 +------ test/bigdecimal/test_bigmath.rb | 18 ++++---- 3 files changed, 44 insertions(+), 68 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index b264c8b8..6bc0f50e 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -43,6 +43,33 @@ def sqrt(x, prec) x.sqrt(prec) end + + # Returns [sign, reduced_x] where reduced_x is in -pi/2..pi/2 + # and satisfies sin(x) = sign * sin(reduced_x) + # If add_half_pi is true, adds pi/2 to x before reduction. + # Precision of pi is adjusted to ensure reduced_x has the required precision. + private def _sin_periodic_reduction(x, prec, add_half_pi: false) + return [1, x] if -Math::PI/2 <= x && x <= Math::PI/2 && !add_half_pi + + mod_prec = prec + BigDecimal.double_fig + pi_extra_prec = [x.exponent, 0].max + BigDecimal.double_fig + while true + pi = PI(mod_prec + pi_extra_prec) + half_pi = pi / 2 + div, mod = (add_half_pi ? x + pi : x + half_pi).divmod(pi) + mod -= half_pi + if mod.zero? || mod_prec + mod.exponent <= 0 + # mod is too small to estimate required pi precision + mod_prec = mod_prec * 3 / 2 + BigDecimal.double_fig + elsif mod_prec + mod.exponent < prec + # Estimate required precision of pi + mod_prec = prec - mod.exponent + BigDecimal.double_fig + else + return [div % 2 == 0 ? 1 : -1, mod.mult(1, prec)] + end + end + end + # call-seq: # sin(decimal, numeric) -> BigDecimal # @@ -60,33 +87,23 @@ def sin(x, prec) n = prec + BigDecimal.double_fig one = BigDecimal("1") two = BigDecimal("2") - x = -x if neg = x < 0 - if x > 6 - twopi = two * BigMath.PI(prec + x.exponent) - if x > 30 - x %= twopi - else - x -= twopi while x > twopi - end - end + sign, x = _sin_periodic_reduction(x, n) x1 = x x2 = x.mult(x,n) - sign = 1 y = x d = y i = one z = one while d.nonzero? && ((m = n - (y.exponent - d.exponent).abs) > 0) m = BigDecimal.double_fig if m < BigDecimal.double_fig - sign = -sign - x1 = x2.mult(x1,n) + x1 = -x2.mult(x1,n) i += two z *= (i-one) * i - d = sign * x1.div(z,m) + d = x1.div(z,m) y += d end - y = BigDecimal("1") if y > 1 - neg ? -y : y + y *= sign + y < -1 ? BigDecimal("-1") : y > 1 ? BigDecimal("1") : y end # call-seq: @@ -103,35 +120,8 @@ def sin(x, prec) def cos(x, prec) raise ArgumentError, "Zero or negative precision for cos" if prec <= 0 return BigDecimal("NaN") if x.infinite? || x.nan? - n = prec + BigDecimal.double_fig - one = BigDecimal("1") - two = BigDecimal("2") - x = -x if x < 0 - if x > 6 - twopi = two * BigMath.PI(prec + x.exponent) - if x > 30 - x %= twopi - else - x -= twopi while x > twopi - end - end - x1 = one - x2 = x.mult(x,n) - sign = 1 - y = one - d = y - i = BigDecimal("0") - z = one - while d.nonzero? && ((m = n - (y.exponent - d.exponent).abs) > 0) - m = BigDecimal.double_fig if m < BigDecimal.double_fig - sign = -sign - x1 = x2.mult(x1,n) - i += two - z *= (i-one) * i - d = sign * x1.div(z,m) - y += d - end - y < -1 ? BigDecimal("-1") : y > 1 ? BigDecimal("1") : y + sign, x = _sin_periodic_reduction(x, prec + BigDecimal.double_fig, add_half_pi: true) + sign * sin(x, prec) end # call-seq: diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 5428e46f..6989cc77 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -40,25 +40,11 @@ def under_gc_stress # Asserts that the calculation of the given block converges to some value # with precision specified by block parameter. - def assert_fixed_point_precision(&block) - _assert_precision(:fixed_point, &block) - end - def assert_relative_precision(&block) - _assert_precision(:relative, &block) - end - - def _assert_precision(mode) expected = yield(200) [50, 100, 150].each do |n| value = yield(n) - if mode == :fixed_point - precision = -(value - expected).exponent - elsif mode == :relative - precision = 1 - (value.div(expected, expected.precision) - 1).exponent - else - raise ArgumentError, "Unknown mode: #{mode}" - end + precision = 1 - (value.div(expected, expected.precision) - 1).exponent assert(value != expected, "Unable to estimate precision for exact value") assert(precision >= n, "Precision is not enough: #{precision} < #{n}") end diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 7a3dcb0a..2863ee88 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -59,11 +59,11 @@ def test_sin assert_in_delta(BigDecimal('0.5'), sin(PI(100) / 6, 100), BigDecimal("1e-100")) assert_in_delta(SQRT3 / 2, sin(PI(100) / 3, 100), BigDecimal("1e-100")) assert_in_delta(SQRT2 / 2, sin(PI(100) / 4, 100), BigDecimal("1e-100")) - assert_fixed_point_precision {|n| sin(BigDecimal("1"), n) } - assert_fixed_point_precision {|n| sin(BigDecimal("1e50"), n) } - assert_fixed_point_precision {|n| sin(BigDecimal("1e-30"), n) } - assert_fixed_point_precision {|n| sin(BigDecimal(PI(50)), n) } - assert_fixed_point_precision {|n| sin(BigDecimal(PI(50) * 100), n) } + assert_relative_precision {|n| sin(BigDecimal("1"), n) } + assert_relative_precision {|n| sin(BigDecimal("1e50"), n) } + assert_relative_precision {|n| sin(BigDecimal("1e-30"), n) } + assert_relative_precision {|n| sin(BigDecimal(PI(50)), n) } + assert_relative_precision {|n| sin(BigDecimal(PI(50) * 100), n) } assert_operator(sin(PI(30) / 2, 30), :<=, 1) assert_operator(sin(-PI(30) / 2, 30), :>=, -1) end @@ -83,10 +83,10 @@ def test_cos assert_in_delta(BigDecimal('0.5'), cos(PI(100) / 3, 100), BigDecimal("1e-100")) assert_in_delta(SQRT3 / 2, cos(PI(100) / 6, 100), BigDecimal("1e-100")) assert_in_delta(SQRT2 / 2, cos(PI(100) / 4, 100), BigDecimal("1e-100")) - assert_fixed_point_precision {|n| cos(BigDecimal("1"), n) } - assert_fixed_point_precision {|n| cos(BigDecimal("1e50"), n) } - assert_fixed_point_precision {|n| cos(BigDecimal(PI(50) / 2), n) } - assert_fixed_point_precision {|n| cos(BigDecimal(PI(50) * 201 / 2), n) } + assert_relative_precision {|n| cos(BigDecimal("1"), n) } + assert_relative_precision {|n| cos(BigDecimal("1e50"), n) } + assert_relative_precision {|n| cos(BigDecimal(PI(50) / 2), n) } + assert_relative_precision {|n| cos(BigDecimal(PI(50) * 201 / 2), n) } assert_operator(cos(PI(30), 30), :>=, -1) assert_operator(cos(PI(30) * 2, 30), :<=, 1) end From 69d764110b719ca3f3e545992f1a24c467805d09 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 11 Sep 2025 22:35:17 +0900 Subject: [PATCH 455/546] Faster exp calculation (#399) Use exp(x) = exp(x.round(k)) * exp(x - x.round(k)). exp(x.round(k)) is fast because calculation of x**n is fast. exp(x-x.round(k) is fast because x**n/n! converges fast. --- lib/bigdecimal.rb | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 41f3890b..52251783 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -282,6 +282,19 @@ def self.log(x, prec) end end + # Taylor series for exp(x) around 0 + private_class_method def self._exp_taylor(x, prec) # :nodoc: + xn = BigDecimal(1) + y = BigDecimal(1) + 1.step do |i| + n = prec + xn.exponent + break if n <= 0 || xn.zero? + xn = xn.mult(x, n).div(i, n) + y = y.add(xn, prec) + end + y + end + # call-seq: # BigMath.exp(decimal, numeric) -> BigDecimal # @@ -298,23 +311,17 @@ def self.exp(x, prec) return BigDecimal::Internal.nan_computation_result if x.nan? return x.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) if x.infinite? return BigDecimal(1) if x.zero? - return BigDecimal(1).div(exp(-x, prec), prec) if x < 0 # exp(x * 10**cnt) = exp(x)**(10**cnt) - cnt = x > 1 ? x.exponent : 0 + cnt = x < -1 || x > 1 ? x.exponent : 0 prec2 = prec + BigDecimal.double_fig + cnt x = x._decimal_shift(-cnt) - xn = BigDecimal(1) - y = BigDecimal(1) - # Taylor series for exp(x) around 0 - 1.step do |i| - n = prec2 + xn.exponent - break if n <= 0 || xn.zero? - x = x.mult(1, n) - xn = xn.mult(x, n).div(i, n) - y = y.add(xn, prec2) - end + # Calculation of exp(small_prec) is fast because calculation of x**n is fast + # Calculation of exp(small_abs) converges fast. + # exp(x) = exp(small_prec_part + small_abs_part) = exp(small_prec_part) * exp(small_abs_part) + x_small_prec = x.round(Integer.sqrt(prec2)) + y = _exp_taylor(x_small_prec, prec2).mult(_exp_taylor(x.sub(x_small_prec, prec2), prec2), prec2) # calculate exp(x * 10**cnt) from exp(x) # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10 From 23144a7abd3b1f31194244da0221572b1660d7c8 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 12 Sep 2025 02:28:17 +0900 Subject: [PATCH 456/546] Rename assert_relative_precision (#425) Now that assert_fixed_point_precision is removed and there is only assert_relative_precision, "relative" part is not important anymore. Change it to `assert_converge_in_precision` which express the assertion more accurate. --- test/bigdecimal/helper.rb | 2 +- test/bigdecimal/test_bigdecimal.rb | 30 ++++++++--------- test/bigdecimal/test_bigmath.rb | 54 +++++++++++++++--------------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 6989cc77..ec309880 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -40,7 +40,7 @@ def under_gc_stress # Asserts that the calculation of the given block converges to some value # with precision specified by block parameter. - def assert_relative_precision(&block) + def assert_converge_in_precision(&block) expected = yield(200) [50, 100, 150].each do |n| value = yield(n) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 77f5d278..c3942617 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2011,21 +2011,21 @@ def test_power_precision y = BigDecimal("3.14159265358979323846264338327950288419716939937511") small = x * BigDecimal("1e-30") large = y * BigDecimal("1e+30") - assert_relative_precision {|n| small.power(small, n) } - assert_relative_precision {|n| large.power(small, n) } - assert_relative_precision {|n| x.power(small, n) } - assert_relative_precision {|n| small.power(y, n) } - assert_relative_precision {|n| small.power(small + 1, n) } - assert_relative_precision {|n| x.power(small + 1, n) } - assert_relative_precision {|n| (small + 1).power(small, n) } - assert_relative_precision {|n| (small + 1).power(large, n) } - assert_relative_precision {|n| (small + 1).power(y, n) } - assert_relative_precision {|n| x.power(y, n) } - assert_relative_precision {|n| x.power(-y, n) } - assert_relative_precision {|n| x.power(123, n) } - assert_relative_precision {|n| x.power(-456, n) } - assert_relative_precision {|n| (x + 12).power(y + 34, n) } - assert_relative_precision {|n| (x + 56).power(y - 78, n) } + assert_converge_in_precision {|n| small.power(small, n) } + assert_converge_in_precision {|n| large.power(small, n) } + assert_converge_in_precision {|n| x.power(small, n) } + assert_converge_in_precision {|n| small.power(y, n) } + assert_converge_in_precision {|n| small.power(small + 1, n) } + assert_converge_in_precision {|n| x.power(small + 1, n) } + assert_converge_in_precision {|n| (small + 1).power(small, n) } + assert_converge_in_precision {|n| (small + 1).power(large, n) } + assert_converge_in_precision {|n| (small + 1).power(y, n) } + assert_converge_in_precision {|n| x.power(y, n) } + assert_converge_in_precision {|n| x.power(-y, n) } + assert_converge_in_precision {|n| x.power(123, n) } + assert_converge_in_precision {|n| x.power(-456, n) } + assert_converge_in_precision {|n| (x + 12).power(y + 34, n) } + assert_converge_in_precision {|n| (x + 56).power(y - 78, n) } end def test_limit diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 2863ee88..a3432ec4 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -18,7 +18,7 @@ def test_pi BigDecimal("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068"), PI(100).round(100) ) - assert_relative_precision {|n| PI(n) } + assert_converge_in_precision {|n| PI(n) } end def test_e @@ -26,7 +26,7 @@ def test_e BigDecimal("2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427"), E(100) ) - assert_relative_precision {|n| E(n) } + assert_converge_in_precision {|n| E(n) } end def test_sqrt @@ -39,9 +39,9 @@ def test_sqrt assert_raise(FloatDomainError) {sqrt(PINF, N)} assert_in_delta(SQRT2, sqrt(BigDecimal("2"), 100), BigDecimal("1e-100")) assert_in_delta(SQRT3, sqrt(BigDecimal("3"), 100), BigDecimal("1e-100")) - assert_relative_precision {|n| sqrt(BigDecimal("2"), n) } - assert_relative_precision {|n| sqrt(BigDecimal("2e-50"), n) } - assert_relative_precision {|n| sqrt(BigDecimal("2e50"), n) } + assert_converge_in_precision {|n| sqrt(BigDecimal("2"), n) } + assert_converge_in_precision {|n| sqrt(BigDecimal("2e-50"), n) } + assert_converge_in_precision {|n| sqrt(BigDecimal("2e50"), n) } end def test_sin @@ -59,11 +59,11 @@ def test_sin assert_in_delta(BigDecimal('0.5'), sin(PI(100) / 6, 100), BigDecimal("1e-100")) assert_in_delta(SQRT3 / 2, sin(PI(100) / 3, 100), BigDecimal("1e-100")) assert_in_delta(SQRT2 / 2, sin(PI(100) / 4, 100), BigDecimal("1e-100")) - assert_relative_precision {|n| sin(BigDecimal("1"), n) } - assert_relative_precision {|n| sin(BigDecimal("1e50"), n) } - assert_relative_precision {|n| sin(BigDecimal("1e-30"), n) } - assert_relative_precision {|n| sin(BigDecimal(PI(50)), n) } - assert_relative_precision {|n| sin(BigDecimal(PI(50) * 100), n) } + assert_converge_in_precision {|n| sin(BigDecimal("1"), n) } + assert_converge_in_precision {|n| sin(BigDecimal("1e50"), n) } + assert_converge_in_precision {|n| sin(BigDecimal("1e-30"), n) } + assert_converge_in_precision {|n| sin(BigDecimal(PI(50)), n) } + assert_converge_in_precision {|n| sin(BigDecimal(PI(50) * 100), n) } assert_operator(sin(PI(30) / 2, 30), :<=, 1) assert_operator(sin(-PI(30) / 2, 30), :>=, -1) end @@ -83,10 +83,10 @@ def test_cos assert_in_delta(BigDecimal('0.5'), cos(PI(100) / 3, 100), BigDecimal("1e-100")) assert_in_delta(SQRT3 / 2, cos(PI(100) / 6, 100), BigDecimal("1e-100")) assert_in_delta(SQRT2 / 2, cos(PI(100) / 4, 100), BigDecimal("1e-100")) - assert_relative_precision {|n| cos(BigDecimal("1"), n) } - assert_relative_precision {|n| cos(BigDecimal("1e50"), n) } - assert_relative_precision {|n| cos(BigDecimal(PI(50) / 2), n) } - assert_relative_precision {|n| cos(BigDecimal(PI(50) * 201 / 2), n) } + assert_converge_in_precision {|n| cos(BigDecimal("1"), n) } + assert_converge_in_precision {|n| cos(BigDecimal("1e50"), n) } + assert_converge_in_precision {|n| cos(BigDecimal(PI(50) / 2), n) } + assert_converge_in_precision {|n| cos(BigDecimal(PI(50) * 201 / 2), n) } assert_operator(cos(PI(30), 30), :>=, -1) assert_operator(cos(PI(30) * 2, 30), :<=, 1) end @@ -99,9 +99,9 @@ def test_atan assert_in_delta(PI(100) / 3, atan(SQRT3, 100), BigDecimal("1e-100")) assert_equal(BigDecimal("0.823840753418636291769355073102514088959345624027952954058347023122539489"), atan(BigDecimal("1.08"), 72).round(72), '[ruby-dev:41257]') - assert_relative_precision {|n| atan(BigDecimal("2"), n)} - assert_relative_precision {|n| atan(BigDecimal("1e-30"), n)} - assert_relative_precision {|n| atan(BigDecimal("1e30"), n)} + assert_converge_in_precision {|n| atan(BigDecimal("2"), n)} + assert_converge_in_precision {|n| atan(BigDecimal("1e-30"), n)} + assert_converge_in_precision {|n| atan(BigDecimal("1e30"), n)} end def test_exp @@ -111,11 +111,11 @@ def test_exp assert_equal(1, BigMath.exp(BigDecimal("0"), N)) assert_in_epsilon(BigDecimal("4.48168907033806482260205546011927581900574986836966705677265008278593667446671377298105383138245339138861635065183019577"), BigMath.exp(BigDecimal("1.5"), 100), BigDecimal("1e-100")) - assert_relative_precision {|n| BigMath.exp(BigDecimal("1"), n) } - assert_relative_precision {|n| BigMath.exp(BigDecimal("-2"), n) } - assert_relative_precision {|n| BigMath.exp(BigDecimal("-34"), n) } - assert_relative_precision {|n| BigMath.exp(BigDecimal("567"), n) } - assert_relative_precision {|n| BigMath.exp(SQRT2, n) } + assert_converge_in_precision {|n| BigMath.exp(BigDecimal("1"), n) } + assert_converge_in_precision {|n| BigMath.exp(BigDecimal("-2"), n) } + assert_converge_in_precision {|n| BigMath.exp(BigDecimal("-34"), n) } + assert_converge_in_precision {|n| BigMath.exp(BigDecimal("567"), n) } + assert_converge_in_precision {|n| BigMath.exp(SQRT2, n) } end def test_log @@ -123,11 +123,11 @@ def test_log assert_in_epsilon(Math.log(10)*1000, BigMath.log(BigDecimal("1e1000"), 10)) assert_in_epsilon(BigDecimal("2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862"), BigMath.log(BigDecimal("10"), 100), BigDecimal("1e-100")) - assert_relative_precision {|n| BigMath.log(BigDecimal("2"), n) } - assert_relative_precision {|n| BigMath.log(BigDecimal("1e-30") + 1, n) } - assert_relative_precision {|n| BigMath.log(BigDecimal("1e-30"), n) } - assert_relative_precision {|n| BigMath.log(BigDecimal("1e30"), n) } - assert_relative_precision {|n| BigMath.log(SQRT2, n) } + assert_converge_in_precision {|n| BigMath.log(BigDecimal("2"), n) } + assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e-30") + 1, n) } + assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e-30"), n) } + assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e30"), n) } + assert_converge_in_precision {|n| BigMath.log(SQRT2, n) } assert_raise(Math::DomainError) {BigMath.log(BigDecimal("0"), 10)} assert_raise(Math::DomainError) {BigMath.log(BigDecimal("-1"), 10)} assert_separately(%w[-rbigdecimal], <<-SRC) From cebd1a591df942e56a550c938bb30b4c1d109b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Fri, 12 Sep 2025 16:03:28 +0200 Subject: [PATCH 457/546] Add support for tangent function (#231) * Add support for tangent function * Apply review comments for tests * Make tan depend only on sin * Use Ziv's loop method suggested in review * Test failing test * Use first implementation based on sin and cos * Remove unused code --- lib/bigdecimal/math.rb | 19 +++++++++++++++++++ test/bigdecimal/test_bigmath.rb | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 6bc0f50e..5407bf0a 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -7,6 +7,7 @@ # sqrt(x, prec) # sin (x, prec) # cos (x, prec) +# tan (x, prec) # atan(x, prec) # PI (prec) # E (prec) == exp(1.0,prec) @@ -124,6 +125,24 @@ def cos(x, prec) sign * sin(x, prec) end + # call-seq: + # tan(decimal, numeric) -> BigDecimal + # + # Computes the tangent of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is Infinity or NaN, returns NaN. + # + # BigMath.tan(BigDecimal("0.0"), 4).to_s + # #=> "0.0" + # + # BigMath.tan(BigMath.PI(4) / 4, 4).to_s + # #=> "0.99999999999999999999419869652481995799388629632650769e0" + # + def tan(x, prec) + sin(x, prec) / cos(x, prec) + end + # call-seq: # atan(decimal, numeric) -> BigDecimal # diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index a3432ec4..c5d2e567 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -91,6 +91,17 @@ def test_cos assert_operator(cos(PI(30) * 2, 30), :<=, 1) end + def test_tan + assert_in_delta(0.0, tan(BigDecimal("0.0"), N)) + assert_in_delta(0.0, tan(PI(N), N)) + assert_in_delta(1.0, tan(PI(N) / 4, N)) + assert_in_delta(sqrt(BigDecimal(3), N), tan(PI(N) / 3, N)) + assert_in_delta(sqrt(BigDecimal(3), 10 * N), tan(PI(10 * N) / 3, 10 * N)) + assert_in_delta(0.0, tan(-PI(N), N)) + assert_in_delta(-1.0, tan(-PI(N) / 4, N)) + assert_in_delta(-sqrt(BigDecimal(3), N), tan(-PI(N) / 3, N)) + end + def test_atan assert_equal(0.0, atan(BigDecimal("0.0"), N)) assert_in_delta(Math::PI/4, atan(BigDecimal("1.0"), N)) From 92a9d7ea6c2e7ac11cc4202f6fd7170afb6e3fe5 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 12 Sep 2025 23:24:14 +0900 Subject: [PATCH 458/546] Make bigdecimal.rb work in JRuby (#420) Use BigDecimal(float, 0) instead of BigDecimal(float). Add a ruby-implemented polyfill of BigDecimal#_decimal_shift. This is needed to let math.rb depend on bigdecimal.rb utility methods. --- lib/bigdecimal.rb | 11 ++++++++--- test/bigdecimal/test_jruby.rb | 11 +++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 52251783..fb51ba8d 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -1,6 +1,11 @@ if RUBY_ENGINE == 'jruby' JRuby::Util.load_ext("org.jruby.ext.bigdecimal.BigDecimalLibrary") - return + + class BigDecimal + def _decimal_shift(i) # :nodoc: + to_java.move_point_right(i).to_d + end + end else require 'bigdecimal.so' end @@ -15,7 +20,7 @@ def self.coerce_to_bigdecimal(x, prec, method_name) # :nodoc: when BigDecimal return x when Integer, Float - return BigDecimal(x) + return BigDecimal(x, 0) when Rational return BigDecimal(x, [prec, 2 * BigDecimal.double_fig].max) end @@ -199,7 +204,7 @@ def sqrt(prec) ex = exponent / 2 x = _decimal_shift(-2 * ex) - y = BigDecimal(Math.sqrt(x.to_f)) + y = BigDecimal(Math.sqrt(x.to_f), 0) precs = [prec + BigDecimal.double_fig] precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig precs.reverse_each do |p| diff --git a/test/bigdecimal/test_jruby.rb b/test/bigdecimal/test_jruby.rb index 24ce6e91..8641d8dd 100644 --- a/test/bigdecimal/test_jruby.rb +++ b/test/bigdecimal/test_jruby.rb @@ -10,6 +10,13 @@ class TestJRuby < Test::Unit::TestCase N = 20 + def test_decimal_shift_polyfill + assert_equal(BigDecimal('123.45e2'), BigDecimal('123.45')._decimal_shift(2)) + assert_equal(BigDecimal('123.45e-2'), BigDecimal('123.45')._decimal_shift(-2)) + assert_equal(BigDecimal('123.45e10000'), BigDecimal('123.45')._decimal_shift(10000)) + assert_equal(BigDecimal('123.45e-10000'), BigDecimal('123.45')._decimal_shift(-10000)) + end + def test_sqrt sqrt2 = BigDecimal(2).sqrt(N) assert_in_delta(Math.sqrt(2), sqrt2) @@ -35,10 +42,10 @@ def test_power expected = 2 ** 2.5 assert_in_delta(expected, x ** BigDecimal('2.5')) assert_in_delta(expected, x.sqrt(N) ** 5) - # assert_in_delta(expected, x ** 2.5) + assert_in_delta(expected, x ** 2.5) assert_in_delta(expected, x ** 2.5r) assert_in_delta(expected, x.power(BigDecimal('2.5'), N)) - # assert_in_delta(expected, x.power(2.5, N)) + assert_in_delta(expected, x.power(2.5, N)) assert_in_delta(expected, x.sqrt(N).power(5, N)) assert_in_delta(expected, x.power(2.5r, N)) end From 2066c207bc67dd063093beae1a5f59fb57ee26ee Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 13 Sep 2025 00:05:12 +0900 Subject: [PATCH 459/546] BigMath methods common interface: coerce x, validate prec, check nan error (#415) --- lib/bigdecimal/math.rb | 22 ++++++++++++++-------- test/bigdecimal/test_bigdecimal.rb | 3 ++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 5407bf0a..a589b197 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -41,6 +41,8 @@ module BigMath # #=> "0.1414213562373095048801688724e1" # def sqrt(x, prec) + BigDecimal::Internal.validate_prec(prec, :sqrt) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sqrt) x.sqrt(prec) end @@ -83,8 +85,9 @@ def sqrt(x, prec) # #=> "0.70710678118654752440082036563292800375e0" # def sin(x, prec) - raise ArgumentError, "Zero or negative precision for sin" if prec <= 0 - return BigDecimal("NaN") if x.infinite? || x.nan? + BigDecimal::Internal.validate_prec(prec, :sin) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sin) + return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? n = prec + BigDecimal.double_fig one = BigDecimal("1") two = BigDecimal("2") @@ -119,8 +122,9 @@ def sin(x, prec) # #=> "-0.999999999999999999999999999999856613163740061349e0" # def cos(x, prec) - raise ArgumentError, "Zero or negative precision for cos" if prec <= 0 - return BigDecimal("NaN") if x.infinite? || x.nan? + BigDecimal::Internal.validate_prec(prec, :cos) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cos) + return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? sign, x = _sin_periodic_reduction(x, prec + BigDecimal.double_fig, add_half_pi: true) sign * sin(x, prec) end @@ -140,6 +144,7 @@ def cos(x, prec) # #=> "0.99999999999999999999419869652481995799388629632650769e0" # def tan(x, prec) + BigDecimal::Internal.validate_prec(prec, :tan) sin(x, prec) / cos(x, prec) end @@ -155,8 +160,9 @@ def tan(x, prec) # #=> "-0.785398163397448309615660845819878471907514682065e0" # def atan(x, prec) - raise ArgumentError, "Zero or negative precision for atan" if prec <= 0 - return BigDecimal("NaN") if x.nan? + BigDecimal::Internal.validate_prec(prec, :atan) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atan) + return BigDecimal::Internal.nan_computation_result if x.nan? pi = PI(prec) x = -x if neg = x < 0 return pi.div(neg ? -2 : 2, prec) if x.infinite? @@ -192,7 +198,7 @@ def atan(x, prec) # #=> "0.3141592653589793238462643388813853786957412e1" # def PI(prec) - raise ArgumentError, "Zero or negative precision for PI" if prec <= 0 + BigDecimal::Internal.validate_prec(prec, :PI) n = prec + BigDecimal.double_fig zero = BigDecimal("0") one = BigDecimal("1") @@ -237,7 +243,7 @@ def PI(prec) # #=> "0.271828182845904523536028752390026306410273e1" # def E(prec) - raise ArgumentError, "Zero or negative precision for E" if prec <= 0 + BigDecimal::Internal.validate_prec(prec, :E) BigMath.exp(1, prec) end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index c3942617..fa8ae6ad 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2484,7 +2484,8 @@ def test_sqrt_exp_log_pow_with_limit BigDecimal.save_limit do BigDecimal.limit(limit) assert_equal(sqrt, BigMath.sqrt(x, prec)) - assert_equal(sqrt_lim, BigMath.sqrt(x, 0)) + assert_equal(sqrt, x.sqrt(prec)) + assert_equal(sqrt_lim, x.sqrt(0)) assert_equal(exp, BigMath.exp(x, prec)) assert_equal(log, BigMath.log(x, prec)) assert_equal(pow, x.power(y, prec)) From 44a2bb6d06d4ae2f56e6ce6fffd143b2cca7bcad Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 13 Sep 2025 03:38:35 +0900 Subject: [PATCH 460/546] Round result of sqrt and BigMath methods (#427) * Round result of sqrt and other BigMath methods BigMath methods no longer returns extra digits which may be inaccurate * Remove workaround for removing extra digits from sqrt result in log calculation --- lib/bigdecimal.rb | 35 +++++++++++--------------- lib/bigdecimal/math.rb | 16 ++++++------ test/bigdecimal/helper.rb | 15 ++++++++--- test/bigdecimal/test_bigdecimal.rb | 40 +++++++++--------------------- test/bigdecimal/test_bigmath.rb | 38 +++++++++++++++++----------- 5 files changed, 69 insertions(+), 75 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index fb51ba8d..2e87534d 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -195,12 +195,9 @@ def sqrt(prec) raise FloatDomainError, "sqrt of 'NaN'(Not a Number)" if nan? return self if zero? - limit = BigDecimal.limit.nonzero? if prec == 0 - - # BigDecimal#sqrt calculates at least n_significant_digits precision. - # This feature maybe problematic for some cases. - n_digits = n_significant_digits - prec = [prec, n_digits].max + if prec == 0 + prec = BigDecimal.limit.nonzero? || n_significant_digits + BigDecimal.double_fig + end ex = exponent / 2 x = _decimal_shift(-2 * ex) @@ -210,8 +207,7 @@ def sqrt(prec) precs.reverse_each do |p| y = y.add(x.div(y, p), p).div(2, p) end - y = y.mult(1, limit) if limit - y._decimal_shift(ex) + y._decimal_shift(ex).mult(1, prec) end end @@ -241,46 +237,43 @@ def self.log(x, prec) return BigDecimal::Internal.infinity_computation_result if x.infinite? return BigDecimal(0) if x == 1 + prec2 = prec + BigDecimal.double_fig BigDecimal.save_limit do BigDecimal.limit(0) if x > 10 || x < 0.1 - log10 = log(BigDecimal(10), prec) + log10 = log(BigDecimal(10), prec2) exponent = x.exponent x = x._decimal_shift(-exponent) if x < 0.3 x *= 10 exponent -= 1 end - return log10 * exponent + log(x, prec) + return (log10 * exponent).add(log(x, prec2), prec) end x_minus_one_exponent = (x - 1).exponent - prec += BigDecimal.double_fig # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps - sqrt_steps = [Integer.sqrt(prec) + 3 * x_minus_one_exponent, 0].max + sqrt_steps = [Integer.sqrt(prec2) + 3 * x_minus_one_exponent, 0].max lg2 = 0.3010299956639812 - prec2 = prec + [-x_minus_one_exponent, 0].max + (sqrt_steps * lg2).ceil + sqrt_prec = prec2 + [-x_minus_one_exponent, 0].max + (sqrt_steps * lg2).ceil sqrt_steps.times do - x = x.sqrt(prec2) - - # Workaround for https://github.com/ruby/bigdecimal/issues/354 - x = x.mult(1, prec2 + BigDecimal.double_fig) + x = x.sqrt(sqrt_prec) end # Taylor series for log(x) around 1 # log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1) # log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...) - x = (x - 1).div(x + 1, prec2) + x = (x - 1).div(x + 1, sqrt_prec) y = x - x2 = x.mult(x, prec) + x2 = x.mult(x, prec2) 1.step do |i| - n = prec + x.exponent - y.exponent + x2.exponent + n = prec2 + x.exponent - y.exponent + x2.exponent break if n <= 0 || x.zero? x = x.mult(x2.round(n - x2.exponent), n) - y = y.add(x.div(2 * i + 1, n), prec) + y = y.add(x.div(2 * i + 1, n), prec2) end y.mult(2 ** (sqrt_steps + 1), prec) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index a589b197..715b7f86 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -106,8 +106,8 @@ def sin(x, prec) d = x1.div(z,m) y += d end - y *= sign - y < -1 ? BigDecimal("-1") : y > 1 ? BigDecimal("1") : y + y = BigDecimal("1") if y > 1 + y.mult(sign, prec) end # call-seq: @@ -145,7 +145,7 @@ def cos(x, prec) # def tan(x, prec) BigDecimal::Internal.validate_prec(prec, :tan) - sin(x, prec) / cos(x, prec) + sin(x, prec + BigDecimal.double_fig).div(cos(x, prec + BigDecimal.double_fig), prec) end # call-seq: @@ -163,11 +163,11 @@ def atan(x, prec) BigDecimal::Internal.validate_prec(prec, :atan) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atan) return BigDecimal::Internal.nan_computation_result if x.nan? - pi = PI(prec) + n = prec + BigDecimal.double_fig + pi = PI(n) x = -x if neg = x < 0 return pi.div(neg ? -2 : 2, prec) if x.infinite? - return pi / (neg ? -4 : 4) if x.round(prec) == 1 - n = prec + BigDecimal.double_fig + return pi.div(neg ? -4 : 4, prec) if x.round(prec) == 1 x = BigDecimal("1").div(x, n) if inv = x > 1 x = (-1 + sqrt(1 + x.mult(x, n), n)).div(x, n) if dbl = x > 0.5 y = x @@ -185,7 +185,7 @@ def atan(x, prec) y *= 2 if dbl y = pi / 2 - y if inv y = -y if neg - y + y.mult(1, prec) end # call-seq: @@ -230,7 +230,7 @@ def PI(prec) pi = pi + d k = k+two end - pi + pi.mult(1, prec) end # call-seq: diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index ec309880..066a13e4 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -37,16 +37,23 @@ def under_gc_stress GC.stress = stress end - # Asserts that the calculation of the given block converges to some value - # with precision specified by block parameter. + # Asserts that +actual+ is calculated with exactly the given +precision+. + # No extra digits are allowed. Only the last digit may differ at most by one. + def assert_in_exact_precision(expected, actual, precision) + expected = BigDecimal(expected) + delta = BigDecimal(1)._decimal_shift(expected.exponent - precision) + assert actual.n_significant_digits <= precision, "Too many significant digits: #{actual.n_significant_digits} > #{precision}" + assert_in_delta(expected.mult(1, precision), actual, delta) + end + # Asserts that the calculation of the given block converges to some value + # with exactly the given +precision+. def assert_converge_in_precision(&block) expected = yield(200) [50, 100, 150].each do |n| value = yield(n) - precision = 1 - (value.div(expected, expected.precision) - 1).exponent assert(value != expected, "Unable to estimate precision for exact value") - assert(precision >= n, "Precision is not enough: #{precision} < #{n}") + assert_in_exact_precision(expected, value, n) end end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index fa8ae6ad..65de3a45 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1439,7 +1439,7 @@ def test_sqrt_bigdecimal x = BigDecimal("-" + (2**100).to_s) assert_raise_with_message(FloatDomainError, "sqrt of negative value") { x.sqrt(1) } x = BigDecimal((2**200).to_s) - assert_equal(2**100, x.sqrt(1)) + assert_equal(BigDecimal(2 ** 100).mult(1, 1), x.sqrt(1)) assert_in_delta(BigDecimal("4.0000000000000000000125"), BigDecimal("16.0000000000000000001").sqrt(100), BigDecimal("1e-40")) @@ -1457,37 +1457,22 @@ def test_sqrt_bigdecimal sqrt2_300 = BigDecimal(2).sqrt(300) (250..270).each do |prec| - sqrt_prec = prec + BigDecimal.double_fig - 1 - assert_in_delta(sqrt2_300, BigDecimal(2).sqrt(prec), BigDecimal("1e#{-sqrt_prec}")) + assert_in_exact_precision(sqrt2_300, BigDecimal(2).sqrt(prec), prec) end end def test_sqrt_5266 x = BigDecimal('2' + '0'*100) - assert_equal('0.14142135623730950488016887242096980785696718753769480731', - x.sqrt(56).to_s(56).split(' ')[0]) - assert_equal('0.1414213562373095048801688724209698078569671875376948073', - x.sqrt(55).to_s(55).split(' ')[0]) + assert_equal('0.14142135623730950488016887242096980785696718753769480732e51', + x.sqrt(56).to_s) + assert_equal('0.1414213562373095048801688724209698078569671875376948073e51', + x.sqrt(55).to_s) x = BigDecimal('2' + '0'*200) - assert_equal('0.14142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462', - x.sqrt(110).to_s(110).split(' ')[0]) - assert_equal('0.1414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641572735013846', - x.sqrt(109).to_s(109).split(' ')[0]) - end - - def test_sqrt_minimum_precision - x = BigDecimal((2**200).to_s) - assert_equal(2**100, x.sqrt(1)) - - x = BigDecimal('1' * 60 + '.' + '1' * 40) - assert_in_delta(BigDecimal('3' * 30 + '.' + '3' * 70), x.sqrt(1), BigDecimal('1e-70')) - - x = BigDecimal('1' * 40 + '.' + '1' * 60) - assert_in_delta(BigDecimal('3' * 20 + '.' + '3' * 80), x.sqrt(1), BigDecimal('1e-80')) - - x = BigDecimal('0.' + '0' * 50 + '1' * 100) - assert_in_delta(BigDecimal('0.' + '0' * 25 + '3' * 100), x.sqrt(1), BigDecimal('1e-125')) + assert_equal('0.14142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462e101', + x.sqrt(110).to_s) + assert_equal('0.1414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641572735013846e101', + x.sqrt(109).to_s) end def test_internal_use_decimal_shift @@ -2430,9 +2415,8 @@ def test_BigMath_log_with_square_of_E def test_BigMath_log_with_high_precision_case e = BigDecimal('2.71828182845904523536028747135266249775724709369996') - e_3 = e.mult(e, 50).mult(e, 50) - log_3 = BigMath.log(e_3, 50) - assert_in_delta(3, log_3, 0.0000000000_0000000000_0000000000_0000000000_0000000001) + e_3 = e.mult(e, 60).mult(e, 60) + assert_in_exact_precision(3, BigMath.log(e_3, 50), 50) end def test_BigMath_log_with_42 diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index c5d2e567..36f3afa0 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -16,7 +16,7 @@ class TestBigMath < Test::Unit::TestCase def test_pi assert_equal( BigDecimal("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068"), - PI(100).round(100) + PI(100) ) assert_converge_in_precision {|n| PI(n) } end @@ -37,8 +37,8 @@ def test_sqrt assert_raise(FloatDomainError) {sqrt(BigDecimal("-1.0"), N)} assert_raise(FloatDomainError) {sqrt(NAN, N)} assert_raise(FloatDomainError) {sqrt(PINF, N)} - assert_in_delta(SQRT2, sqrt(BigDecimal("2"), 100), BigDecimal("1e-100")) - assert_in_delta(SQRT3, sqrt(BigDecimal("3"), 100), BigDecimal("1e-100")) + assert_in_exact_precision(SQRT2, sqrt(BigDecimal("2"), 100), 100) + assert_in_exact_precision(SQRT3, sqrt(BigDecimal("3"), 100), 100) assert_converge_in_precision {|n| sqrt(BigDecimal("2"), n) } assert_converge_in_precision {|n| sqrt(BigDecimal("2e-50"), n) } assert_converge_in_precision {|n| sqrt(BigDecimal("2e50"), n) } @@ -56,9 +56,9 @@ def test_sin assert_in_delta(0.0, sin(PI(N) * 21, N)) assert_in_delta(0.0, sin(PI(N) * 30, N)) assert_in_delta(-1.0, sin(PI(N) * BigDecimal("301.5"), N)) - assert_in_delta(BigDecimal('0.5'), sin(PI(100) / 6, 100), BigDecimal("1e-100")) - assert_in_delta(SQRT3 / 2, sin(PI(100) / 3, 100), BigDecimal("1e-100")) - assert_in_delta(SQRT2 / 2, sin(PI(100) / 4, 100), BigDecimal("1e-100")) + assert_in_exact_precision(BigDecimal('0.5'), sin(PI(100) / 6, 100), 100) + assert_in_exact_precision(SQRT3 / 2, sin(PI(100) / 3, 100), 100) + assert_in_exact_precision(SQRT2 / 2, sin(PI(100) / 4, 100), 100) assert_converge_in_precision {|n| sin(BigDecimal("1"), n) } assert_converge_in_precision {|n| sin(BigDecimal("1e50"), n) } assert_converge_in_precision {|n| sin(BigDecimal("1e-30"), n) } @@ -80,9 +80,9 @@ def test_cos assert_in_delta(-1.0, cos(PI(N) * 21, N)) assert_in_delta(1.0, cos(PI(N) * 30, N)) assert_in_delta(0.0, cos(PI(N) * BigDecimal("301.5"), N)) - assert_in_delta(BigDecimal('0.5'), cos(PI(100) / 3, 100), BigDecimal("1e-100")) - assert_in_delta(SQRT3 / 2, cos(PI(100) / 6, 100), BigDecimal("1e-100")) - assert_in_delta(SQRT2 / 2, cos(PI(100) / 4, 100), BigDecimal("1e-100")) + assert_in_exact_precision(BigDecimal('0.5'), cos(PI(100) / 3, 100), 100) + assert_in_exact_precision(SQRT3 / 2, cos(PI(100) / 6, 100), 100) + assert_in_exact_precision(SQRT2 / 2, cos(PI(100) / 4, 100), 100) assert_converge_in_precision {|n| cos(BigDecimal("1"), n) } assert_converge_in_precision {|n| cos(BigDecimal("1e50"), n) } assert_converge_in_precision {|n| cos(BigDecimal(PI(50) / 2), n) } @@ -100,6 +100,10 @@ def test_tan assert_in_delta(0.0, tan(-PI(N), N)) assert_in_delta(-1.0, tan(-PI(N) / 4, N)) assert_in_delta(-sqrt(BigDecimal(3), N), tan(-PI(N) / 3, N)) + assert_in_exact_precision(SQRT3, tan(PI(100) / 3, 100), 100) + assert_converge_in_precision {|n| tan(1, n) } + assert_converge_in_precision {|n| tan(BigMath::PI(50) / 2, n) } + assert_converge_in_precision {|n| tan(BigMath::PI(50), n) } end def test_atan @@ -107,7 +111,7 @@ def test_atan assert_in_delta(Math::PI/4, atan(BigDecimal("1.0"), N)) assert_in_delta(Math::PI/6, atan(sqrt(BigDecimal("3.0"), N) / 3, N)) assert_in_delta(Math::PI/2, atan(PINF, N)) - assert_in_delta(PI(100) / 3, atan(SQRT3, 100), BigDecimal("1e-100")) + assert_in_exact_precision(PI(100) / 3, atan(SQRT3, 100), 100) assert_equal(BigDecimal("0.823840753418636291769355073102514088959345624027952954058347023122539489"), atan(BigDecimal("1.08"), 72).round(72), '[ruby-dev:41257]') assert_converge_in_precision {|n| atan(BigDecimal("2"), n)} @@ -120,8 +124,11 @@ def test_exp assert_in_epsilon(Math.exp(x), BigMath.exp(BigDecimal(x, 0), N)) end assert_equal(1, BigMath.exp(BigDecimal("0"), N)) - assert_in_epsilon(BigDecimal("4.48168907033806482260205546011927581900574986836966705677265008278593667446671377298105383138245339138861635065183019577"), - BigMath.exp(BigDecimal("1.5"), 100), BigDecimal("1e-100")) + assert_in_exact_precision( + BigDecimal("4.48168907033806482260205546011927581900574986836966705677265008278593667446671377298105383138245339138861635065183019577"), + BigMath.exp(BigDecimal("1.5"), 100), + 100 + ) assert_converge_in_precision {|n| BigMath.exp(BigDecimal("1"), n) } assert_converge_in_precision {|n| BigMath.exp(BigDecimal("-2"), n) } assert_converge_in_precision {|n| BigMath.exp(BigDecimal("-34"), n) } @@ -132,8 +139,11 @@ def test_exp def test_log assert_equal(0, BigMath.log(BigDecimal("1.0"), 10)) assert_in_epsilon(Math.log(10)*1000, BigMath.log(BigDecimal("1e1000"), 10)) - assert_in_epsilon(BigDecimal("2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862"), - BigMath.log(BigDecimal("10"), 100), BigDecimal("1e-100")) + assert_in_exact_precision( + BigDecimal("2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862"), + BigMath.log(BigDecimal("10"), 100), + 100 + ) assert_converge_in_precision {|n| BigMath.log(BigDecimal("2"), n) } assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e-30") + 1, n) } assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e-30"), n) } From 07696bcf067e0a9b19dff7bcb3641106ce6373d5 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 13 Sep 2025 04:27:33 +0900 Subject: [PATCH 461/546] Update example calculation result in BigMath document (#428) --- lib/bigdecimal/math.rb | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 715b7f86..521acc42 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -25,8 +25,8 @@ # # include BigMath # -# a = BigDecimal((PI(100)/2).to_s) -# puts sin(a,100) # => 0.99999999999999999999......e0 +# a = BigDecimal((PI(49)/2).to_s) +# puts sin(a,100) # => 0.9999999999...9999999986e0 # module BigMath module_function @@ -37,8 +37,8 @@ module BigMath # Computes the square root of +decimal+ to the specified number of digits of # precision, +numeric+. # - # BigMath.sqrt(BigDecimal('2'), 16).to_s - # #=> "0.1414213562373095048801688724e1" + # BigMath.sqrt(BigDecimal('2'), 32).to_s + # #=> "0.14142135623730950488016887242097e1" # def sqrt(x, prec) BigDecimal::Internal.validate_prec(prec, :sqrt) @@ -81,8 +81,8 @@ def sqrt(x, prec) # # If +decimal+ is Infinity or NaN, returns NaN. # - # BigMath.sin(BigMath.PI(5)/4, 5).to_s - # #=> "0.70710678118654752440082036563292800375e0" + # BigMath.sin(BigMath.PI(5)/4, 32).to_s + # #=> "0.70710807985947359435812921837984e0" # def sin(x, prec) BigDecimal::Internal.validate_prec(prec, :sin) @@ -118,8 +118,8 @@ def sin(x, prec) # # If +decimal+ is Infinity or NaN, returns NaN. # - # BigMath.cos(BigMath.PI(4), 16).to_s - # #=> "-0.999999999999999999999999999999856613163740061349e0" + # BigMath.cos(BigMath.PI(16), 32).to_s + # #=> "-0.99999999999999999999999999999997e0" # def cos(x, prec) BigDecimal::Internal.validate_prec(prec, :cos) @@ -140,8 +140,8 @@ def cos(x, prec) # BigMath.tan(BigDecimal("0.0"), 4).to_s # #=> "0.0" # - # BigMath.tan(BigMath.PI(4) / 4, 4).to_s - # #=> "0.99999999999999999999419869652481995799388629632650769e0" + # BigMath.tan(BigMath.PI(24) / 4, 32).to_s + # #=> "0.99999999999999999999999830836025e0" # def tan(x, prec) BigDecimal::Internal.validate_prec(prec, :tan) @@ -156,8 +156,8 @@ def tan(x, prec) # # If +decimal+ is NaN, returns NaN. # - # BigMath.atan(BigDecimal('-1'), 16).to_s - # #=> "-0.785398163397448309615660845819878471907514682065e0" + # BigMath.atan(BigDecimal('-1'), 32).to_s + # #=> "-0.78539816339744830961566084581988e0" # def atan(x, prec) BigDecimal::Internal.validate_prec(prec, :atan) @@ -194,8 +194,8 @@ def atan(x, prec) # Computes the value of pi to the specified number of digits of precision, # +numeric+. # - # BigMath.PI(10).to_s - # #=> "0.3141592653589793238462643388813853786957412e1" + # BigMath.PI(32).to_s + # #=> "0.31415926535897932384626433832795e1" # def PI(prec) BigDecimal::Internal.validate_prec(prec, :PI) @@ -239,8 +239,8 @@ def PI(prec) # Computes e (the base of natural logarithms) to the specified number of # digits of precision, +numeric+. # - # BigMath.E(10).to_s - # #=> "0.271828182845904523536028752390026306410273e1" + # BigMath.E(32).to_s + # #=> "0.27182818284590452353602874713527e1" # def E(prec) BigDecimal::Internal.validate_prec(prec, :E) From 34e60a7bf898423d9e25f851266f8f5da711cd23 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 13 Sep 2025 21:36:12 +0900 Subject: [PATCH 462/546] BigMath.log(0,n)==-Infinity just like Math.log(0) (#430) --- lib/bigdecimal.rb | 3 ++- test/bigdecimal/test_bigdecimal.rb | 5 +++-- test/bigdecimal/test_bigmath.rb | 3 +-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 2e87534d..d0a318d8 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -233,7 +233,8 @@ def self.log(x, prec) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log) return BigDecimal::Internal.nan_computation_result if x.nan? - raise Math::DomainError, 'Zero or negative argument for log' if x <= 0 + raise Math::DomainError, 'Negative argument for log' if x < 0 + return -BigDecimal::Internal.infinity_computation_result if x.zero? return BigDecimal::Internal.infinity_computation_result if x.infinite? return BigDecimal(0) if x == 1 diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 65de3a45..3c4bc0b2 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2352,8 +2352,9 @@ def test_BigMath_log_with_complex end def test_BigMath_log_with_zero_arg - assert_raise(Math::DomainError) do - BigMath.log(0, 20) + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) + assert_equal(Math.log(0), BigMath.log(0, 20)) end end diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 36f3afa0..dee63e79 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -149,8 +149,7 @@ def test_log assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e-30"), n) } assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e30"), n) } assert_converge_in_precision {|n| BigMath.log(SQRT2, n) } - assert_raise(Math::DomainError) {BigMath.log(BigDecimal("0"), 10)} - assert_raise(Math::DomainError) {BigMath.log(BigDecimal("-1"), 10)} + assert_raise(Math::DomainError) {BigMath.log(BigDecimal("-0.1"), 10)} assert_separately(%w[-rbigdecimal], <<-SRC) begin x = BigMath.log(BigDecimal("1E19999999999999"), 10) From 8ca324995520127079841e8b5b64ce1917fe6f28 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 13 Sep 2025 21:59:25 +0900 Subject: [PATCH 463/546] Fix divmod and modulo by infinity to match Float#divmod and Float#modulo (#429) --- ext/bigdecimal/bigdecimal.c | 21 +++++++++++++++------ test/bigdecimal/test_bigdecimal.rb | 20 +++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1eca3ef2..2c00e472 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1733,17 +1733,26 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod = (NULLABLE_BDVALUE) { nan, DATA_PTR(nan) }; goto Done; } - if (VpIsInf(b.real)) { - VALUE zero = BigDecimal_positive_zero(); - *div = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; - *mod = bdvalue_nullable(a); - goto Done; - } if (VpIsZero(a.real)) { VALUE zero = BigDecimal_positive_zero(); *div = *mod = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; goto Done; } + if (VpIsInf(b.real)) { + if (!truncate && VpGetSign(a.real) * VpGetSign(b.real) < 0) { + BDVALUE minus_one = NewZeroWrap(1, BASE_FIG); + VpSetOne(minus_one.real); + VpSetSign(minus_one.real, -1); + RB_GC_GUARD(minus_one.bigdecimal); + *div = bdvalue_nullable(minus_one); + *mod = bdvalue_nullable(b); + } else { + VALUE zero = BigDecimal_positive_zero(); + *div = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; + *mod = bdvalue_nullable(a); + } + goto Done; + } a_exponent = VpExponent10(a.real); b_exponent = VpExponent10(b.real); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 3c4bc0b2..c794ed0d 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2698,14 +2698,24 @@ def test_llong_min_gh_200 assert_equal(BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max), "[GH-200]") end - def test_reminder_infinity_gh_187 - # https://github.com/ruby/bigdecimal/issues/187 + def test_divmod_modulo_remainder_infinity + pend '-4.2.divmod(Float::INFINITY) is not [-1, Infinity]' if RUBY_ENGINE == 'truffleruby' BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - bd = BigDecimal("4.2") - assert_equal(bd.remainder(BigDecimal("+Infinity")), bd) - assert_equal(bd.remainder(BigDecimal("-Infinity")), bd) + pinf = BigDecimal("+Infinity") + minf = BigDecimal("-Infinity") + assert_nan(pinf.modulo(pinf)) + assert_nan(pinf.remainder(pinf)) + [BigDecimal("-4.2"), BigDecimal(0), BigDecimal("4.2")].each do |x| + assert_equal(x.to_f.divmod(pinf.to_f), x.divmod(pinf)) + assert_equal(x.to_f.divmod(minf.to_f), x.divmod(minf)) + assert_equal(x.to_f.modulo(pinf.to_f), x.modulo(pinf)) + assert_equal(x.to_f.modulo(minf.to_f), x.modulo(minf)) + # Float#remainder(plus_or_minus_infinity) returns itself in Ruby >= 3.1 + assert_equal(x, x.remainder(pinf)) + assert_equal(x, x.remainder(minf)) + end end end From 6682fd8c59010b29d213a33aed715b961615c1d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:07:46 +0000 Subject: [PATCH 464/546] Bump step-security/harden-runner from 2.13.0 to 2.13.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.0 to 2.13.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/ec9f2d5744a09debf3a187a3f4f675c53b671911...f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.13.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index d2838490..a57d50fb 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit From d93b542015d03b4b20565f59830b20c4d45bf87b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 16 Sep 2025 05:12:51 +0900 Subject: [PATCH 465/546] Make internal BigMath method a private method (#432) --- lib/bigdecimal/math.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 521acc42..ba879ec4 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -51,7 +51,7 @@ def sqrt(x, prec) # and satisfies sin(x) = sign * sin(reduced_x) # If add_half_pi is true, adds pi/2 to x before reduction. # Precision of pi is adjusted to ensure reduced_x has the required precision. - private def _sin_periodic_reduction(x, prec, add_half_pi: false) + private_class_method def _sin_periodic_reduction(x, prec, add_half_pi: false) # :nodoc: return [1, x] if -Math::PI/2 <= x && x <= Math::PI/2 && !add_half_pi mod_prec = prec + BigDecimal.double_fig From cb2458bde33bf90a8364b58d53e8948a7ba555ea Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 3 Oct 2025 23:32:03 +0900 Subject: [PATCH 466/546] Add newline at EOF [ci skip] --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 9514e271..314f83aa 100644 --- a/Rakefile +++ b/Rakefile @@ -76,4 +76,4 @@ namespace :dev do end end end -end \ No newline at end of file +end From a267ca741a8fc57f69d16d6be52059e25b08705d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 7 Oct 2025 01:47:55 +0900 Subject: [PATCH 467/546] Improve performance of x**y when y is a huge value (#438) When y.exponent is several thousand or more, x**y was slow because exponentiation by squaring requires several thousands of multiplications. Use exp and log in such case. Needed to calaculate (1+1/n).power(n, prec) --- lib/bigdecimal.rb | 31 ++++++++++++++++-------------- test/bigdecimal/test_bigdecimal.rb | 5 +++++ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index d0a318d8..cf2dfaae 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -165,22 +165,25 @@ def power(y, prec = nil) return BigDecimal(1).div(inv, prec) end - int_part = y.fix.to_i prec2 = prec + BigDecimal.double_fig - pow_prec = prec2 + (int_part > 0 ? y.exponent : 0) - ans = BigDecimal(1) - n = 1 - xn = x - while true - ans = ans.mult(xn, pow_prec) if int_part.allbits?(n) - n <<= 1 - break if n > int_part - xn = xn.mult(xn, pow_prec) - end - unless frac_part.zero? - ans = ans.mult(BigMath.exp(BigMath.log(x, prec2).mult(frac_part, prec2), prec2), prec2) + + if frac_part.zero? && y.exponent < Math.log(prec) * 5 + 20 + # Use exponentiation by squaring if y is an integer and not too large + pow_prec = prec2 + y.exponent + n = 1 + xn = x + ans = BigDecimal(1) + int_part = y.fix.to_i + while true + ans = ans.mult(xn, pow_prec) if int_part.allbits?(n) + n <<= 1 + break if n > int_part + xn = xn.mult(xn, pow_prec) + end + ans.mult(1, prec) + else + BigMath.exp(BigMath.log(x, prec2).mult(y, prec2), prec) end - ans.mult(1, prec) end # Returns the square root of the value. diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index c794ed0d..26cc5f02 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1991,6 +1991,11 @@ def test_power_with_rational assert_in_epsilon(z2, x2 ** y, 1e-99) end + def test_power_with_huge_value + n = BigDecimal('7e+10000') + assert_equal(BigMath.exp(1, 100), (1 + BigDecimal(1).div(n, 120)).power(n, 100)) + end + def test_power_precision x = BigDecimal("1.41421356237309504880168872420969807856967187537695") y = BigDecimal("3.14159265358979323846264338327950288419716939937511") From f718178428cba27eab531902c7391568b31d2c64 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 7 Oct 2025 21:20:01 +0900 Subject: [PATCH 468/546] Fix precision of x.power(y, prec) when the result is nearly infinity (#439) --- lib/bigdecimal.rb | 7 +++++++ test/bigdecimal/test_bigdecimal.rb | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index cf2dfaae..e7ade29b 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -182,6 +182,13 @@ def power(y, prec = nil) end ans.mult(1, prec) else + if x > 1 + # To calculate exp(z, prec), z needs prec+max(z.exponent, 0) precision if z > 0. + # Estimate (y*log(x)).exponent + logx_exponent = x < 2 ? (x - 1).exponent : Math.log10(x.exponent).round + ylogx_exponent = y.exponent + logx_exponent + prec2 += [ylogx_exponent, 0].max + end BigMath.exp(BigMath.log(x, prec2).mult(y, prec2), prec) end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 26cc5f02..159b875f 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1996,6 +1996,22 @@ def test_power_with_huge_value assert_equal(BigMath.exp(1, 100), (1 + BigDecimal(1).div(n, 120)).power(n, 100)) end + def test_power_almost_infinite + omit if EXPONENT_MAX < 9e+18 + + x = BigDecimal(1.9) + y = BigDecimal('1e+18') + 0.1 + assert_equal(x.power(y, 120).mult(1, 100), x.power(y, 100)) + + x = BigDecimal("9e+#{10**17}").div(7, 100) + y = BigDecimal(1.9) + assert_equal(x.power(y, 120).mult(1, 100), x.power(y, 100)) + + x = BigDecimal("9e+#{10**9}").div(7, 100) + y = BigDecimal('1e+8') + 0.1 + assert_equal(x.power(y, 120).mult(1, 100), x.power(y, 100)) + end + def test_power_precision x = BigDecimal("1.41421356237309504880168872420969807856967187537695") y = BigDecimal("3.14159265358979323846264338327950288419716939937511") From 0aa97bb2b6ec095827c436cf908baf643cd0153c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 7 Oct 2025 21:27:28 +0900 Subject: [PATCH 469/546] Bump version to 3.3.0 (#437) * Update changelog for v3.3.0 * Bump version to 3.3.0 --- CHANGES.md | 22 ++++++++++++++++++++++ ext/bigdecimal/bigdecimal.c | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 52948639..48ff0548 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,27 @@ # CHANGES +## 3.3.0 + +* Allow calling to_d without arguments [GH-421] + + **@fsateler** + +* Calculate BigMath.sin and cos in relative precision [GH-422] + + **@tompng** + +* Add support for tangent function [GH-231] + + **@rhannequin** + +* BigMath methods accepts numeric as an argument [GH-415] + + **@tompng** + +* Round result of sqrt and BigMath methods [GH-427] + + **@tompng** + ## 3.2.3 * Allow BigDecimal accept Float without precision [GH-314] diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 2c00e472..88f26f2b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.2.3" +#define BIGDECIMAL_VERSION "3.3.0" /* #define ENABLE_NUMERIC_STRING */ From a831065cbdcccd19cd201bfb9d7064aa80753208 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 8 Oct 2025 14:36:47 +0900 Subject: [PATCH 470/546] Fix modulo/remainder of negative zero (#441) --- ext/bigdecimal/bigdecimal.c | 3 ++- test/bigdecimal/test_bigdecimal.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 88f26f2b..91f02d50 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1735,7 +1735,8 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE } if (VpIsZero(a.real)) { VALUE zero = BigDecimal_positive_zero(); - *div = *mod = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; + *div = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; + *mod = bdvalue_nullable(a); goto Done; } if (VpIsInf(b.real)) { diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 159b875f..0b06bd86 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2740,6 +2740,16 @@ def test_divmod_modulo_remainder_infinity end end + def test_signed_zero_modulo_remainder + [-0.0, +0.0].each do |a| + [-1.0, +1.0].each do |b| + assert_equal(BigDecimal(a.remainder(b)).to_s, BigDecimal(a).remainder(BigDecimal(b)).to_s) + assert_equal(BigDecimal(a.modulo(b)).to_s, BigDecimal(a).modulo(BigDecimal(b)).to_s) + assert_equal(a.divmod(b).map {|x| BigDecimal(x)}.inspect, BigDecimal(a).divmod(BigDecimal(b)).inspect) + end + end + end + def test_bsearch_for_bigdecimal assert_raise(TypeError) { (BigDecimal('0.5')..BigDecimal('2.25')).bsearch From 8f34991a7e01193feaef29461f9fa980801f2541 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 10 Oct 2025 00:53:58 +0900 Subject: [PATCH 471/546] Unify all precision validation to be consistent with BigDecimal#add (#442) BigMath methods was aligned with BigMath.log precision handling that raises "precision must be an Integer" for non-integers. This is different from BigDecimal#add handling that converts with to_int. This commit unifies all precision handling to match BigDecimal#add series. In addition, bigdecimal tests in ruby/spec requires non-integer precision to be accepted. --- lib/bigdecimal.rb | 33 ++++++++++++++++-------- lib/bigdecimal/math.rb | 14 +++++----- test/bigdecimal/test_bigdecimal.rb | 16 +++--------- test/bigdecimal/test_bigmath.rb | 41 ++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 30 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index e7ade29b..db03a178 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -27,13 +27,24 @@ def self.coerce_to_bigdecimal(x, prec, method_name) # :nodoc: raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal" end - def self.validate_prec(prec, method_name, accept_zero: false) # :nodoc: - raise ArgumentError, 'precision must be an Integer' unless Integer === prec + def self.coerce_validate_prec(prec, method_name, accept_zero: false) # :nodoc: + unless Integer === prec + original = prec + # Emulate Integer.try_convert for ruby < 3.1 + if prec.respond_to?(:to_int) + prec = prec.to_int + else + raise TypeError, "no implicit conversion of #{original.class} into Integer" + end + raise TypeError, "can't convert #{original.class} to Integer" unless Integer === prec + end + if accept_zero raise ArgumentError, "Negative precision for #{method_name}" if prec < 0 else raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0 end + prec end def self.infinity_computation_result # :nodoc: @@ -83,10 +94,10 @@ def **(y) # # Also available as the operator **. # - def power(y, prec = nil) - Internal.validate_prec(prec, :power) if prec + def power(y, prec = 0) + prec = Internal.coerce_validate_prec(prec, :power, accept_zero: true) x = self - y = Internal.coerce_to_bigdecimal(y, prec || n_significant_digits, :power) + y = Internal.coerce_to_bigdecimal(y, prec.nonzero? || n_significant_digits, :power) return Internal.nan_computation_result if x.nan? || y.nan? return BigDecimal(1) if y.zero? @@ -133,10 +144,10 @@ def power(y, prec = nil) return BigDecimal(1) end - prec ||= BigDecimal.limit.nonzero? + prec = BigDecimal.limit if prec.zero? frac_part = y.frac - if frac_part.zero? && !prec + if frac_part.zero? && prec.zero? # Infinite precision calculation for `x ** int` and `x.power(int)` int_part = y.fix.to_i int_part = -int_part if (neg = int_part < 0) @@ -156,7 +167,7 @@ def power(y, prec = nil) return neg ? BigDecimal(1) / ans : ans end - prec ||= [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig + prec = [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig if prec.zero? if y < 0 inv = x.power(-y, prec) @@ -198,7 +209,7 @@ def power(y, prec = nil) # Result has at least prec significant digits. # def sqrt(prec) - Internal.validate_prec(prec, :sqrt, accept_zero: true) + prec = Internal.coerce_validate_prec(prec, :sqrt, accept_zero: true) return Internal.infinity_computation_result if infinite? == 1 raise FloatDomainError, 'sqrt of negative value' if self < 0 @@ -238,7 +249,7 @@ module BigMath # If +decimal+ is NaN, returns NaN. # def self.log(x, prec) - BigDecimal::Internal.validate_prec(prec, :log) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :log) raise Math::DomainError, 'Complex argument for BigMath.log' if Complex === x x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log) @@ -315,7 +326,7 @@ def self.log(x, prec) # If +decimal+ is NaN, returns NaN. # def self.exp(x, prec) - BigDecimal::Internal.validate_prec(prec, :exp) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :exp) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :exp) return BigDecimal::Internal.nan_computation_result if x.nan? return x.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) if x.infinite? diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index ba879ec4..d2d2e68c 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -41,7 +41,7 @@ module BigMath # #=> "0.14142135623730950488016887242097e1" # def sqrt(x, prec) - BigDecimal::Internal.validate_prec(prec, :sqrt) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :sqrt) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sqrt) x.sqrt(prec) end @@ -85,7 +85,7 @@ def sqrt(x, prec) # #=> "0.70710807985947359435812921837984e0" # def sin(x, prec) - BigDecimal::Internal.validate_prec(prec, :sin) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :sin) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sin) return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? n = prec + BigDecimal.double_fig @@ -122,7 +122,7 @@ def sin(x, prec) # #=> "-0.99999999999999999999999999999997e0" # def cos(x, prec) - BigDecimal::Internal.validate_prec(prec, :cos) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :cos) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cos) return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? sign, x = _sin_periodic_reduction(x, prec + BigDecimal.double_fig, add_half_pi: true) @@ -144,7 +144,7 @@ def cos(x, prec) # #=> "0.99999999999999999999999830836025e0" # def tan(x, prec) - BigDecimal::Internal.validate_prec(prec, :tan) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :tan) sin(x, prec + BigDecimal.double_fig).div(cos(x, prec + BigDecimal.double_fig), prec) end @@ -160,7 +160,7 @@ def tan(x, prec) # #=> "-0.78539816339744830961566084581988e0" # def atan(x, prec) - BigDecimal::Internal.validate_prec(prec, :atan) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :atan) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atan) return BigDecimal::Internal.nan_computation_result if x.nan? n = prec + BigDecimal.double_fig @@ -198,7 +198,7 @@ def atan(x, prec) # #=> "0.31415926535897932384626433832795e1" # def PI(prec) - BigDecimal::Internal.validate_prec(prec, :PI) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :PI) n = prec + BigDecimal.double_fig zero = BigDecimal("0") one = BigDecimal("1") @@ -243,7 +243,7 @@ def PI(prec) # #=> "0.27182818284590452353602874713527e1" # def E(prec) - BigDecimal::Internal.validate_prec(prec, :E) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :E) BigMath.exp(1, prec) end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 0b06bd86..243a9583 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1813,6 +1813,10 @@ def test_power_with_Bignum def test_power_with_intger_infinite_precision assert_equal(1234 ** 100, (BigDecimal("12.34") ** 100) * BigDecimal("1e200")) assert_in_delta(1234 ** 100, 1 / (BigDecimal("12.34") ** -100) * BigDecimal("1e200"), 1) + BigDecimal.save_limit do + BigDecimal.limit 10 + assert_equal(BigDecimal("0.1353679867e110"), BigDecimal("12.34") ** 100) + end end def test_power_with_BigDecimal @@ -2354,18 +2358,6 @@ def test_BigMath_log_with_nil end end - def test_BigMath_log_with_non_integer_precision - assert_raise(ArgumentError) do - BigMath.log(1, 0.5) - end - end - - def test_BigMath_log_with_nil_precision - assert_raise(ArgumentError) do - BigMath.log(1, nil) - end - end - def test_BigMath_log_with_complex assert_raise(Math::DomainError) do BigMath.log(Complex(1, 2), 20) diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index dee63e79..8d2f6ca5 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -29,6 +29,47 @@ def test_e assert_converge_in_precision {|n| E(n) } end + def assert_consistent_precision_acceptance(accept_zero: false) + value = yield 5 + assert_equal(value, yield(5.9)) + + obj_with_to_int = Object.new + obj_with_to_int.define_singleton_method(:to_int) { 5 } + assert_equal(value, yield(obj_with_to_int)) + + wrong_to_int = Object.new + wrong_to_int.define_singleton_method(:to_int) { 5.5 } + assert_raise(TypeError) { yield wrong_to_int } + + assert_raise(TypeError) { yield nil } + assert_raise(TypeError) { yield '5' } + assert_raise(ArgumentError) { yield(-1) } + if accept_zero + assert_nothing_raised { yield 0 } + else + assert_raise(ArgumentError) { yield 0 } + end + end + + def test_consistent_precision_acceptance + x = BigDecimal('1.23456789') + # Exclude div because div(x, nil) is a special case + assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.add(x, prec) } + assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.sub(x, prec) } + assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.mult(x, prec) } + assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.power(x, prec) } + assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.sqrt(prec) } + assert_consistent_precision_acceptance {|prec| BigMath.sqrt(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.exp(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.log(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.sin(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.cos(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.tan(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.atan(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.E(prec) } + assert_consistent_precision_acceptance {|prec| BigMath.PI(prec) } + end + def test_sqrt assert_in_delta(2**0.5, sqrt(BigDecimal("2"), N)) assert_equal(10, sqrt(BigDecimal("100"), N)) From 2d932f479c5802ad59d1dd1be0af63945a343e9a Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 10 Oct 2025 01:38:16 +0900 Subject: [PATCH 472/546] Bump version to 3.3.1 (#443) * Update changelog for v3.3.1 * Bump version to 3.3.1 --- CHANGES.md | 6 ++++++ ext/bigdecimal/bigdecimal.c | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 48ff0548..f2472d16 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 3.3.1 + +* All BigMath methods converts non integer precision with to_int + + **@tompng** + ## 3.3.0 * Allow calling to_d without arguments [GH-421] diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 91f02d50..594459db 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.3.0" +#define BIGDECIMAL_VERSION "3.3.1" /* #define ENABLE_NUMERIC_STRING */ From 4626b7f8119588d7e4769e8fa9d9e31d4d0d4796 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 16 Oct 2025 19:57:35 +0900 Subject: [PATCH 473/546] Fix x**y, x.power(y, 0) and x.sqrt(0) calculates huge digits if precision limit is huge (#445) --- lib/bigdecimal.rb | 25 ++++++++++++++----------- test/bigdecimal/test_bigdecimal.rb | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index db03a178..22719746 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -144,10 +144,10 @@ def power(y, prec = 0) return BigDecimal(1) end - prec = BigDecimal.limit if prec.zero? + limit = BigDecimal.limit frac_part = y.frac - if frac_part.zero? && prec.zero? + if frac_part.zero? && prec.zero? && limit.zero? # Infinite precision calculation for `x ** int` and `x.power(int)` int_part = y.fix.to_i int_part = -int_part if (neg = int_part < 0) @@ -167,18 +167,19 @@ def power(y, prec = 0) return neg ? BigDecimal(1) / ans : ans end - prec = [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig if prec.zero? + result_prec = prec.nonzero? || [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig + result_prec = [result_prec, limit].min if prec.zero? && limit.nonzero? + + prec2 = result_prec + BigDecimal.double_fig if y < 0 - inv = x.power(-y, prec) + inv = x.power(-y, prec2) return BigDecimal(0) if inv.infinite? return BigDecimal::Internal.infinity_computation_result if inv.zero? - return BigDecimal(1).div(inv, prec) + return BigDecimal(1).div(inv, result_prec) end - prec2 = prec + BigDecimal.double_fig - - if frac_part.zero? && y.exponent < Math.log(prec) * 5 + 20 + if frac_part.zero? && y.exponent < Math.log(result_prec) * 5 + 20 # Use exponentiation by squaring if y is an integer and not too large pow_prec = prec2 + y.exponent n = 1 @@ -191,7 +192,7 @@ def power(y, prec = 0) break if n > int_part xn = xn.mult(xn, pow_prec) end - ans.mult(1, prec) + ans.mult(1, result_prec) else if x > 1 # To calculate exp(z, prec), z needs prec+max(z.exponent, 0) precision if z > 0. @@ -200,7 +201,7 @@ def power(y, prec = 0) ylogx_exponent = y.exponent + logx_exponent prec2 += [ylogx_exponent, 0].max end - BigMath.exp(BigMath.log(x, prec2).mult(y, prec2), prec) + BigMath.exp(BigMath.log(x, prec2).mult(y, prec2), result_prec) end end @@ -217,7 +218,9 @@ def sqrt(prec) return self if zero? if prec == 0 - prec = BigDecimal.limit.nonzero? || n_significant_digits + BigDecimal.double_fig + limit = BigDecimal.limit + prec = n_significant_digits + BigDecimal.double_fig + prec = [limit, prec].min if limit.nonzero? end ex = exponent / 2 diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 243a9583..497d6290 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2072,14 +2072,17 @@ def test_arithmetic_operation_with_limit assert_equal(BigDecimal('0.889'), (BigDecimal('0.8888') - BigDecimal('0'))) assert_equal(BigDecimal('2.66'), (BigDecimal('0.888') * BigDecimal('3'))) assert_equal(BigDecimal('0.296'), (BigDecimal('0.8888') / BigDecimal('3'))) + assert_equal(BigDecimal('0.222e109'), BigDecimal('98.76') ** BigDecimal('54.32')) assert_equal(BigDecimal('0.889'), BigDecimal('0.8888').add(BigDecimal('0'), 0)) assert_equal(BigDecimal('0.889'), BigDecimal('0.8888').sub(BigDecimal('0'), 0)) assert_equal(BigDecimal('2.66'), BigDecimal('0.888').mult(BigDecimal('3'), 0)) assert_equal(BigDecimal('0.296'), BigDecimal('0.8888').div(BigDecimal('3'), 0)) + assert_equal(BigDecimal('0.222e109'), BigDecimal('98.76').power(BigDecimal('54.32'), 0)) assert_equal(BigDecimal('0.8888'), BigDecimal('0.8888').add(BigDecimal('0'), 5)) assert_equal(BigDecimal('0.8888'), BigDecimal('0.8888').sub(BigDecimal('0'), 5)) assert_equal(BigDecimal('2.664'), BigDecimal('0.888').mult(BigDecimal('3'), 5)) assert_equal(BigDecimal('0.29627'), BigDecimal('0.8888').div(BigDecimal('3'), 5)) + assert_equal(BigDecimal('0.22164e109'), BigDecimal('98.76').power(BigDecimal('54.32'), 5)) end end @@ -2092,6 +2095,26 @@ def test_div_with_huge_limit end end + def test_sqrt_with_huge_limit + BigDecimal.save_limit do + x = BigDecimal("12.34") + sqrt = x.sqrt(0) + BigDecimal.limit(1000) + assert_equal(sqrt, x.sqrt(0)) + end + end + +def test_power_with_huge_limit + BigDecimal.save_limit do + x = BigDecimal("12.34") + y = BigDecimal("56.78") + pow = x ** y + BigDecimal.limit(1000) + assert_equal(pow, x ** y) + assert_equal(pow, x.power(y, 0)) + end + end + def test_div_mod_rem_operation_with_limit x = -(9 ** 100) y = 7 ** 100 From 23dc06b7f59c46cb6665274d7a0b22a40021d7e8 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 28 Oct 2025 21:43:20 +0900 Subject: [PATCH 474/546] Implement major math functions (#336) * Move special value assertion methods to helper * Implement BigMath.atan2(y, x, prec) * Implement BigMath.hypot(x, y, prec) * Implement BigMath.cbrt * Implement asin and acos * Implement hyperbolic functions(sinh, cosh, tanh) * Implement inverse hyperbolic functions(asinh, acosh, atanh) * Implement BigMath.log2 and BigMath.log10 * Implement BigMath.log1p and BigMath.expm1 * Add argument coerce assertion for all BigMath methods --- lib/bigdecimal/math.rb | 383 ++++++++++++++++++++++++++++- test/bigdecimal/helper.rb | 51 ++++ test/bigdecimal/test_bigdecimal.rb | 50 ---- test/bigdecimal/test_bigmath.rb | 269 +++++++++++++++++++- 4 files changed, 701 insertions(+), 52 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index d2d2e68c..16e095df 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -5,15 +5,30 @@ #-- # Contents: # sqrt(x, prec) +# cbrt(x, prec) +# hypot(x, y, prec) # sin (x, prec) # cos (x, prec) # tan (x, prec) +# asin(x, prec) +# acos(x, prec) # atan(x, prec) +# atan2(y, x, prec) +# sinh (x, prec) +# cosh (x, prec) +# tanh (x, prec) +# asinh(x, prec) +# acosh(x, prec) +# atanh(x, prec) +# log2 (x, prec) +# log10(x, prec) +# log1p(x, prec) +# expm1(x, prec) # PI (prec) # E (prec) == exp(1.0,prec) # # where: -# x ... BigDecimal number to be computed. +# x, y ... BigDecimal number to be computed. # prec ... Number of digits to be obtained. #++ # @@ -73,6 +88,53 @@ def sqrt(x, prec) end end + # call-seq: + # cbrt(decimal, numeric) -> BigDecimal + # + # Computes the cube root of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # BigMath.cbrt(BigDecimal('2'), 32).to_s + # #=> "0.12599210498948731647672106072782e1" + # + def cbrt(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :cbrt) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cbrt) + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal::Internal.infinity_computation_result * x.infinite? if x.infinite? + return BigDecimal(0) if x.zero? + + x = -x if neg = x < 0 + ex = x.exponent / 3 + x = x._decimal_shift(-3 * ex) + y = BigDecimal(Math.cbrt(x.to_f)) + precs = [prec + BigDecimal.double_fig] + precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig + precs.reverse_each do |p| + y = (2 * y + x.div(y, p).div(y, p)).div(3, p) + end + y._decimal_shift(ex).mult(neg ? -1 : 1, prec) + end + + # call-seq: + # hypot(x, y, numeric) -> BigDecimal + # + # Returns sqrt(x**2 + y**2) to the specified number of digits of + # precision, +numeric+. + # + # BigMath.hypot(BigDecimal('1'), BigDecimal('2'), 32).to_s + # #=> "0.22360679774997896964091736687313e1" + # + def hypot(x, y, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :hypot) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :hypot) + y = BigDecimal::Internal.coerce_to_bigdecimal(y, prec, :hypot) + return BigDecimal::Internal.nan_computation_result if x.nan? || y.nan? + return BigDecimal::Internal.infinity_computation_result if x.infinite? || y.infinite? + prec2 = prec + BigDecimal.double_fig + sqrt(x.mult(x, prec2) + y.mult(y, prec2), prec) + end + # call-seq: # sin(decimal, numeric) -> BigDecimal # @@ -148,6 +210,57 @@ def tan(x, prec) sin(x, prec + BigDecimal.double_fig).div(cos(x, prec + BigDecimal.double_fig), prec) end + # call-seq: + # asin(decimal, numeric) -> BigDecimal + # + # Computes the arcsine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.asin(BigDecimal('0.5'), 32).to_s + # #=> "0.52359877559829887307710723054658e0" + # + def asin(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :asin) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :asin) + raise Math::DomainError, "Out of domain argument for asin" if x < -1 || x > 1 + return BigDecimal::Internal.nan_computation_result if x.nan? + + prec2 = prec + BigDecimal.double_fig + cos = (1 - x**2).sqrt(prec2) + if cos.zero? + PI(prec2).div(x > 0 ? 2 : -2, prec) + else + atan(x.div(cos, prec2), prec) + end + end + + # call-seq: + # acos(decimal, numeric) -> BigDecimal + # + # Computes the arccosine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.acos(BigDecimal('0.5'), 32).to_s + # #=> "0.10471975511965977461542144610932e1" + # + def acos(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :acos) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :acos) + raise Math::DomainError, "Out of domain argument for acos" if x < -1 || x > 1 + return BigDecimal::Internal.nan_computation_result if x.nan? + + prec2 = prec + BigDecimal.double_fig + return (PI(prec2) / 2).sub(asin(x, prec2), prec) if x < 0 + return PI(prec2).div(2, prec) if x.zero? + + sin = (1 - x**2).sqrt(prec2) + atan(sin.div(x, prec2), prec) + end + # call-seq: # atan(decimal, numeric) -> BigDecimal # @@ -188,6 +301,274 @@ def atan(x, prec) y.mult(1, prec) end + # call-seq: + # atan2(decimal, decimal, numeric) -> BigDecimal + # + # Computes the arctangent of y and x to the specified number of digits of + # precision, +numeric+. + # + # BigMath.atan2(BigDecimal('-1'), BigDecimal('1'), 32).to_s + # #=> "-0.78539816339744830961566084581988e0" + # + def atan2(y, x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :atan2) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atan2) + y = BigDecimal::Internal.coerce_to_bigdecimal(y, prec, :atan2) + return BigDecimal::Internal.nan_computation_result if x.nan? || y.nan? + + if x.infinite? || y.infinite? + one = BigDecimal(1) + zero = BigDecimal(0) + x = x.infinite? ? (x > 0 ? one : -one) : zero + y = y.infinite? ? (y > 0 ? one : -one) : y.sign * zero + end + + return x.sign >= 0 ? BigDecimal(0) : y.sign * PI(prec) if y.zero? + + y = -y if neg = y < 0 + xlarge = y.abs < x.abs + prec2 = prec + BigDecimal.double_fig + if x > 0 + v = xlarge ? atan(y.div(x, prec2), prec) : PI(prec2) / 2 - atan(x.div(y, prec2), prec2) + else + v = xlarge ? PI(prec2) - atan(-y.div(x, prec2), prec2) : PI(prec2) / 2 + atan(x.div(-y, prec2), prec2) + end + v.mult(neg ? -1 : 1, prec) + end + + # call-seq: + # sinh(decimal, numeric) -> BigDecimal + # + # Computes the hyperbolic sine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.sinh(BigDecimal('1'), 32).to_s + # #=> "0.11752011936438014568823818505956e1" + # + def sinh(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :sinh) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sinh) + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal::Internal.infinity_computation_result * x.infinite? if x.infinite? + + prec2 = prec + BigDecimal.double_fig + prec2 -= x.exponent if x.exponent < 0 + e = BigMath.exp(x, prec2) + (e - BigDecimal(1).div(e, prec2)).div(2, prec) + end + + # call-seq: + # cosh(decimal, numeric) -> BigDecimal + # + # Computes the hyperbolic cosine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.cosh(BigDecimal('1'), 32).to_s + # #=> "0.15430806348152437784779056207571e1" + # + def cosh(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :cosh) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cosh) + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal::Internal.infinity_computation_result if x.infinite? + + prec2 = prec + BigDecimal.double_fig + e = BigMath.exp(x, prec2) + (e + BigDecimal(1).div(e, prec2)).div(2, prec) + end + + # call-seq: + # tanh(decimal, numeric) -> BigDecimal + # + # Computes the hyperbolic tangent of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.tanh(BigDecimal('1'), 32).to_s + # #=> "0.76159415595576488811945828260479e0" + # + def tanh(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :tanh) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :tanh) + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal(x.infinite?) if x.infinite? + + prec2 = prec + BigDecimal.double_fig + [-x.exponent, 0].max + e = BigMath.exp(x, prec2) + einv = BigDecimal(1).div(e, prec2) + (e - einv).div(e + einv, prec) + end + + # call-seq: + # asinh(decimal, numeric) -> BigDecimal + # + # Computes the inverse hyperbolic sine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.asinh(BigDecimal('1'), 32).to_s + # #=> "0.88137358701954302523260932497979e0" + # + def asinh(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :asinh) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :asinh) + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal::Internal.infinity_computation_result * x.infinite? if x.infinite? + return -asinh(-x, prec) if x < 0 + + sqrt_prec = prec + [-x.exponent, 0].max + BigDecimal.double_fig + BigMath.log(x + sqrt(x**2 + 1, sqrt_prec), prec) + end + + # call-seq: + # acosh(decimal, numeric) -> BigDecimal + # + # Computes the inverse hyperbolic cosine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.acosh(BigDecimal('2'), 32).to_s + # #=> "0.1316957896924816708625046347308e1" + # + def acosh(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :acosh) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :acosh) + raise Math::DomainError, "Out of domain argument for acosh" if x < 1 + return BigDecimal::Internal.infinity_computation_result if x.infinite? + return BigDecimal::Internal.nan_computation_result if x.nan? + + BigMath.log(x + sqrt(x**2 - 1, prec + BigDecimal.double_fig), prec) + end + + # call-seq: + # atanh(decimal, numeric) -> BigDecimal + # + # Computes the inverse hyperbolic tangent of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.atanh(BigDecimal('0.5'), 32).to_s + # #=> "0.54930614433405484569762261846126e0" + # + def atanh(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :atanh) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atanh) + raise Math::DomainError, "Out of domain argument for atanh" if x < -1 || x > 1 + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal::Internal.infinity_computation_result if x == 1 + return -BigDecimal::Internal.infinity_computation_result if x == -1 + + prec2 = prec + BigDecimal.double_fig + (BigMath.log(x + 1, prec2) - BigMath.log(1 - x, prec2)).div(2, prec) + end + + # call-seq: + # BigMath.log2(decimal, numeric) -> BigDecimal + # + # Computes the base 2 logarithm of +decimal+ to the specified number of + # digits of precision, +numeric+. + # + # If +decimal+ is zero or negative, raises Math::DomainError. + # + # If +decimal+ is positive infinity, returns Infinity. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.log2(BigDecimal('3'), 32).to_s + # #=> "0.15849625007211561814537389439478e1" + # + def log2(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :log2) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log2) + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal::Internal.infinity_computation_result if x.infinite? == 1 + + prec2 = prec + BigDecimal.double_fig * 3 / 2 + v = BigMath.log(x, prec2).div(BigMath.log(BigDecimal(2), prec2), prec2) + # Perform half-up rounding to calculate log2(2**n)==n correctly in every rounding mode + v = v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) + v.mult(1, prec) + end + + # call-seq: + # BigMath.log10(decimal, numeric) -> BigDecimal + # + # Computes the base 10 logarithm of +decimal+ to the specified number of + # digits of precision, +numeric+. + # + # If +decimal+ is zero or negative, raises Math::DomainError. + # + # If +decimal+ is positive infinity, returns Infinity. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.log10(BigDecimal('3'), 32).to_s + # #=> "0.47712125471966243729502790325512e0" + # + def log10(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :log10) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log10) + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal::Internal.infinity_computation_result if x.infinite? == 1 + + prec2 = prec + BigDecimal.double_fig * 3 / 2 + v = BigMath.log(x, prec2).div(BigMath.log(BigDecimal(10), prec2), prec2) + # Perform half-up rounding to calculate log10(10**n)==n correctly in every rounding mode + v = v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) + v.mult(1, prec) + end + + # call-seq: + # BigMath.log1p(decimal, numeric) -> BigDecimal + # + # Computes log(1 + decimal) to the specified number of digits of precision, +numeric+. + # + # BigMath.log1p(BigDecimal('0.1'), 32).to_s + # #=> "0.95310179804324860043952123280765e-1" + # + def log1p(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :log1p) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log1p) + raise Math::DomainError, 'Out of domain argument for log1p' if x < -1 + + return BigMath.log(x + 1, prec) + end + + # call-seq: + # BigMath.expm1(decimal, numeric) -> BigDecimal + # + # Computes exp(decimal) - 1 to the specified number of digits of precision, +numeric+. + # + # BigMath.expm1(BigDecimal('0.1'), 32).to_s + # #=> "0.10517091807564762481170782649025e0" + # + def expm1(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :expm1) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :expm1) + return BigDecimal(-1) if x.infinite? == -1 + + exp_prec = prec + if x < -1 + # log10(exp(x)) = x * log10(e) + lg_e = 0.4342944819032518 + exp_prec = prec + (lg_e * x).ceil + 2 + elsif x < 1 + exp_prec = prec - x.exponent + 2 + else + exp_prec = prec + end + exp_prec > 0 ? BigMath.exp(x, exp_prec).sub(1, prec) : BigDecimal(-1) + end + + # call-seq: # PI(numeric) -> BigDecimal # diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 066a13e4..baad5bb9 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -56,4 +56,55 @@ def assert_converge_in_precision(&block) assert_in_exact_precision(expected, value, n) end end + + + def assert_nan(x) + assert(x.nan?, "Expected #{x.inspect} to be NaN") + end + + def assert_positive_infinite(x) + assert(x.infinite?, "Expected #{x.inspect} to be positive infinite") + assert_operator(x, :>, 0) + end + + def assert_negative_infinite(x) + assert(x.infinite?, "Expected #{x.inspect} to be negative infinite") + assert_operator(x, :<, 0) + end + + def assert_infinite_calculation(positive:) + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) + positive ? assert_positive_infinite(yield) : assert_negative_infinite(yield) + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) + assert_raise_with_message(FloatDomainError, /Infinity/) { yield } + end + end + + def assert_positive_infinite_calculation(&block) + assert_infinite_calculation(positive: true, &block) + end + + def assert_negative_infinite_calculation(&block) + assert_infinite_calculation(positive: false, &block) + end + + def assert_nan_calculation(&block) + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + assert_nan(yield) + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) + assert_raise_with_message(FloatDomainError, /NaN/) { yield } + end + end + + def assert_positive_zero(x) + assert_equal(BigDecimal::SIGN_POSITIVE_ZERO, x.sign, + "Expected #{x.inspect} to be positive zero") + end + + def assert_negative_zero(x) + assert_equal(BigDecimal::SIGN_NEGATIVE_ZERO, x.sign, + "Expected #{x.inspect} to be negative zero") + end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 497d6290..8f1dd3d8 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -48,56 +48,6 @@ class TestBigDecimal < Test::Unit::TestCase [ BigDecimal::ROUND_FLOOR, :floor], ] - def assert_nan(x) - assert(x.nan?, "Expected #{x.inspect} to be NaN") - end - - def assert_positive_infinite(x) - assert(x.infinite?, "Expected #{x.inspect} to be positive infinite") - assert_operator(x, :>, 0) - end - - def assert_negative_infinite(x) - assert(x.infinite?, "Expected #{x.inspect} to be negative infinite") - assert_operator(x, :<, 0) - end - - def assert_infinite_calculation(positive:) - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) - positive ? assert_positive_infinite(yield) : assert_negative_infinite(yield) - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) - assert_raise_with_message(FloatDomainError, /Infinity/) { yield } - end - end - - def assert_positive_infinite_calculation(&block) - assert_infinite_calculation(positive: true, &block) - end - - def assert_negative_infinite_calculation(&block) - assert_infinite_calculation(positive: false, &block) - end - - def assert_nan_calculation(&block) - BigDecimal.save_exception_mode do - BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - assert_nan(yield) - BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) - assert_raise_with_message(FloatDomainError, /NaN/) { yield } - end - end - - def assert_positive_zero(x) - assert_equal(BigDecimal::SIGN_POSITIVE_ZERO, x.sign, - "Expected #{x.inspect} to be positive zero") - end - - def assert_negative_zero(x) - assert_equal(BigDecimal::SIGN_NEGATIVE_ZERO, x.sign, - "Expected #{x.inspect} to be negative zero") - end - def test_not_equal assert_not_equal BigDecimal("1"), BigDecimal("2") end diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 8d2f6ca5..1147a337 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -9,6 +9,7 @@ class TestBigMath < Test::Unit::TestCase # SQRT in 116 (= 100 + double_fig) digits SQRT2 = BigDecimal("1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309123") SQRT3 = BigDecimal("1.7320508075688772935274463415058723669428052538103806280558069794519330169088000370811461867572485756756261414154067") + SQRT5 = BigDecimal("2.2360679774997896964091736687312762354406183596115257242708972454105209256378048994144144083787822749695081761507738") PINF = BigDecimal("+Infinity") MINF = BigDecimal("-Infinity") NAN = BigDecimal("NaN") @@ -52,7 +53,7 @@ def assert_consistent_precision_acceptance(accept_zero: false) end def test_consistent_precision_acceptance - x = BigDecimal('1.23456789') + x = BigDecimal('0.123456789') # Exclude div because div(x, nil) is a special case assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.add(x, prec) } assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.sub(x, prec) } @@ -65,11 +66,52 @@ def test_consistent_precision_acceptance assert_consistent_precision_acceptance {|prec| BigMath.sin(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.cos(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.tan(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.asin(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.acos(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.atan(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.atan2(x, x + 1, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.sinh(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.cosh(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.tanh(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.asinh(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.acosh(x + 1, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.atanh(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.log2(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.log10(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.log1p(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.expm1(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.E(prec) } assert_consistent_precision_acceptance {|prec| BigMath.PI(prec) } end + def test_coerce_argument + f = 0.8 + bd = BigDecimal(f) + assert_equal(BigMath.exp(bd, N), BigMath.exp(f, N)) + assert_equal(BigMath.log(bd, N), BigMath.log(f, N)) + assert_equal(sqrt(bd, N), sqrt(f, N)) + assert_equal(cbrt(bd, N), cbrt(f, N)) + assert_equal(hypot(bd, bd, N), hypot(f, f, N)) + assert_equal(sin(bd, N), sin(f, N)) + assert_equal(cos(bd, N), cos(f, N)) + assert_equal(tan(bd, N), tan(f, N)) + assert_equal(asin(bd, N), asin(f, N)) + assert_equal(acos(bd, N), acos(f, N)) + assert_equal(atan(bd, N), atan(f, N)) + assert_equal(atan2(bd, bd, N), atan2(f, f, N)) + assert_equal(sinh(bd, N), sinh(f, N)) + assert_equal(cosh(bd, N), cosh(f, N)) + assert_equal(tanh(bd, N), tanh(f, N)) + assert_equal(asinh(bd, N), asinh(f, N)) + assert_equal(acosh(bd * 2, N), acosh(f * 2, N)) + assert_equal(atanh(bd, N), atanh(f, N)) + assert_equal(log2(bd, N), log2(f, N)) + assert_equal(log10(bd, N), log10(f, N)) + assert_equal(log1p(bd, N), log1p(f, N)) + assert_equal(expm1(bd, N), expm1(f, N)) + end + def test_sqrt assert_in_delta(2**0.5, sqrt(BigDecimal("2"), N)) assert_equal(10, sqrt(BigDecimal("100"), N)) @@ -85,6 +127,39 @@ def test_sqrt assert_converge_in_precision {|n| sqrt(BigDecimal("2e50"), n) } end + def test_cbrt + assert_equal(1234, cbrt(BigDecimal(1234**3), N)) + assert_equal(-12345, cbrt(BigDecimal(-12345**3), N)) + assert_equal(12345678987654321, cbrt(BigDecimal(12345678987654321) ** 3, N)) + assert_equal(0, cbrt(BigDecimal("0"), N)) + assert_equal(0, cbrt(BigDecimal("-0"), N)) + assert_positive_infinite_calculation { cbrt(PINF, N) } + assert_negative_infinite_calculation { cbrt(MINF, N) } + + assert_in_exact_precision(SQRT2, cbrt(SQRT2 ** 3, 100), 100) + assert_in_exact_precision(SQRT3, cbrt(SQRT3 ** 3, 100), 100) + assert_equal(BigDecimal("3e50"), cbrt(BigDecimal("27e150"), N)) + assert_equal(BigDecimal("-4e50"), cbrt(BigDecimal("-64e150"), N)) + assert_in_epsilon(Math.cbrt(28e150), cbrt(BigDecimal("28e150"), N)) + assert_in_epsilon(Math.cbrt(27e151), cbrt(BigDecimal("27e151"), N)) + assert_converge_in_precision {|n| cbrt(BigDecimal("2"), n) } + assert_converge_in_precision {|n| cbrt(BigDecimal("2e-50"), n) } + assert_converge_in_precision {|n| cbrt(SQRT2, n) } + assert_converge_in_precision {|n| cbrt(BigDecimal("2e50"), n) } + end + + def test_hypot + assert_in_exact_precision(SQRT2, hypot(BigDecimal("1"), BigDecimal("1"), 100), 100) + assert_in_exact_precision(SQRT5, hypot(SQRT2, SQRT3, 100), 100) + assert_equal(0, hypot(BigDecimal(0), BigDecimal(0), N)) + assert_positive_infinite_calculation { hypot(PINF, SQRT3, N) } + assert_positive_infinite_calculation { hypot(SQRT3, MINF, N) } + assert_converge_in_precision {|n| hypot(BigDecimal("1e-30"), BigDecimal("2e-30"), n) } + assert_converge_in_precision {|n| hypot(BigDecimal("1.23"), BigDecimal("4.56"), n) } + assert_converge_in_precision {|n| hypot(SQRT2 - 1, SQRT3 - 1, n) } + assert_converge_in_precision {|n| hypot(BigDecimal("2e30"), BigDecimal("1e30"), n) } + end + def test_sin assert_in_delta(0.0, sin(BigDecimal("0.0"), N)) assert_in_delta(Math.sqrt(2.0) / 2, sin(PI(N) / 4, N)) @@ -147,6 +222,41 @@ def test_tan assert_converge_in_precision {|n| tan(BigMath::PI(50), n) } end + def test_asin + ["-1", "-0.9", "-0.1", "0", "0.1", "0.9", "1"].each do |x| + assert_in_delta(Math.asin(x.to_f), asin(BigDecimal(x), N)) + end + assert_raise(Math::DomainError) { BigMath.asin(BigDecimal("1.1"), N) } + assert_raise(Math::DomainError) { BigMath.asin(BigDecimal("-1.1"), N) } + assert_in_exact_precision(PI(100) / 6, asin(BigDecimal("0.5"), 100), 100) + assert_converge_in_precision {|n| asin(BigDecimal("-0.4"), n) } + assert_converge_in_precision {|n| asin(BigDecimal("0.3"), n) } + assert_converge_in_precision {|n| asin(SQRT2 / 2, n) } + assert_converge_in_precision {|n| asin(BigDecimal("0.9"), n) } + assert_converge_in_precision {|n| asin(BigDecimal("0.#{"9" * 50}"), n) } + assert_converge_in_precision {|n| asin(BigDecimal("0.#{"9" * 100}"), n) } + assert_converge_in_precision {|n| asin(BigDecimal("0.#{"9" * 195}"), n) } + assert_converge_in_precision {|n| asin(BigDecimal("1e-30"), n) } + end + + def test_acos + ["-1", "-0.9", "-0.1", "0", "0.1", "0.9", "1"].each do |x| + assert_in_delta(Math.acos(x.to_f), acos(BigDecimal(x), N)) + end + assert_raise(Math::DomainError) { BigMath.acos(BigDecimal("1.1"), N) } + assert_raise(Math::DomainError) { BigMath.acos(BigDecimal("-1.1"), N) } + assert_equal(0, acos(BigDecimal("1.0"), N)) + assert_in_exact_precision(PI(100) / 3, acos(BigDecimal("0.5"), 100), 100) + assert_converge_in_precision {|n| acos(BigDecimal("-0.4"), n) } + assert_converge_in_precision {|n| acos(BigDecimal("0.3"), n) } + assert_converge_in_precision {|n| acos(SQRT2 / 2, n) } + assert_converge_in_precision {|n| acos(BigDecimal("0.9"), n) } + assert_converge_in_precision {|n| acos(BigDecimal("0.#{"9" * 50}"), n) } + assert_converge_in_precision {|n| acos(BigDecimal("0.#{"9" * 100}"), n) } + assert_converge_in_precision {|n| acos(BigDecimal("0.#{"9" * 195}"), n) } + assert_converge_in_precision {|n| acos(BigDecimal("1e-30"), n) } + end + def test_atan assert_equal(0.0, atan(BigDecimal("0.0"), N)) assert_in_delta(Math::PI/4, atan(BigDecimal("1.0"), N)) @@ -160,6 +270,103 @@ def test_atan assert_converge_in_precision {|n| atan(BigDecimal("1e30"), n)} end + def test_atan2 + zero = BigDecimal(0) + one = BigDecimal(1) + assert_equal(0, atan2(zero, zero, N)) + assert_equal(0, atan2(zero, one, N)) + [MINF, -one, -zero, zero, one, PINF].repeated_permutation(2) do |y, x| + assert_in_delta(Math::atan2(y.to_f, x.to_f), atan2(y, x, N)) + end + assert_in_exact_precision(PI(100), atan2(zero, -one, 100), 100) + assert_in_exact_precision(PI(100) / 2, atan2(one, zero, 100), 100) + assert_in_exact_precision(-PI(100) / 2, atan2(-one, zero, 100), 100) + assert_in_exact_precision(PI(100) / 3, atan2(BigDecimal(3), SQRT3, 100), 100) + assert_in_exact_precision(PI(100) / 6, atan2(SQRT3, BigDecimal(3), 100), 100) + assert_converge_in_precision {|n| atan2(SQRT2, SQRT3, n) } + ['-1e20', '-2', '-1e-30', '1e-30', '2', '1e20'].repeated_permutation(2) do |y, x| + assert_in_delta(Math.atan2(y.to_f, x.to_f), atan2(BigDecimal(y), BigDecimal(x), N)) + assert_converge_in_precision {|n| atan2(BigDecimal(y), BigDecimal(x), n) } + end + end + + def test_hyperbolic + [-1, 0, 0.5, 1, 10].each do |x| + assert_in_delta(Math.sinh(x), sinh(BigDecimal(x.to_s), N)) + assert_in_delta(Math.cosh(x), cosh(BigDecimal(x.to_s), N)) + assert_in_delta(Math.tanh(x), tanh(BigDecimal(x.to_s), N)) + end + assert_negative_infinite_calculation { sinh(MINF, N) } + assert_positive_infinite_calculation { sinh(PINF, N) } + assert_positive_infinite_calculation { cosh(MINF, N) } + assert_positive_infinite_calculation { cosh(PINF, N) } + assert_equal(-1, tanh(MINF, N)) + assert_equal(+1, tanh(PINF, N)) + + x = BigDecimal("0.3") + assert_in_exact_precision(sinh(x, 120) / cosh(x, 120), tanh(x, 100), 100) + assert_in_exact_precision(tanh(x, 120) * cosh(x, 120), sinh(x, 100), 100) + assert_in_exact_precision(sinh(x, 120) / tanh(x, 120), cosh(x, 100), 100) + + e = E(120) + assert_in_exact_precision((e - 1 / e) / 2, sinh(BigDecimal(1), 100), 100) + assert_in_exact_precision((e + 1 / e) / 2, cosh(BigDecimal(1), 100), 100) + assert_in_exact_precision((e - 1 / e) / (e + 1 / e), tanh(BigDecimal(1), 100), 100) + + ["1e-30", "0.2", SQRT2, "10", "100"].each do |x| + assert_converge_in_precision {|n| sinh(BigDecimal(x), n)} + assert_converge_in_precision {|n| cosh(BigDecimal(x), n)} + assert_converge_in_precision {|n| tanh(BigDecimal(x), n)} + end + end + + def test_asinh + [-3, 0.5, 10].each do |x| + assert_in_delta(Math.asinh(x), asinh(BigDecimal(x.to_s), N)) + end + assert_equal(0, asinh(BigDecimal(0), N)) + assert_positive_infinite_calculation { asinh(PINF, N) } + assert_negative_infinite_calculation { asinh(MINF, N) } + + x = SQRT2 / 2 + assert_in_exact_precision(x, asinh(sinh(x, 120), 100), 100) + + ["1e-30", "0.2", "10", "100"].each do |x| + assert_converge_in_precision {|n| asinh(BigDecimal(x), n)} + end + end + + def test_acosh + [1.5, 2, 10].each do |x| + assert_in_delta(Math.acosh(x), acosh(BigDecimal(x.to_s), N)) + end + assert_equal(0, acosh(BigDecimal(1), N)) + assert_positive_infinite_calculation { acosh(PINF, N) } + + x = SQRT2 + assert_in_exact_precision(x, acosh(cosh(x, 120), 100), 100) + + ["1." + "0" * 30 + "1", "1.5", "2", "100"].each do |x| + assert_converge_in_precision {|n| acosh(BigDecimal(x), n)} + end + end + + def test_atanh + [-0.5, 0.1, 0.9].each do |x| + assert_in_delta(Math.atanh(x), atanh(BigDecimal(x.to_s), N)) + end + assert_equal(0, atanh(BigDecimal(0), N)) + assert_positive_infinite_calculation { atanh(BigDecimal(1), N) } + assert_negative_infinite_calculation { atanh(BigDecimal(-1), N) } + + x = SQRT2 / 2 + assert_in_exact_precision(x, atanh(tanh(x, 120), 100), 100) + + ["1e-30", "0.5", "0.9" + "9" * 30].each do |x| + assert_converge_in_precision {|n| atanh(BigDecimal(x), n)} + end + end + def test_exp [-100, -2, 0.5, 10, 100].each do |x| assert_in_epsilon(Math.exp(x), BigMath.exp(BigDecimal(x, 0), N)) @@ -202,4 +409,64 @@ def test_log end SRC end + + def test_log2 + assert_raise(Math::DomainError) { log2(BigDecimal("-0.01"), N) } + assert_raise(Math::DomainError) { log2(MINF, N) } + assert_positive_infinite_calculation { log2(PINF, N) } + assert_in_exact_precision( + BigDecimal("1.5849625007211561814537389439478165087598144076924810604557526545410982277943585625222804749180882420909806624750592"), + log2(BigDecimal("3"), 100), + 100 + ) + assert_converge_in_precision {|n| log2(SQRT2, n) } + assert_converge_in_precision {|n| log2(BigDecimal("3e20"), n) } + assert_converge_in_precision {|n| log2(BigDecimal("1e-20") + 1, n) } + [BigDecimal::ROUND_UP, BigDecimal::ROUND_DOWN].each do |round_mode| + BigDecimal.mode(BigDecimal::ROUND_MODE, round_mode) + [0, 1, 2, 11, 123].each do |n| + assert_equal(n, log2(BigDecimal(2**n), N)) + end + end + end + + def test_log10 + assert_raise(Math::DomainError) { log10(BigDecimal("-0.01"), N) } + assert_raise(Math::DomainError) { log10(MINF, N) } + assert_positive_infinite_calculation { log10(PINF, N) } + assert_in_exact_precision( + BigDecimal("0.4771212547196624372950279032551153092001288641906958648298656403052291527836611230429683556476163015104646927682520"), + log10(BigDecimal("3"), 100), + 100 + ) + assert_converge_in_precision {|n| log10(SQRT2, n) } + assert_converge_in_precision {|n| log10(BigDecimal("3e20"), n) } + assert_converge_in_precision {|n| log10(BigDecimal("1e-20") + 1, n) } + [BigDecimal::ROUND_UP, BigDecimal::ROUND_DOWN].each do |round_mode| + BigDecimal.mode(BigDecimal::ROUND_MODE, round_mode) + [0, 1, 2, 11, 123].each do |n| + assert_equal(n, log10(BigDecimal(10**n), N)) + end + end + end + + def test_log1p + assert_raise(Math::DomainError) { log1p(MINF, N) } + assert_raise(Math::DomainError) { log1p(BigDecimal("-1.01"), N) } + assert_in_epsilon(Math.log(0.01), log1p(BigDecimal("-0.99"), N)) + assert_positive_infinite_calculation { log1p(PINF, N) } + assert_in_exact_precision(BigMath.log(1 + BigDecimal("1e-20"), 100), log1p(BigDecimal("1e-20"), 100), 100) + end + + def test_expm1 + assert_equal(-1, expm1(MINF, N)) + assert_positive_infinite_calculation { expm1(PINF, N) } + assert_equal(-1, expm1(BigDecimal("-400"), 100)) + assert_equal(-1, expm1(BigDecimal("-231"), 100)) + assert_not_equal(-1, expm1(BigDecimal("-229"), 100)) + assert_in_exact_precision(BigMath.exp(-220, 100) - 1, expm1(BigDecimal("-220"), 100), 100) + assert_in_exact_precision(BigMath.exp(-3, 100) - 1, expm1(BigDecimal("-3"), 100), 100) + assert_in_exact_precision(BigMath.exp(BigDecimal("1.23e-10"), 120) - 1, expm1(BigDecimal("1.23e-10"), 100), 100) + assert_in_exact_precision(BigMath.exp(123, 120) - 1, expm1(BigDecimal("123"), 100), 100) + end end From d35e82be24970e0e8510b207b10b5779936b999d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 29 Oct 2025 01:13:26 +0900 Subject: [PATCH 475/546] Fix fast-path of frac and _decimal_shift affected by BigDecimal.limit (#447) --- ext/bigdecimal/bigdecimal.c | 6 +++--- test/bigdecimal/test_bigdecimal.rb | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 594459db..d9247790 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2507,7 +2507,7 @@ BigDecimal_decimal_shift(VALUE self, VALUE v) prec = a.real->Prec + shiftDown; c = NewZeroWrap(1, prec * BASE_FIG); if (shift == 0) { - VpAsgn(c.real, a.real, 1); + VpAsgn(c.real, a.real, 10); } else if (shiftDown) { DECDIG carry = 0; exponentShift++; @@ -6166,7 +6166,7 @@ VpFrac(Real *y, Real *x) size_t my, ind_y, ind_x; if (!VpHasVal(x)) { - VpAsgn(y, x, 1); + VpAsgn(y, x, 10); goto Exit; } @@ -6175,7 +6175,7 @@ VpFrac(Real *y, Real *x) goto Exit; } else if (x->exponent <= 0) { - VpAsgn(y, x, 1); + VpAsgn(y, x, 10); goto Exit; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 8f1dd3d8..32ba442b 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1440,6 +1440,11 @@ def test_internal_use_decimal_shift assert_equal(num / 10**shift, num._decimal_shift(-shift)) end end + BigDecimal.save_limit do + BigDecimal.limit(3) + assert_equal(BigDecimal('123456789e4'), BigDecimal('123456789')._decimal_shift(4)) + assert_equal(BigDecimal('123456789e9'), BigDecimal('123456789')._decimal_shift(9)) + end end def test_fix @@ -1454,6 +1459,11 @@ def test_frac assert_equal(0.1, BigDecimal("0.1").frac) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) assert_nan(BigDecimal("NaN").frac) + BigDecimal.save_limit do + BigDecimal.limit(3) + assert_equal(BigDecimal('0.456789'), BigDecimal('123.456789').frac) + assert_equal(BigDecimal('0.0123456789'), BigDecimal('0.0123456789').frac) + end end def test_round From 3fb5616f3df1ed2ca7e79e0f0cd20addd00ee01c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 29 Oct 2025 15:07:30 +0900 Subject: [PATCH 476/546] Update the latest versions of actions --- .github/workflows/push_gem.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index a57d50fb..672223be 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -31,16 +31,16 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Ruby - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 + uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 with: bundler-cache: true ruby-version: ${{ matrix.ruby }} - name: Publish to RubyGems - uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 + uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2 - name: Create GitHub release run: | From d57013e45044664356a4c80a443647a6a617477f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 30 Oct 2025 03:43:20 +0900 Subject: [PATCH 477/546] Add missing bigmath precision test, add missing indent (#450) --- test/bigdecimal/test_bigdecimal.rb | 2 +- test/bigdecimal/test_bigmath.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 32ba442b..35736b94 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2064,7 +2064,7 @@ def test_sqrt_with_huge_limit end end -def test_power_with_huge_limit + def test_power_with_huge_limit BigDecimal.save_limit do x = BigDecimal("12.34") y = BigDecimal("56.78") diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 1147a337..3fbf24c2 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -60,9 +60,11 @@ def test_consistent_precision_acceptance assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.mult(x, prec) } assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.power(x, prec) } assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.sqrt(prec) } - assert_consistent_precision_acceptance {|prec| BigMath.sqrt(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.exp(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.log(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.sqrt(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.cbrt(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.hypot(x, x + 1, prec) } assert_consistent_precision_acceptance {|prec| BigMath.sin(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.cos(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.tan(x, prec) } From a6d0db190d468c5657310eb1a694089fb2c8fae9 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 5 Nov 2025 15:32:25 +0900 Subject: [PATCH 478/546] Make BigMath.exp and log also a module_method (#452) --- lib/bigdecimal.rb | 7 ++--- lib/bigdecimal/math.rb | 22 +++++++-------- test/bigdecimal/test_bigmath.rb | 48 ++++++++++++++++----------------- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 22719746..e9f8954b 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -238,6 +238,7 @@ def sqrt(prec) # Core BigMath methods for BigDecimal (log, exp) are defined here. # Other methods (sin, cos, atan) are defined in 'bigdecimal/math.rb'. module BigMath + module_function # call-seq: # BigMath.log(decimal, numeric) -> BigDecimal @@ -251,7 +252,7 @@ module BigMath # # If +decimal+ is NaN, returns NaN. # - def self.log(x, prec) + def log(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :log) raise Math::DomainError, 'Complex argument for BigMath.log' if Complex === x @@ -306,7 +307,7 @@ def self.log(x, prec) end # Taylor series for exp(x) around 0 - private_class_method def self._exp_taylor(x, prec) # :nodoc: + private_class_method def _exp_taylor(x, prec) # :nodoc: xn = BigDecimal(1) y = BigDecimal(1) 1.step do |i| @@ -328,7 +329,7 @@ def self.log(x, prec) # # If +decimal+ is NaN, returns NaN. # - def self.exp(x, prec) + def exp(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :exp) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :exp) return BigDecimal::Internal.nan_computation_result if x.nan? diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 16e095df..d0d49cb8 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -355,7 +355,7 @@ def sinh(x, prec) prec2 = prec + BigDecimal.double_fig prec2 -= x.exponent if x.exponent < 0 - e = BigMath.exp(x, prec2) + e = exp(x, prec2) (e - BigDecimal(1).div(e, prec2)).div(2, prec) end @@ -377,7 +377,7 @@ def cosh(x, prec) return BigDecimal::Internal.infinity_computation_result if x.infinite? prec2 = prec + BigDecimal.double_fig - e = BigMath.exp(x, prec2) + e = exp(x, prec2) (e + BigDecimal(1).div(e, prec2)).div(2, prec) end @@ -399,7 +399,7 @@ def tanh(x, prec) return BigDecimal(x.infinite?) if x.infinite? prec2 = prec + BigDecimal.double_fig + [-x.exponent, 0].max - e = BigMath.exp(x, prec2) + e = exp(x, prec2) einv = BigDecimal(1).div(e, prec2) (e - einv).div(e + einv, prec) end @@ -423,7 +423,7 @@ def asinh(x, prec) return -asinh(-x, prec) if x < 0 sqrt_prec = prec + [-x.exponent, 0].max + BigDecimal.double_fig - BigMath.log(x + sqrt(x**2 + 1, sqrt_prec), prec) + log(x + sqrt(x**2 + 1, sqrt_prec), prec) end # call-seq: @@ -444,7 +444,7 @@ def acosh(x, prec) return BigDecimal::Internal.infinity_computation_result if x.infinite? return BigDecimal::Internal.nan_computation_result if x.nan? - BigMath.log(x + sqrt(x**2 - 1, prec + BigDecimal.double_fig), prec) + log(x + sqrt(x**2 - 1, prec + BigDecimal.double_fig), prec) end # call-seq: @@ -467,7 +467,7 @@ def atanh(x, prec) return -BigDecimal::Internal.infinity_computation_result if x == -1 prec2 = prec + BigDecimal.double_fig - (BigMath.log(x + 1, prec2) - BigMath.log(1 - x, prec2)).div(2, prec) + (log(x + 1, prec2) - log(1 - x, prec2)).div(2, prec) end # call-seq: @@ -492,7 +492,7 @@ def log2(x, prec) return BigDecimal::Internal.infinity_computation_result if x.infinite? == 1 prec2 = prec + BigDecimal.double_fig * 3 / 2 - v = BigMath.log(x, prec2).div(BigMath.log(BigDecimal(2), prec2), prec2) + v = log(x, prec2).div(log(BigDecimal(2), prec2), prec2) # Perform half-up rounding to calculate log2(2**n)==n correctly in every rounding mode v = v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) v.mult(1, prec) @@ -520,7 +520,7 @@ def log10(x, prec) return BigDecimal::Internal.infinity_computation_result if x.infinite? == 1 prec2 = prec + BigDecimal.double_fig * 3 / 2 - v = BigMath.log(x, prec2).div(BigMath.log(BigDecimal(10), prec2), prec2) + v = log(x, prec2).div(log(BigDecimal(10), prec2), prec2) # Perform half-up rounding to calculate log10(10**n)==n correctly in every rounding mode v = v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) v.mult(1, prec) @@ -539,7 +539,7 @@ def log1p(x, prec) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log1p) raise Math::DomainError, 'Out of domain argument for log1p' if x < -1 - return BigMath.log(x + 1, prec) + return log(x + 1, prec) end # call-seq: @@ -565,7 +565,7 @@ def expm1(x, prec) else exp_prec = prec end - exp_prec > 0 ? BigMath.exp(x, exp_prec).sub(1, prec) : BigDecimal(-1) + exp_prec > 0 ? exp(x, exp_prec).sub(1, prec) : BigDecimal(-1) end @@ -625,6 +625,6 @@ def PI(prec) # def E(prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :E) - BigMath.exp(1, prec) + exp(1, prec) end end diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 3fbf24c2..86d3cf96 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -90,8 +90,8 @@ def test_consistent_precision_acceptance def test_coerce_argument f = 0.8 bd = BigDecimal(f) - assert_equal(BigMath.exp(bd, N), BigMath.exp(f, N)) - assert_equal(BigMath.log(bd, N), BigMath.log(f, N)) + assert_equal(exp(bd, N), exp(f, N)) + assert_equal(log(bd, N), log(f, N)) assert_equal(sqrt(bd, N), sqrt(f, N)) assert_equal(cbrt(bd, N), cbrt(f, N)) assert_equal(hypot(bd, bd, N), hypot(f, f, N)) @@ -371,35 +371,35 @@ def test_atanh def test_exp [-100, -2, 0.5, 10, 100].each do |x| - assert_in_epsilon(Math.exp(x), BigMath.exp(BigDecimal(x, 0), N)) + assert_in_epsilon(Math.exp(x), exp(BigDecimal(x, 0), N)) end - assert_equal(1, BigMath.exp(BigDecimal("0"), N)) + assert_equal(1, exp(BigDecimal("0"), N)) assert_in_exact_precision( BigDecimal("4.48168907033806482260205546011927581900574986836966705677265008278593667446671377298105383138245339138861635065183019577"), - BigMath.exp(BigDecimal("1.5"), 100), + exp(BigDecimal("1.5"), 100), 100 ) - assert_converge_in_precision {|n| BigMath.exp(BigDecimal("1"), n) } - assert_converge_in_precision {|n| BigMath.exp(BigDecimal("-2"), n) } - assert_converge_in_precision {|n| BigMath.exp(BigDecimal("-34"), n) } - assert_converge_in_precision {|n| BigMath.exp(BigDecimal("567"), n) } - assert_converge_in_precision {|n| BigMath.exp(SQRT2, n) } + assert_converge_in_precision {|n| exp(BigDecimal("1"), n) } + assert_converge_in_precision {|n| exp(BigDecimal("-2"), n) } + assert_converge_in_precision {|n| exp(BigDecimal("-34"), n) } + assert_converge_in_precision {|n| exp(BigDecimal("567"), n) } + assert_converge_in_precision {|n| exp(SQRT2, n) } end def test_log - assert_equal(0, BigMath.log(BigDecimal("1.0"), 10)) - assert_in_epsilon(Math.log(10)*1000, BigMath.log(BigDecimal("1e1000"), 10)) + assert_equal(0, log(BigDecimal("1.0"), 10)) + assert_in_epsilon(Math.log(10)*1000, log(BigDecimal("1e1000"), 10)) assert_in_exact_precision( BigDecimal("2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862"), - BigMath.log(BigDecimal("10"), 100), + log(BigDecimal("10"), 100), 100 ) - assert_converge_in_precision {|n| BigMath.log(BigDecimal("2"), n) } - assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e-30") + 1, n) } - assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e-30"), n) } - assert_converge_in_precision {|n| BigMath.log(BigDecimal("1e30"), n) } - assert_converge_in_precision {|n| BigMath.log(SQRT2, n) } - assert_raise(Math::DomainError) {BigMath.log(BigDecimal("-0.1"), 10)} + assert_converge_in_precision {|n| log(BigDecimal("2"), n) } + assert_converge_in_precision {|n| log(BigDecimal("1e-30") + 1, n) } + assert_converge_in_precision {|n| log(BigDecimal("1e-30"), n) } + assert_converge_in_precision {|n| log(BigDecimal("1e30"), n) } + assert_converge_in_precision {|n| log(SQRT2, n) } + assert_raise(Math::DomainError) {log(BigDecimal("-0.1"), 10)} assert_separately(%w[-rbigdecimal], <<-SRC) begin x = BigMath.log(BigDecimal("1E19999999999999"), 10) @@ -457,7 +457,7 @@ def test_log1p assert_raise(Math::DomainError) { log1p(BigDecimal("-1.01"), N) } assert_in_epsilon(Math.log(0.01), log1p(BigDecimal("-0.99"), N)) assert_positive_infinite_calculation { log1p(PINF, N) } - assert_in_exact_precision(BigMath.log(1 + BigDecimal("1e-20"), 100), log1p(BigDecimal("1e-20"), 100), 100) + assert_in_exact_precision(log(1 + BigDecimal("1e-20"), 100), log1p(BigDecimal("1e-20"), 100), 100) end def test_expm1 @@ -466,9 +466,9 @@ def test_expm1 assert_equal(-1, expm1(BigDecimal("-400"), 100)) assert_equal(-1, expm1(BigDecimal("-231"), 100)) assert_not_equal(-1, expm1(BigDecimal("-229"), 100)) - assert_in_exact_precision(BigMath.exp(-220, 100) - 1, expm1(BigDecimal("-220"), 100), 100) - assert_in_exact_precision(BigMath.exp(-3, 100) - 1, expm1(BigDecimal("-3"), 100), 100) - assert_in_exact_precision(BigMath.exp(BigDecimal("1.23e-10"), 120) - 1, expm1(BigDecimal("1.23e-10"), 100), 100) - assert_in_exact_precision(BigMath.exp(123, 120) - 1, expm1(BigDecimal("123"), 100), 100) + assert_in_exact_precision(exp(-220, 100) - 1, expm1(BigDecimal("-220"), 100), 100) + assert_in_exact_precision(exp(-3, 100) - 1, expm1(BigDecimal("-3"), 100), 100) + assert_in_exact_precision(exp(BigDecimal("1.23e-10"), 120) - 1, expm1(BigDecimal("1.23e-10"), 100), 100) + assert_in_exact_precision(exp(123, 120) - 1, expm1(BigDecimal("123"), 100), 100) end end From 9a45a00cf077948841b07e40c8709544843e6d49 Mon Sep 17 00:00:00 2001 From: Troy <101618367+troy-dunamu@users.noreply.github.com> Date: Wed, 12 Nov 2025 02:27:29 +0900 Subject: [PATCH 479/546] Fix incorrect exception when exponent is fractional for Infinity base (#453) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle Infinity base in power’s precision estimator * Apply precision path only when x > 1 && x.finite? --- lib/bigdecimal.rb | 2 +- test/bigdecimal/test_bigdecimal.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index e9f8954b..12250ce9 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -194,7 +194,7 @@ def power(y, prec = 0) end ans.mult(1, result_prec) else - if x > 1 + if x > 1 && x.finite? # To calculate exp(z, prec), z needs prec+max(z.exponent, 0) precision if z > 0. # Estimate (y*log(x)).exponent logx_exponent = x < 2 ? (x - 1).exponent : Math.log10(x.exponent).round diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 35736b94..4f2ded4f 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1851,10 +1851,16 @@ def test_power_of_positive_infinity assert_positive_infinite_calculation { BigDecimal::INFINITY ** 1.quo(1) } assert_positive_infinite_calculation { BigDecimal::INFINITY ** 1.0 } assert_positive_infinite_calculation { BigDecimal::INFINITY ** BigDecimal(1) } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 1.quo(2) } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** 0.5 } + assert_positive_infinite_calculation { BigDecimal::INFINITY ** BigDecimal(0.5) } assert_equal(1, BigDecimal::INFINITY ** 0) assert_equal(1, BigDecimal::INFINITY ** 0.quo(1)) assert_equal(1, BigDecimal::INFINITY ** 0.0) assert_equal(1, BigDecimal::INFINITY ** BigDecimal(0)) + assert_positive_zero(BigDecimal::INFINITY ** -1.quo(2)) + assert_positive_zero(BigDecimal::INFINITY ** -0.5) + assert_positive_zero(BigDecimal::INFINITY ** BigDecimal(-0.5)) assert_positive_zero(BigDecimal::INFINITY ** -1) assert_positive_zero(BigDecimal::INFINITY ** -1.quo(1)) assert_positive_zero(BigDecimal::INFINITY ** -1.0) From 3e1f0ec2c7efc566c727612af9c0b53d42f87aa9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 02:28:33 +0900 Subject: [PATCH 480/546] Bump step-security/harden-runner from 2.13.1 to 2.13.2 (#454) Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.1 to 2.13.2. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a...95d9a5deda9de15063e7595e9719c11c38c90ae2) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.13.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 672223be..92fd8c90 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit From 84646b8db3818cceca4c73ad572f9c0a1390cf7f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 12 Nov 2025 03:13:52 +0900 Subject: [PATCH 481/546] Don't use assert_separatly if not needed (#455) --- test/bigdecimal/test_bigdecimal.rb | 17 ++++++++--------- test/bigdecimal/test_bigmath.rb | 6 ++---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 4f2ded4f..71a05979 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -786,15 +786,14 @@ def test_precs_deprecated end def test_precs - assert_separately(["-rbigdecimal"], "#{<<~"begin;"}\n#{<<~'end;'}") - begin; - $VERBOSE = nil - a = BigDecimal("1").precs - assert_instance_of(Array, a) - assert_equal(2, a.size) - assert_kind_of(Integer, a[0]) - assert_kind_of(Integer, a[1]) - end; + $VERBOSE, verbose = nil, $VERBOSE + a = BigDecimal("1").precs + assert_instance_of(Array, a) + assert_equal(2, a.size) + assert_kind_of(Integer, a[0]) + assert_kind_of(Integer, a[1]) + ensure + $VERBOSE = verbose end def test_hash diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 86d3cf96..39dee611 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -400,16 +400,14 @@ def test_log assert_converge_in_precision {|n| log(BigDecimal("1e30"), n) } assert_converge_in_precision {|n| log(SQRT2, n) } assert_raise(Math::DomainError) {log(BigDecimal("-0.1"), 10)} - assert_separately(%w[-rbigdecimal], <<-SRC) begin - x = BigMath.log(BigDecimal("1E19999999999999"), 10) + x = BigDecimal("1E19999999999999") rescue FloatDomainError else unless x.infinite? - assert_in_epsilon(Math.log(10)*19999999999999, x) + assert_in_epsilon(Math.log(10) * 19999999999999, BigMath.log(x, 10)) end end - SRC end def test_log2 From 5006b8dcd9a7e76ad53d83fc586770a3a0456ed9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 12:46:18 +0000 Subject: [PATCH 482/546] Bump actions/checkout from 5.0.0 to 6.0.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/jruby_test.yml | 2 +- .github/workflows/push_gem.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index a0531481..3c4f78d4 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -43,7 +43,7 @@ jobs: - { os: windows-latest , ruby: "3.2" } steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6.0.0 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae9a3b06..79016354 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: BIGDECIMAL_USE_VP_TEST_METHODS: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6.0.0 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/jruby_test.yml b/.github/workflows/jruby_test.yml index c82ed5ba..4ae50e8a 100644 --- a/.github/workflows/jruby_test.yml +++ b/.github/workflows/jruby_test.yml @@ -22,7 +22,7 @@ jobs: - jruby-head steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6.0.0 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 92fd8c90..2801e6a2 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -31,7 +31,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up Ruby uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 From b176820463e9631283086fd7e823b46f65ddf524 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:21:52 +0000 Subject: [PATCH 483/546] Bump actions/checkout from 5.0.1 to 6.0.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 2801e6a2..6160df16 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -31,7 +31,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5 # v5.0.1 - name: Set up Ruby uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 From 63afffa46616b936541b07cc7d3e2f9e7dfa06f3 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 3 Dec 2025 19:43:45 +0900 Subject: [PATCH 484/546] Add missing BigMath test for jruby (#459) * Add missing BigMath test for jruby * Fix BigMath.cbrt to work in jruby --- lib/bigdecimal/math.rb | 2 +- test/bigdecimal/test_jruby.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index d0d49cb8..aa2175cf 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -107,7 +107,7 @@ def cbrt(x, prec) x = -x if neg = x < 0 ex = x.exponent / 3 x = x._decimal_shift(-3 * ex) - y = BigDecimal(Math.cbrt(x.to_f)) + y = BigDecimal(Math.cbrt(x.to_f), 0) precs = [prec + BigDecimal.double_fig] precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig precs.reverse_each do |p| diff --git a/test/bigdecimal/test_jruby.rb b/test/bigdecimal/test_jruby.rb index 8641d8dd..f7cf81a8 100644 --- a/test/bigdecimal/test_jruby.rb +++ b/test/bigdecimal/test_jruby.rb @@ -52,9 +52,25 @@ def test_power def test_bigmath assert_in_delta(Math.sqrt(2), BigMath.sqrt(BigDecimal(2), N)) + assert_in_delta(Math.cbrt(2), BigMath.cbrt(BigDecimal(2), N)) + assert_in_delta(Math.hypot(2, 3), BigMath.hypot(BigDecimal(2), BigDecimal(3), N)) assert_in_delta(Math.sin(1), BigMath.sin(BigDecimal(1), N)) assert_in_delta(Math.cos(1), BigMath.cos(BigDecimal(1), N)) + assert_in_delta(Math.tan(1), BigMath.tan(BigDecimal(1), N)) + assert_in_delta(Math.asin(0.5), BigMath.asin(BigDecimal('0.5'), N)) + assert_in_delta(Math.acos(0.5), BigMath.acos(BigDecimal('0.5'), N)) assert_in_delta(Math.atan(1), BigMath.atan(BigDecimal(1), N)) + assert_in_delta(Math.atan2(1, 2), BigMath.atan2(BigDecimal(1), BigDecimal(2), N)) + assert_in_delta(Math.sinh(1), BigMath.sinh(BigDecimal(1), N)) + assert_in_delta(Math.cosh(1), BigMath.cosh(BigDecimal(1), N)) + assert_in_delta(Math.tanh(1), BigMath.tanh(BigDecimal(1), N)) + assert_in_delta(Math.asinh(1), BigMath.asinh(BigDecimal(1), N)) + assert_in_delta(Math.acosh(2), BigMath.acosh(BigDecimal(2), N)) + assert_in_delta(Math.atanh(0.5), BigMath.atanh(BigDecimal('0.5'), N)) + assert_in_delta(Math.log2(3), BigMath.log2(BigDecimal(3), N)) + assert_in_delta(Math.log10(3), BigMath.log10(BigDecimal(3), N)) + assert_in_delta(Math.log1p(0.1), BigMath.log1p(BigDecimal('0.1'), N)) if defined? Math.log1p + assert_in_delta(Math.expm1(0.1), BigMath.expm1(BigDecimal('0.1'), N)) if defined? Math.expm1 assert_in_delta(Math::PI, BigMath.PI(N)) assert_in_delta(Math::E, BigMath.E(N)) end From 55d30c839168e3e26187c4d40b67decfd6f941aa Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 3 Dec 2025 20:22:12 +0900 Subject: [PATCH 485/546] Change remainder/modulo/divmod test of +0/-0 type tolerant (#460) --- test/bigdecimal/test_bigdecimal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 71a05979..0062bfe1 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2725,7 +2725,7 @@ def test_signed_zero_modulo_remainder [-1.0, +1.0].each do |b| assert_equal(BigDecimal(a.remainder(b)).to_s, BigDecimal(a).remainder(BigDecimal(b)).to_s) assert_equal(BigDecimal(a.modulo(b)).to_s, BigDecimal(a).modulo(BigDecimal(b)).to_s) - assert_equal(a.divmod(b).map {|x| BigDecimal(x)}.inspect, BigDecimal(a).divmod(BigDecimal(b)).inspect) + assert_equal(a.divmod(b).map {|x| BigDecimal(x)}.inspect, BigDecimal(a).divmod(BigDecimal(b)).map {|x| BigDecimal(x)}.inspect) end end end From 150d01d49bac4af46c1cd045efc6d7de7526ad43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciek=20Rz=C4=85sa?= Date: Wed, 3 Dec 2025 12:33:55 +0100 Subject: [PATCH 486/546] Cast divmod quotient to int (#312) * Cast divmod quotient to int * fixup! Cast divmod quotient to int --------- Co-authored-by: tomoya ishida --- ext/bigdecimal/bigdecimal.c | 2 +- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d9247790..f2e7deeb 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1867,7 +1867,7 @@ BigDecimal_divmod(VALUE self, VALUE r) NULLABLE_BDVALUE div, mod; if (BigDecimal_DoDivmod(self, r, &div, &mod, false)) { - return rb_assoc_new(CheckGetValue(bdvalue_nonnullable(div)), CheckGetValue(bdvalue_nonnullable(mod))); + return rb_assoc_new(BigDecimal_to_i(CheckGetValue(bdvalue_nonnullable(div))), CheckGetValue(bdvalue_nonnullable(mod))); } return DoSomeOne(self,r,rb_intern("divmod")); } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 0062bfe1..d3ae0002 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1245,6 +1245,10 @@ def test_divmod assert_equal([0, 0], BigDecimal("0").divmod(2)) + quotient, reminder = BigDecimal("10").divmod(3) + assert_kind_of(Integer, quotient) + assert_kind_of(BigDecimal, reminder) + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) assert_raise(ZeroDivisionError){BigDecimal("0").divmod(0)} end From 2e7555c04a1483125ac2d4951172e35036e9ef3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:03:21 +0000 Subject: [PATCH 487/546] Bump step-security/harden-runner from 2.13.2 to 2.13.3 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.2 to 2.13.3. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/95d9a5deda9de15063e7595e9719c11c38c90ae2...df199fb7be9f65074067a9eb93f12bb4c5547cf2) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.13.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 6160df16..92014bff 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit From 79a00c61c4bed7e752f747c06ea67dd7f33c2d40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:03:27 +0000 Subject: [PATCH 488/546] Bump actions/checkout from 6.0.0 to 6.0.1 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v6...v6.0.1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/jruby_test.yml | 2 +- .github/workflows/push_gem.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 3c4f78d4..a585adb8 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -43,7 +43,7 @@ jobs: - { os: windows-latest , ruby: "3.2" } steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.1 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79016354..68816212 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: BIGDECIMAL_USE_VP_TEST_METHODS: true steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.1 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/jruby_test.yml b/.github/workflows/jruby_test.yml index 4ae50e8a..4b0be8b3 100644 --- a/.github/workflows/jruby_test.yml +++ b/.github/workflows/jruby_test.yml @@ -22,7 +22,7 @@ jobs: - jruby-head steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.1 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 6160df16..8ab3595b 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -31,7 +31,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5 # v5.0.1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.1 - name: Set up Ruby uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 From d1a2bc190f081e4ad45365397235ff8947e1aa46 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 10 Dec 2025 22:45:26 +0900 Subject: [PATCH 489/546] Implement BigMath.erf(x, prec) and BigMath.erfc(x, prec) (#357) Uses asymptotic expansion of erfc if possible and fallback to taylor series of erf --- lib/bigdecimal/math.rb | 130 ++++++++++++++++++++++++++++++++ test/bigdecimal/test_bigmath.rb | 58 ++++++++++++++ test/bigdecimal/test_jruby.rb | 2 + 3 files changed, 190 insertions(+) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index aa2175cf..b59f1849 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -24,6 +24,8 @@ # log10(x, prec) # log1p(x, prec) # expm1(x, prec) +# erf (x, prec) +# erfc(x, prec) # PI (prec) # E (prec) == exp(1.0,prec) # @@ -568,6 +570,134 @@ def expm1(x, prec) exp_prec > 0 ? exp(x, exp_prec).sub(1, prec) : BigDecimal(-1) end + # call-seq: + # erf(decimal, numeric) -> BigDecimal + # + # Computes the error function of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.erf(BigDecimal('1'), 32).to_s + # #=> "0.84270079294971486934122063508261e0" + # + def erf(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :erf) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erf) + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal(x.infinite?) if x.infinite? + return BigDecimal(0) if x == 0 + return -erf(-x, prec) if x < 0 + return BigDecimal(1) if x > 5000000000 # erf(5000000000) > 1 - 1e-10000000000000000000 + + if x > 8 + xf = x.to_f + log10_erfc = -xf ** 2 / Math.log(10) - Math.log10(xf * Math::PI ** 0.5) + erfc_prec = [prec + log10_erfc.ceil, 1].max + erfc = _erfc_asymptotic(x, erfc_prec) + return BigDecimal(1).sub(erfc, prec) if erfc + end + + prec2 = prec + BigDecimal.double_fig + x_smallprec = x.mult(1, Integer.sqrt(prec2) / 2) + # Taylor series of x with small precision is fast + erf1 = _erf_taylor(x_smallprec, BigDecimal(0), BigDecimal(0), prec2) + # Taylor series converges quickly for small x + _erf_taylor(x - x_smallprec, x_smallprec, erf1, prec2).mult(1, prec) + end + + # call-seq: + # erfc(decimal, numeric) -> BigDecimal + # + # Computes the complementary error function of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.erfc(BigDecimal('10'), 32).to_s + # #=> "0.20884875837625447570007862949578e-44" + # + def erfc(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :erfc) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erfc) + return BigDecimal::Internal.nan_computation_result if x.nan? + return BigDecimal(1 - x.infinite?) if x.infinite? + return BigDecimal(1).sub(erf(x, prec), prec) if x < 0 + return BigDecimal(0) if x > 5000000000 # erfc(5000000000) < 1e-10000000000000000000 (underflow) + + if x >= 8 + y = _erfc_asymptotic(x, prec) + return y.mult(1, prec) if y + end + + # erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi) + # Precision of erf(x) needs about log10(exp(-x**2)) extra digits + log10 = 2.302585092994046 + high_prec = prec + BigDecimal.double_fig + (x.ceil**2 / log10).ceil + BigDecimal(1).sub(erf(x, high_prec), prec) + end + + # Calculates erf(x + a) + private_class_method def _erf_taylor(x, a, erf_a, prec) # :nodoc: + return erf_a if x.zero? + # Let f(x+a) = erf(x+a)*exp((x+a)**2)*sqrt(pi)/2 + # = c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + ... + # f'(x+a) = 1+2*(x+a)*f(x+a) + # f'(x+a) = c1 + 2*c2*x + 3*c3*x**2 + 4*c4*x**3 + 5*c5*x**4 + ... + # = 1+2*(x+a)*(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + ...) + # therefore, + # c0 = f(a) + # c1 = 2 * a * c0 + 1 + # c2 = (2 * c0 + 2 * a * c1) / 2 + # c3 = (2 * c1 + 2 * a * c2) / 3 + # c4 = (2 * c2 + 2 * a * c3) / 4 + # + # All coefficients are positive when a >= 0 + + scale = BigDecimal(2).div(sqrt(PI(prec), prec), prec) + c_prev = erf_a.div(scale.mult(exp(-a*a, prec), prec), prec) + c_next = (2 * a * c_prev).add(1, prec).mult(x, prec) + sum = c_prev.add(c_next, prec) + + 2.step do |k| + c = (c_prev.mult(x, prec) + a * c_next).mult(2, prec).mult(x, prec).div(k, prec) + sum = sum.add(c, prec) + c_prev, c_next = c_next, c + break if [c_prev, c_next].all? { |c| c.zero? || (c.exponent < sum.exponent - prec) } + end + value = sum.mult(scale.mult(exp(-(x + a).mult(x + a, prec), prec), prec), prec) + value > 1 ? BigDecimal(1) : value + end + + private_class_method def _erfc_asymptotic(x, prec) # :nodoc: + # Let f(x) = erfc(x)*sqrt(pi)*exp(x**2)/2 + # f(x) satisfies the following differential equation: + # 2*x*f(x) = f'(x) + 1 + # From the above equation, we can derive the following asymptotic expansion: + # f(x) = (0..kmax).sum { (-1)**k * (2*k)! / 4**k / k! / x**(2*k)) } / x + + # This asymptotic expansion does not converge. + # But if there is a k that satisfies (2*k)! / 4**k / k! / x**(2*k) < 10**(-prec), + # It is enough to calculate erfc within the given precision. + # Using Stirling's approximation, we can simplify this condition to: + # sqrt(2)/2 + k*log(k) - k - 2*k*log(x) < -prec*log(10) + # and the left side is minimized when k = x**2. + prec += BigDecimal.double_fig + xf = x.to_f + kmax = (1..(xf ** 2).floor).bsearch do |k| + Math.log(2) / 2 + k * Math.log(k) - k - 2 * k * Math.log(xf) < -prec * Math.log(10) + end + return unless kmax + + sum = BigDecimal(1) + x2 = x.mult(x, prec) + d = BigDecimal(1) + (1..kmax).each do |k| + d = d.div(x2, prec).mult(1 - 2 * k, prec).div(2, prec) + sum = sum.add(d, prec) + end + sum.div(exp(x2, prec).mult(PI(prec).sqrt(prec), prec), prec).div(x, prec) + end # call-seq: # PI(numeric) -> BigDecimal diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 39dee611..b7545466 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -82,6 +82,8 @@ def test_consistent_precision_acceptance assert_consistent_precision_acceptance {|prec| BigMath.log10(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.log1p(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.expm1(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.erf(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.erfc(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.E(prec) } assert_consistent_precision_acceptance {|prec| BigMath.PI(prec) } @@ -112,6 +114,8 @@ def test_coerce_argument assert_equal(log10(bd, N), log10(f, N)) assert_equal(log1p(bd, N), log1p(f, N)) assert_equal(expm1(bd, N), expm1(f, N)) + assert_equal(erf(bd, N), erf(f, N)) + assert_equal(erfc(bd, N), erfc(f, N)) end def test_sqrt @@ -469,4 +473,58 @@ def test_expm1 assert_in_exact_precision(exp(BigDecimal("1.23e-10"), 120) - 1, expm1(BigDecimal("1.23e-10"), 100), 100) assert_in_exact_precision(exp(123, 120) - 1, expm1(BigDecimal("123"), 100), 100) end + + def test_erf + [-0.5, 0.1, 0.3, 2.1, 3.3].each do |x| + assert_in_epsilon(Math.erf(x), BigMath.erf(BigDecimal(x.to_s), N)) + end + assert_equal(1, BigMath.erf(PINF, N)) + assert_equal(-1, BigMath.erf(MINF, N)) + assert_equal(1, BigMath.erf(BigDecimal(1000), 100)) + assert_equal(-1, BigMath.erf(BigDecimal(-1000), 100)) + assert_not_equal(1, BigMath.erf(BigDecimal(10), 45)) + assert_not_equal(1, BigMath.erf(BigDecimal(15), 100)) + assert_equal(1, BigMath.erf(BigDecimal('1e400'), 10)) + assert_equal(-1, BigMath.erf(BigDecimal('-1e400'), 10)) + assert_equal(BigMath.erf(BigDecimal('1e-300'), N) * BigDecimal('1e-100'), BigMath.erf(BigDecimal('1e-400'), N)) + assert_equal( + BigDecimal("0.9953222650189527341620692563672529286108917970400600767383523262004372807199951773676290080196806805"), + BigMath.erf(BigDecimal("2"), 100) + ) + assert_converge_in_precision {|n| BigMath.erf(BigDecimal("1e-30"), n) } + assert_converge_in_precision {|n| BigMath.erf(BigDecimal("0.3"), n) } + assert_converge_in_precision {|n| BigMath.erf(SQRT2, n) } + end + + def test_erfc + [-0.5, 0.1, 0.3, 2.1, 3.3].each do |x| + assert_in_epsilon(Math.erfc(x), BigMath.erfc(BigDecimal(x.to_s), N)) + end + assert_equal(0, BigMath.erfc(PINF, N)) + assert_equal(2, BigMath.erfc(MINF, N)) + assert_equal(0, BigMath.erfc(BigDecimal('1e400'), 10)) + assert_equal(2, BigMath.erfc(BigDecimal('-1e400'), 10)) + assert_equal(1, BigMath.erfc(BigDecimal('1e-400'), N)) + + # erfc with taylor series + assert_equal( + BigDecimal("2.088487583762544757000786294957788611560818119321163727012213713938174695833440290610766384285723554e-45"), + BigMath.erfc(BigDecimal("10"), 100) + ) + assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(0.3), n) } + assert_converge_in_precision {|n| BigMath.erfc(SQRT2, n) } + assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(8), n) } + # erfc with asymptotic expansion + assert_equal( + BigDecimal("1.896961059966276509268278259713415434936907563929186183462834752900411805205111886605256690776760041e-697"), + BigMath.erfc(BigDecimal("40"), 100) + ) + assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(30), n) } + assert_converge_in_precision {|n| BigMath.erfc(30 * SQRT2, n) } + assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(50), n) } + assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(60000), n) } + # Near crossover point between taylor series and asymptotic expansion around prec=150 + assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(19.5), n) } + assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(20.5), n) } + end end diff --git a/test/bigdecimal/test_jruby.rb b/test/bigdecimal/test_jruby.rb index f7cf81a8..9a19af17 100644 --- a/test/bigdecimal/test_jruby.rb +++ b/test/bigdecimal/test_jruby.rb @@ -71,6 +71,8 @@ def test_bigmath assert_in_delta(Math.log10(3), BigMath.log10(BigDecimal(3), N)) assert_in_delta(Math.log1p(0.1), BigMath.log1p(BigDecimal('0.1'), N)) if defined? Math.log1p assert_in_delta(Math.expm1(0.1), BigMath.expm1(BigDecimal('0.1'), N)) if defined? Math.expm1 + assert_in_delta(Math.erf(1), BigMath.erf(BigDecimal(1), N)) + assert_in_delta(Math.erfc(10), BigMath.erfc(BigDecimal(10), N)) assert_in_delta(Math::PI, BigMath.PI(N)) assert_in_delta(Math::E, BigMath.E(N)) end From 2c186103069a759bc0748f07859fe05be3db042d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 10 Dec 2025 22:59:29 +0900 Subject: [PATCH 490/546] Implement BigMath.gamma and BigMath.lgamma (#451) Implement gamma and lgamma with Spouge's approximation Co-authored-by: Kenta Murata <3959+mrkn@users.noreply.github.com> --- lib/bigdecimal/math.rb | 121 +++++++++++++++++++++++++++++++- test/bigdecimal/test_bigmath.rb | 47 +++++++++++++ test/bigdecimal/test_jruby.rb | 2 + 3 files changed, 169 insertions(+), 1 deletion(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index b59f1849..11a78b39 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -26,6 +26,8 @@ # expm1(x, prec) # erf (x, prec) # erfc(x, prec) +# gamma(x, prec) +# lgamma(x, prec) # PI (prec) # E (prec) == exp(1.0,prec) # @@ -570,7 +572,6 @@ def expm1(x, prec) exp_prec > 0 ? exp(x, exp_prec).sub(1, prec) : BigDecimal(-1) end - # call-seq: # erf(decimal, numeric) -> BigDecimal # # Computes the error function of +decimal+ to the specified number of digits of @@ -699,6 +700,124 @@ def erfc(x, prec) sum.div(exp(x2, prec).mult(PI(prec).sqrt(prec), prec), prec).div(x, prec) end + # call-seq: + # BigMath.gamma(decimal, numeric) -> BigDecimal + # + # Computes the gamma function of +decimal+ to the specified number of + # digits of precision, +numeric+. + # + # BigMath.gamma(BigDecimal('0.5'), 32).to_s + # #=> "0.17724538509055160272981674833411e1" + # + def gamma(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :gamma) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :gamma) + prec2 = prec + BigDecimal.double_fig + if x < 0.5 + raise Math::DomainError 'Numerical argument is out of domain - gamma' if x.frac.zero? + + # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) + pi = PI(prec2) + sin = _sinpix(x, pi, prec2) + return pi.div(gamma(1 - x, prec).mult(sin, prec2), prec) + elsif x.frac.zero? && x < 1000 * prec + return _gamma_positive_integer(x, prec2).mult(1, prec) + end + + a, sum = _gamma_spouge_sum_part(x, prec2) + (x + (a - 1)).power(x - 0.5, prec2).mult(BigMath.exp(1 - x, prec2), prec2).mult(sum, prec) + end + + # call-seq: + # BigMath.lgamma(decimal, numeric) -> [BigDecimal, Integer] + # + # Computes the natural logarithm of the absolute value of the gamma function + # of +decimal+ to the specified number of digits of precision, +numeric+ and its sign. + # + # BigMath.lgamma(BigDecimal('0.5'), 32) + # #=> [0.57236494292470008707171367567653e0, 1] + # + def lgamma(x, prec) + prec = BigDecimal::Internal.coerce_validate_prec(prec, :lgamma) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :lgamma) + prec2 = prec + BigDecimal.double_fig + if x < 0.5 + return [BigDecimal::INFINITY, 1] if x.frac.zero? + + # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) + pi = PI(prec2) + sin = _sinpix(x, pi, prec2) + log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec).first + BigMath.log(sin.abs, prec2), prec) + [log_gamma, sin > 0 ? 1 : -1] + elsif x.frac.zero? && x < 1000 * prec + log_gamma = BigMath.log(_gamma_positive_integer(x, prec2), prec) + [log_gamma, 1] + else + a, sum = _gamma_spouge_sum_part(x, prec2) + log_gamma = BigMath.log(sum, prec2).add((x - 0.5).mult(BigMath.log(x.add(a - 1, prec2), prec2), prec2) + 1 - x, prec) + [log_gamma, 1] + end + end + + # Returns sum part: sqrt(2*pi) and c[k]/(x+k) terms of Spouge's approximation + private_class_method def _gamma_spouge_sum_part(x, prec) # :nodoc: + x -= 1 + # Spouge's approximation + # x! = (x + a)**(x + 0.5) * exp(-x - a) * (sqrt(2 * pi) + (1..a - 1).sum{|k| c[k] / (x + k) } + epsilon) + # where c[k] = (-1)**k * (a - k)**(k - 0.5) * exp(a - k) / (k - 1)! + # and epsilon is bounded by a**(-0.5) * (2 * pi) ** (-a - 0.5) + + # Estimate required a for given precision + a = (prec / Math.log10(2 * Math::PI)).ceil + + # Calculate exponent of c[k] in low precision to estimate required precision + low_prec = 16 + log10f = Math.log(10) + x_low_prec = x.mult(1, low_prec) + loggamma_k = 0 + ck_exponents = (1..a-1).map do |k| + loggamma_k += Math.log10(k - 1) if k > 1 + -loggamma_k - k / log10f + (k - 0.5) * Math.log10(a - k) - BigMath.log10(x_low_prec.add(k, low_prec), low_prec) + end + + # Estimate exponent of sum by Stirling's approximation + approx_sum_exponent = x < 1 ? -Math.log10(a) / 2 : Math.log10(2 * Math::PI) / 2 + x_low_prec.add(0.5, low_prec) * Math.log10(x_low_prec / x_low_prec.add(a, low_prec)) + + # Determine required precision of c[k] + prec2 = [ck_exponents.max.ceil - approx_sum_exponent.floor, 0].max + prec + + einv = BigMath.exp(-1, prec2) + sum = (PI(prec) * 2).sqrt(prec).mult(BigMath.exp(-a, prec), prec) + y = BigDecimal(1) + (1..a - 1).each do |k| + # c[k] = (-1)**k * (a - k)**(k - 0.5) * exp(-k) / (k-1)! / (x + k) + y = y.div(1 - k, prec2) if k > 1 + y = y.mult(einv, prec2) + z = y.mult(BigDecimal((a - k) ** k), prec2).div(BigDecimal(a - k).sqrt(prec2).mult(x.add(k, prec2), prec2), prec2) + # sum += c[k] / (x + k) + sum = sum.add(z, prec2) + end + [a, sum] + end + + private_class_method def _gamma_positive_integer(x, prec) # :nodoc: + return x if x == 1 + numbers = (1..x - 1).map {|i| BigDecimal(i) } + while numbers.size > 1 + numbers = numbers.each_slice(2).map {|a, b| b ? a.mult(b, prec) : a } + end + numbers.first + end + + # Returns sin(pi * x), for gamma reflection formula calculation + private_class_method def _sinpix(x, pi, prec) # :nodoc: + x = x % 2 + sign = x > 1 ? -1 : 1 + x %= 1 + x = 1 - x if x > 0.5 # to avoid sin(pi*x) loss of precision for x close to 1 + sign * sin(x.mult(pi, prec), prec) + end + # call-seq: # PI(numeric) -> BigDecimal # diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index b7545466..e0acdaed 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -84,6 +84,8 @@ def test_consistent_precision_acceptance assert_consistent_precision_acceptance {|prec| BigMath.expm1(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.erf(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.erfc(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.gamma(x, prec) } + assert_consistent_precision_acceptance {|prec| BigMath.lgamma(x, prec) } assert_consistent_precision_acceptance {|prec| BigMath.E(prec) } assert_consistent_precision_acceptance {|prec| BigMath.PI(prec) } @@ -116,6 +118,8 @@ def test_coerce_argument assert_equal(expm1(bd, N), expm1(f, N)) assert_equal(erf(bd, N), erf(f, N)) assert_equal(erfc(bd, N), erfc(f, N)) + assert_equal(gamma(bd, N), gamma(f, N)) + assert_equal(lgamma(bd, N), lgamma(f, N)) end def test_sqrt @@ -527,4 +531,47 @@ def test_erfc assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(19.5), n) } assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(20.5), n) } end + + def test_gamma + [-1.8, -0.7, 0.6, 1.5, 2.4].each do |x| + assert_in_epsilon(Math.gamma(x), gamma(BigDecimal(x.to_s), N)) + end + [1, 2, 3, 10, 16].each do |x| + assert_equal(Math.gamma(x).round, gamma(BigDecimal(x), N)) + end + sqrt_pi = PI(120).sqrt(120) + assert_equal(sqrt_pi.mult(1, 100), gamma(BigDecimal("0.5"), 100)) + assert_equal((sqrt_pi * 4).div(3, 100), gamma(BigDecimal("-1.5"), 100)) + assert_equal( + BigDecimal('0.28242294079603478742934215780245355184774949260912e456569'), + BigMath.gamma(100000, 50) + ) + assert_converge_in_precision {|n| gamma(BigDecimal("0.3"), n) } + assert_converge_in_precision {|n| gamma(BigDecimal("-1.9" + "9" * 30), n) } + assert_converge_in_precision {|n| gamma(BigDecimal("1234.56789"), n) } + assert_converge_in_precision {|n| gamma(BigDecimal("-987.654321"), n) } + end + + def test_lgamma + [-2, -1, 0].each do |x| + l, sign = lgamma(BigDecimal(x), N) + assert(l.infinite?) + assert_equal(1, sign) + end + [-1.8, -0.7, 0.6, 1, 1.5, 2, 2.4, 3, 1e+300].each do |x| + l, sign = Math.lgamma(x) + bigl, bigsign = lgamma(BigDecimal(x.to_s), N) + assert_in_epsilon(l, bigl) + assert_equal(sign, bigsign) + end + assert_equal([BigMath.log(PI(120).sqrt(120), 100), 1], lgamma(BigDecimal("0.5"), 100)) + assert_converge_in_precision {|n| lgamma(BigDecimal("-1." + "9" * 30), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("-3." + "0" * 30 + "1"), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("10"), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("0.3"), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("-1.9" + "9" * 30), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("987.65421"), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("-1234.56789"), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("1e+400"), n).first } + end end diff --git a/test/bigdecimal/test_jruby.rb b/test/bigdecimal/test_jruby.rb index 9a19af17..4fb3c17e 100644 --- a/test/bigdecimal/test_jruby.rb +++ b/test/bigdecimal/test_jruby.rb @@ -73,6 +73,8 @@ def test_bigmath assert_in_delta(Math.expm1(0.1), BigMath.expm1(BigDecimal('0.1'), N)) if defined? Math.expm1 assert_in_delta(Math.erf(1), BigMath.erf(BigDecimal(1), N)) assert_in_delta(Math.erfc(10), BigMath.erfc(BigDecimal(10), N)) + assert_in_delta(Math.gamma(0.5), BigMath.gamma(BigDecimal('0.5'), N)) + assert_in_delta(Math.lgamma(0.5).first, BigMath.lgamma(BigDecimal('0.5'), N).first) assert_in_delta(Math::PI, BigMath.PI(N)) assert_in_delta(Math::E, BigMath.E(N)) end From e05f4bf4caed7fe7ce533d3b573fb5183d1b813c Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Wed, 10 Dec 2025 10:52:26 -0800 Subject: [PATCH 491/546] Fix typos + improve copy/paste in readme - Fix a few typos - Make it easier to copy/paste from the readme by using code blocks Signed-off-by: Tim Smith --- CHANGES.md | 4 ++-- README.md | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f2472d16..316c2e8f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -80,7 +80,7 @@ * Fix memory leak in VpAlloc [GH-294] [GH-290] - Reoprted by **@MaxLap** + Reported by **@MaxLap** ## 3.1.7 @@ -211,7 +211,7 @@ **Kenta Murata** -* FIx the defaullt precision of `Float#to_d`. +* Fix the default precision of `Float#to_d`. [Bug #13331] **Kenta Murata** diff --git a/README.md b/README.md index e9cbf7b1..de231689 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # BigDecimal -![CI](https://github.com/ruby/bigdecimal/workflows/CI/badge.svg?branch=master&event=push) +[![CI](https://github.com/ruby/bigdecimal/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/ruby/bigdecimal/actions/workflows/ci.yml) BigDecimal provides an arbitrary-precision decimal floating-point number class. @@ -14,11 +14,15 @@ gem 'bigdecimal' And then execute: - $ bundle +```bash +bundle +``` Or install it yourself as: - $ gem install bigdecimal +```bash +gem install bigdecimal +``` ### For RubyInstaller users From 5c6854bbe82f555c50ceebca3f95f7eaff926971 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 12 Dec 2025 03:18:36 +0900 Subject: [PATCH 492/546] Fix inaccurate calculation (last digit) and add a workaround for add/sub hang bug (#465) --- lib/bigdecimal/math.rb | 31 +++++++++++++++++++++++-------- test/bigdecimal/helper.rb | 2 +- test/bigdecimal/test_bigmath.rb | 3 +++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 11a78b39..7bf0fca1 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -563,13 +563,23 @@ def expm1(x, prec) if x < -1 # log10(exp(x)) = x * log10(e) lg_e = 0.4342944819032518 - exp_prec = prec + (lg_e * x).ceil + 2 + exp_prec = prec + (lg_e * x).ceil + BigDecimal.double_fig elsif x < 1 - exp_prec = prec - x.exponent + 2 + exp_prec = prec - x.exponent + BigDecimal.double_fig else exp_prec = prec end - exp_prec > 0 ? exp(x, exp_prec).sub(1, prec) : BigDecimal(-1) + + return BigDecimal(-1) if exp_prec <= 0 + + exp = exp(x, exp_prec) + + if exp.exponent > prec + BigDecimal.double_fig + # Workaroudn for https://github.com/ruby/bigdecimal/issues/464 + exp + else + exp.sub(1, prec) + end end # erf(decimal, numeric) -> BigDecimal @@ -596,7 +606,12 @@ def erf(x, prec) log10_erfc = -xf ** 2 / Math.log(10) - Math.log10(xf * Math::PI ** 0.5) erfc_prec = [prec + log10_erfc.ceil, 1].max erfc = _erfc_asymptotic(x, erfc_prec) - return BigDecimal(1).sub(erfc, prec) if erfc + if erfc + # Workaround for https://github.com/ruby/bigdecimal/issues/464 + return BigDecimal(1) if erfc.exponent < -prec - BigDecimal.double_fig + + return BigDecimal(1).sub(erfc, prec) + end end prec2 = prec + BigDecimal.double_fig @@ -623,7 +638,7 @@ def erfc(x, prec) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erfc) return BigDecimal::Internal.nan_computation_result if x.nan? return BigDecimal(1 - x.infinite?) if x.infinite? - return BigDecimal(1).sub(erf(x, prec), prec) if x < 0 + return BigDecimal(1).sub(erf(x, prec + BigDecimal.double_fig), prec) if x < 0 return BigDecimal(0) if x > 5000000000 # erfc(5000000000) < 1e-10000000000000000000 (underflow) if x >= 8 @@ -714,12 +729,12 @@ def gamma(x, prec) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :gamma) prec2 = prec + BigDecimal.double_fig if x < 0.5 - raise Math::DomainError 'Numerical argument is out of domain - gamma' if x.frac.zero? + raise Math::DomainError, 'Numerical argument is out of domain - gamma' if x.frac.zero? # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) pi = PI(prec2) sin = _sinpix(x, pi, prec2) - return pi.div(gamma(1 - x, prec).mult(sin, prec2), prec) + return pi.div(gamma(1 - x, prec2).mult(sin, prec2), prec) elsif x.frac.zero? && x < 1000 * prec return _gamma_positive_integer(x, prec2).mult(1, prec) end @@ -747,7 +762,7 @@ def lgamma(x, prec) # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) pi = PI(prec2) sin = _sinpix(x, pi, prec2) - log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec).first + BigMath.log(sin.abs, prec2), prec) + log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec2).first + BigMath.log(sin.abs, prec2), prec) [log_gamma, sin > 0 ? 1 : -1] elsif x.frac.zero? && x < 1000 * prec log_gamma = BigMath.log(_gamma_positive_integer(x, prec2), prec) diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index baad5bb9..37089bd8 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -53,7 +53,7 @@ def assert_converge_in_precision(&block) [50, 100, 150].each do |n| value = yield(n) assert(value != expected, "Unable to estimate precision for exact value") - assert_in_exact_precision(expected, value, n) + assert_equal(expected.mult(1, n), value) end end diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index e0acdaed..40020763 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -476,6 +476,7 @@ def test_expm1 assert_in_exact_precision(exp(-3, 100) - 1, expm1(BigDecimal("-3"), 100), 100) assert_in_exact_precision(exp(BigDecimal("1.23e-10"), 120) - 1, expm1(BigDecimal("1.23e-10"), 100), 100) assert_in_exact_precision(exp(123, 120) - 1, expm1(BigDecimal("123"), 100), 100) + assert_equal(exp(BigDecimal("1e+12"), N), expm1(BigDecimal("1e+12"), N)) end def test_erf @@ -486,6 +487,7 @@ def test_erf assert_equal(-1, BigMath.erf(MINF, N)) assert_equal(1, BigMath.erf(BigDecimal(1000), 100)) assert_equal(-1, BigMath.erf(BigDecimal(-1000), 100)) + assert_equal(1, BigMath.erf(BigDecimal(10000000), 100)) assert_not_equal(1, BigMath.erf(BigDecimal(10), 45)) assert_not_equal(1, BigMath.erf(BigDecimal(15), 100)) assert_equal(1, BigMath.erf(BigDecimal('1e400'), 10)) @@ -524,6 +526,7 @@ def test_erfc BigMath.erfc(BigDecimal("40"), 100) ) assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(30), n) } + assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(-2), n) } assert_converge_in_precision {|n| BigMath.erfc(30 * SQRT2, n) } assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(50), n) } assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(60000), n) } From bfb1cb36ece1c2d4ccf44191871922caf26ad907 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 12 Dec 2025 03:46:16 +0900 Subject: [PATCH 493/546] Fix lgamma precision around 1 and 2 (#466) Increase internal calculation digits for lgamma when x is near 1 or 2. Perform linear interpolation if x is extreamely close to 1 or 2. --- lib/bigdecimal/math.rb | 18 ++++++++++++++++++ test/bigdecimal/test_bigmath.rb | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 7bf0fca1..95c5b3b6 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -768,6 +768,24 @@ def lgamma(x, prec) log_gamma = BigMath.log(_gamma_positive_integer(x, prec2), prec) [log_gamma, 1] else + # if x is close to 1 or 2, increase precision to reduce loss of significance + diff1_exponent = (x - 1).exponent + diff2_exponent = (x - 2).exponent + extra_prec = [-diff1_exponent, -diff2_exponent, 0].max + extremely_near_one = diff1_exponent < -prec2 + extremely_near_two = diff2_exponent < -prec2 + + if extremely_near_one || extremely_near_two + # If x is extreamely close to base = 1 or 2, linear interpolation is accurate enough. + # Taylor expansion at x = base is: (x - base) * digamma(base) + (x - base) ** 2 * trigamma(base) / 2 + ... + # And we can ignore (x - base) ** 2 and higher order terms. + base = extremely_near_one ? 1 : 2 + d = BigDecimal(1)._decimal_shift(1 - prec2) + log_gamma_d, sign = lgamma(base + d, prec2) + return [log_gamma_d.mult(x - base, prec2).div(d, prec), sign] + end + + prec2 += [-diff1_exponent, -diff2_exponent, 0].max a, sum = _gamma_spouge_sum_part(x, prec2) log_gamma = BigMath.log(sum, prec2).add((x - 0.5).mult(BigMath.log(x.add(a - 1, prec2), prec2), prec2) + 1 - x, prec) [log_gamma, 1] diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 40020763..74a45d2c 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -568,6 +568,10 @@ def test_lgamma assert_equal(sign, bigsign) end assert_equal([BigMath.log(PI(120).sqrt(120), 100), 1], lgamma(BigDecimal("0.5"), 100)) + assert_converge_in_precision {|n| lgamma(BigDecimal("0." + "9" * 80), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("1." + "0" * 80 + "1"), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("1." + "9" * 80), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal("2." + "0" * 80 + "1"), n).first } assert_converge_in_precision {|n| lgamma(BigDecimal("-1." + "9" * 30), n).first } assert_converge_in_precision {|n| lgamma(BigDecimal("-3." + "0" * 30 + "1"), n).first } assert_converge_in_precision {|n| lgamma(BigDecimal("10"), n).first } From 80d66c96bacbaafb6d8e08538803c3e48dabe596 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 13 Dec 2025 02:34:08 +0900 Subject: [PATCH 494/546] Fix lgamma precision when gamma(negative_x).abs nearly equals 1 (#467) There are many non-integer negative x that gamma(x) intersects with +1 or -1. Retry calculation with increased precision if the calculate value is not precise enough (by loss of significance error). --- lib/bigdecimal/math.rb | 15 ++++++++++----- test/bigdecimal/test_bigmath.rb | 4 ++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 95c5b3b6..63739bec 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -759,11 +759,16 @@ def lgamma(x, prec) if x < 0.5 return [BigDecimal::INFINITY, 1] if x.frac.zero? - # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) - pi = PI(prec2) - sin = _sinpix(x, pi, prec2) - log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec2).first + BigMath.log(sin.abs, prec2), prec) - [log_gamma, sin > 0 ? 1 : -1] + loop do + # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) + pi = PI(prec2) + sin = _sinpix(x, pi, prec2) + log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec2).first + BigMath.log(sin.abs, prec2), prec) + return [log_gamma, sin > 0 ? 1 : -1] if prec2 + log_gamma.exponent > prec + BigDecimal.double_fig + + # Retry with higher precision if loss of significance is too large + prec2 = prec2 * 3 / 2 + end elsif x.frac.zero? && x < 1000 * prec log_gamma = BigMath.log(_gamma_positive_integer(x, prec2), prec) [log_gamma, 1] diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 74a45d2c..42ddd124 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -580,5 +580,9 @@ def test_lgamma assert_converge_in_precision {|n| lgamma(BigDecimal("987.65421"), n).first } assert_converge_in_precision {|n| lgamma(BigDecimal("-1234.56789"), n).first } assert_converge_in_precision {|n| lgamma(BigDecimal("1e+400"), n).first } + + # gamma close 1 or -1 cases + assert_converge_in_precision {|n| lgamma(BigDecimal('-3.143580888349980058694358781820227899566'), n).first } + assert_converge_in_precision {|n| lgamma(BigDecimal('-4.991544640560047722345260122806465721667'), n).first } end end From 30c6755d201ab9600559192b238622e639755ec0 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 14 Dec 2025 18:40:57 +0900 Subject: [PATCH 495/546] Implement BigMath.frexp and ldexp with exponent of 10 (#448) * Implement BigMath.frexp and ldexp with exponent of 10 Math.frexp and ldexp calculates with exponent of 2, BigDecimal version of frexp and ldexp calculates with exponent of 10 * Apply suggestion from @mrkn Co-authored-by: Kenta Murata <3959+mrkn@users.noreply.github.com> --------- Co-authored-by: Kenta Murata <3959+mrkn@users.noreply.github.com> --- lib/bigdecimal/math.rb | 32 ++++++++++++++++++++++++++++++++ test/bigdecimal/test_bigmath.rb | 24 ++++++++++++++++++++++++ test/bigdecimal/test_jruby.rb | 2 ++ 3 files changed, 58 insertions(+) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 63739bec..bf47123c 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -28,6 +28,8 @@ # erfc(x, prec) # gamma(x, prec) # lgamma(x, prec) +# frexp(x) +# ldexp(x, exponent) # PI (prec) # E (prec) == exp(1.0,prec) # @@ -856,6 +858,36 @@ def lgamma(x, prec) sign * sin(x.mult(pi, prec), prec) end + # call-seq: + # frexp(x) -> [BigDecimal, Integer] + # + # Decomposes +x+ into a normalized fraction and an integral power of ten. + # + # BigMath.frexp(BigDecimal(123.456)) + # #=> [0.123456e0, 3] + # + def frexp(x) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, 0, :frexp) + return [x, 0] unless x.finite? + + exponent = x.exponent + [x._decimal_shift(-exponent), exponent] + end + + # call-seq: + # ldexp(fraction, exponent) -> BigDecimal + # + # Inverse of +frexp+. + # Returns the value of fraction * 10**exponent. + # + # BigMath.ldexp(BigDecimal("0.123456e0"), 3) + # #=> 0.123456e3 + # + def ldexp(x, exponent) + x = BigDecimal::Internal.coerce_to_bigdecimal(x, 0, :ldexp) + x.finite? ? x._decimal_shift(exponent) : x + end + # call-seq: # PI(numeric) -> BigDecimal # diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 42ddd124..7b7ee113 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -585,4 +585,28 @@ def test_lgamma assert_converge_in_precision {|n| lgamma(BigDecimal('-3.143580888349980058694358781820227899566'), n).first } assert_converge_in_precision {|n| lgamma(BigDecimal('-4.991544640560047722345260122806465721667'), n).first } end + + def test_frexp + BigDecimal.save_limit do + BigDecimal.limit(3) + assert_equal([BigDecimal("-0.123456"), 10], BigMath.frexp(BigDecimal("-0.123456e10"))) + assert_equal([BigDecimal("0.123456"), -10], BigMath.frexp(BigDecimal("0.123456e-10"))) + assert_equal([BigDecimal("0.123456789"), 9], BigMath.frexp(123456789)) + assert_equal([BigDecimal(0), 0], BigMath.frexp(BigDecimal(0))) + assert_equal([BigDecimal::NAN, 0], BigMath.frexp(BigDecimal::NAN)) + assert_equal([BigDecimal::INFINITY, 0], BigMath.frexp(BigDecimal::INFINITY)) + end + end + + def test_ldexp + BigDecimal.save_limit do + BigDecimal.limit(3) + assert_equal(BigDecimal("-0.123456e10"), BigMath.ldexp(BigDecimal("-0.123456"), 10)) + assert_equal(BigDecimal("0.123456e20"), BigMath.ldexp(BigDecimal("0.123456e10"), 10.9)) + assert_equal(BigDecimal("0.123456e-10"), BigMath.ldexp(BigDecimal("0.123456"), -10)) + assert_equal(BigDecimal("0.123456789e19"), BigMath.ldexp(123456789, 10)) + assert(BigMath.ldexp(BigDecimal::NAN, 10).nan?) + assert_equal(BigDecimal::INFINITY, BigMath.ldexp(BigDecimal::INFINITY, 10)) + end + end end diff --git a/test/bigdecimal/test_jruby.rb b/test/bigdecimal/test_jruby.rb index 4fb3c17e..e02cbf08 100644 --- a/test/bigdecimal/test_jruby.rb +++ b/test/bigdecimal/test_jruby.rb @@ -75,6 +75,8 @@ def test_bigmath assert_in_delta(Math.erfc(10), BigMath.erfc(BigDecimal(10), N)) assert_in_delta(Math.gamma(0.5), BigMath.gamma(BigDecimal('0.5'), N)) assert_in_delta(Math.lgamma(0.5).first, BigMath.lgamma(BigDecimal('0.5'), N).first) + assert_equal([BigDecimal('0.123'), 4], BigMath.frexp(BigDecimal('0.123e4'))) + assert_equal(BigDecimal('12.3e4'), BigMath.ldexp(BigDecimal('12.3'), 4)) assert_in_delta(Math::PI, BigMath.PI(N)) assert_in_delta(Math::E, BigMath.E(N)) end From 5313fedcb827c7f7160c630308103ee4610f8a16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:03:35 +0000 Subject: [PATCH 496/546] Bump step-security/harden-runner from 2.13.3 to 2.14.0 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.3 to 2.14.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/df199fb7be9f65074067a9eb93f12bb4c5547cf2...20cf305ff2072d973412fa9b1e3a4f227bda3c76) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 6edb8163..33b84345 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit From b7e93bf366807d1f3a83a7167c68b8c4faf08bbd Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 16 Dec 2025 02:08:48 +0900 Subject: [PATCH 497/546] Better rounding of BigMath.atan(nearly_one, prec) (#469) --- lib/bigdecimal/math.rb | 2 +- test/bigdecimal/test_bigmath.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index bf47123c..b3052512 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -286,7 +286,7 @@ def atan(x, prec) pi = PI(n) x = -x if neg = x < 0 return pi.div(neg ? -2 : 2, prec) if x.infinite? - return pi.div(neg ? -4 : 4, prec) if x.round(prec) == 1 + return pi.div(neg ? -4 : 4, prec) if x.round(n) == 1 x = BigDecimal("1").div(x, n) if inv = x > 1 x = (-1 + sqrt(1 + x.mult(x, n), n)).div(x, n) if dbl = x > 0.5 y = x diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 7b7ee113..8b85aa6d 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -278,6 +278,7 @@ def test_atan assert_converge_in_precision {|n| atan(BigDecimal("2"), n)} assert_converge_in_precision {|n| atan(BigDecimal("1e-30"), n)} assert_converge_in_precision {|n| atan(BigDecimal("1e30"), n)} + assert_equal(BigDecimal(0.78), BigMath.atan(0.996, 2)) end def test_atan2 From 81463364fc89be2978dc0d073ebbf5cafec36cbc Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 16 Dec 2025 22:10:01 +0900 Subject: [PATCH 498/546] Remove deprecated method BigDecimal#precs (#470) `BigDecimal#precs` was deprecated in v3.0.0. Next release (v4.0.0) is a good timing for removing it. --- ext/bigdecimal/bigdecimal.c | 32 ------------------------------ test/bigdecimal/test_bigdecimal.rb | 20 ------------------- 2 files changed, 52 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f2e7deeb..6bc0e905 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -400,37 +400,6 @@ BigDecimal_double_fig(VALUE self) return INT2FIX(BIGDECIMAL_DOUBLE_FIGURES); } -/* call-seq: - * precs -> array - * - * Returns an Array of two Integer values that represent platform-dependent - * internal storage properties. - * - * This method is deprecated and will be removed in the future. - * Instead, use BigDecimal#n_significant_digits for obtaining the number of - * significant digits in scientific notation, and BigDecimal#precision for - * obtaining the number of digits in decimal notation. - * - */ - -static VALUE -BigDecimal_prec(VALUE self) -{ - BDVALUE v; - VALUE obj; - - rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, - "BigDecimal#precs is deprecated and will be removed in the future; " - "use BigDecimal#precision instead."); - - v = GetBDValueMust(self); - obj = rb_assoc_new(SIZET2NUM(v.real->Prec*VpBaseFig()), - SIZET2NUM(v.real->MaxPrec*VpBaseFig())); - - RB_GC_GUARD(v.bigdecimal); - return obj; -} - static void VpCountPrecisionAndScale(Real *p, ssize_t *out_precision, ssize_t *out_scale) { @@ -3593,7 +3562,6 @@ Init_bigdecimal(void) rb_define_const(rb_cBigDecimal, "NAN", BIGDECIMAL_LITERAL(NAN, NaN)); /* instance methods */ - rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); rb_define_method(rb_cBigDecimal, "precision", BigDecimal_precision, 0); rb_define_method(rb_cBigDecimal, "scale", BigDecimal_scale, 0); rb_define_method(rb_cBigDecimal, "precision_scale", BigDecimal_precision_scale, 0); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index d3ae0002..676bc530 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -778,24 +778,6 @@ def test_cmp_data assert_operator(BigDecimal((2**100).to_s), :==, d) end - def test_precs_deprecated - assert_warn(/BigDecimal#precs is deprecated and will be removed in the future/) do - Warning[:deprecated] = true if defined?(Warning.[]) - BigDecimal("1").precs - end - end - - def test_precs - $VERBOSE, verbose = nil, $VERBOSE - a = BigDecimal("1").precs - assert_instance_of(Array, a) - assert_equal(2, a.size) - assert_kind_of(Integer, a[0]) - assert_kind_of(Integer, a[1]) - ensure - $VERBOSE = verbose - end - def test_hash a = [] b = BigDecimal("1") @@ -832,11 +814,9 @@ def test_load_invalid_precision too_few_precs = BigDecimal._load('100:' + digits_part) assert_equal(1000, too_few_precs.precision) assert_equal(n, too_few_precs) - assert_equal(n.precs, too_few_precs.precs) too_large_precs = BigDecimal._load('999999999999:' + digits_part) assert_equal(1000, too_large_precs.precision) assert_equal(n, too_large_precs) - assert_equal(n.precs, too_large_precs.precs) ensure $VERBOSE = verbose end From 45d203a9106c1fb33f349200083180a3660ea439 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 16 Dec 2025 22:12:25 +0900 Subject: [PATCH 499/546] Deprecate ludcmp, jacobian and newton (#471) It's not tested at all for a long time. Jacobian and Newton is independent on BigDecimal, suggesting that it doesn't need to belong to bigdecimal. Functionalty of Ludcmp duplicates with gem 'matrix'. --- lib/bigdecimal/jacobian.rb | 2 ++ lib/bigdecimal/ludcmp.rb | 2 ++ lib/bigdecimal/newton.rb | 2 ++ 3 files changed, 6 insertions(+) diff --git a/lib/bigdecimal/jacobian.rb b/lib/bigdecimal/jacobian.rb index 4448024c..9a5e5be7 100644 --- a/lib/bigdecimal/jacobian.rb +++ b/lib/bigdecimal/jacobian.rb @@ -2,6 +2,8 @@ require 'bigdecimal' +warn "'bigdecimal/jacobian' is deprecated and will be removed in a future release." + # require 'bigdecimal/jacobian' # # Provides methods to compute the Jacobian matrix of a set of equations at a diff --git a/lib/bigdecimal/ludcmp.rb b/lib/bigdecimal/ludcmp.rb index dd265e48..d112fc1f 100644 --- a/lib/bigdecimal/ludcmp.rb +++ b/lib/bigdecimal/ludcmp.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require 'bigdecimal' +warn "'bigdecimal/ludcmp' is deprecated and will be removed in a future release." + # # Solves a*x = b for x, using LU decomposition. # diff --git a/lib/bigdecimal/newton.rb b/lib/bigdecimal/newton.rb index 85bacb7f..bb70d476 100644 --- a/lib/bigdecimal/newton.rb +++ b/lib/bigdecimal/newton.rb @@ -2,6 +2,8 @@ require "bigdecimal/ludcmp" require "bigdecimal/jacobian" +warn "'bigdecimal/newton' is deprecated and will be removed in a future release." + # # newton.rb # From d9914c90af8217651a4eabc1ac8a8c7ac3fb95e5 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 16 Dec 2025 22:43:25 +0900 Subject: [PATCH 500/546] Bump version to v4.0.0 (#472) --- CHANGES.md | 22 ++++++++++++++++++++++ ext/bigdecimal/bigdecimal.c | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 316c2e8f..6058cce9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,27 @@ # CHANGES +## 4.0.0 + +* `BigDecimal#divmod` return value changed to `[Integer, BigDecimal]` [GH-312] + + **@mrzasa** + +* Remove `BigDecimal#precs` [GH-470] + + **@tompng** + +* BigMath now supports all functions defined in Math module [GH-336] [GH-357] [GH-451] [GH-448] + + **@tompng** + +* Fix incorrect exception when exponent is fractional for Infinity base [GH-453] + + **@troy-dunamu** + +* Deprecate `bigdecimal/jacobian`, `bigdecimal/ludcmp` and `bigdecimal/newton` [GH-471] + + **@tompng** + ## 3.3.1 * All BigMath methods converts non integer precision with to_int diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6bc0e905..c0581fd5 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "3.3.1" +#define BIGDECIMAL_VERSION "4.0.0" /* #define ENABLE_NUMERIC_STRING */ From d93ef2bec855c830e912cd93017c0dd9553c9e5c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 11:01:36 +0900 Subject: [PATCH 501/546] Exclude dependabot updates from release note --- .github/release.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..db1d8e96 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,4 @@ +changelog: + exclude: + labels: + - dependencies # Added by Dependabot From 41203257dedf6b6b766d618db646e709ecfd0f89 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 18 Dec 2025 01:15:50 +0900 Subject: [PATCH 502/546] Remove unused variable (and add test for it) (#475) * Remove unused variable (and add test for it) * Fix shadowing outer local variable warning on ruby-2.5 --- lib/bigdecimal/math.rb | 7 +++---- test/bigdecimal/test_bigmath.rb | 7 +++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index b3052512..a57ad115 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -678,9 +678,9 @@ def erfc(x, prec) sum = c_prev.add(c_next, prec) 2.step do |k| - c = (c_prev.mult(x, prec) + a * c_next).mult(2, prec).mult(x, prec).div(k, prec) - sum = sum.add(c, prec) - c_prev, c_next = c_next, c + cn = (c_prev.mult(x, prec) + a * c_next).mult(2, prec).mult(x, prec).div(k, prec) + sum = sum.add(cn, prec) + c_prev, c_next = c_next, cn break if [c_prev, c_next].all? { |c| c.zero? || (c.exponent < sum.exponent - prec) } end value = sum.mult(scale.mult(exp(-(x + a).mult(x + a, prec), prec), prec), prec) @@ -778,7 +778,6 @@ def lgamma(x, prec) # if x is close to 1 or 2, increase precision to reduce loss of significance diff1_exponent = (x - 1).exponent diff2_exponent = (x - 2).exponent - extra_prec = [-diff1_exponent, -diff2_exponent, 0].max extremely_near_one = diff1_exponent < -prec2 extremely_near_two = diff2_exponent < -prec2 diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 8b85aa6d..903fd7f9 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -14,6 +14,13 @@ class TestBigMath < Test::Unit::TestCase MINF = BigDecimal("-Infinity") NAN = BigDecimal("NaN") + def test_no_warning + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + require 'bigdecimal/math' + end; + end + def test_pi assert_equal( BigDecimal("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068"), From 4914cc38d22f21f70d96684664997d9de949ea5f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 18 Dec 2025 01:21:58 +0900 Subject: [PATCH 503/546] Remove "Which version should you select" section (#476) This section is outdated. --- README.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/README.md b/README.md index de231689..d5a86817 100644 --- a/README.md +++ b/README.md @@ -32,24 +32,6 @@ If your Ruby comes from [RubyInstaller](https://rubyinstaller.org/), make sure [ I don't have enough knowledge about Chocolatey. Please tell me what should I write here. -## Which version should you select - -The differences among versions are given below: - -| version | characteristics | Supported ruby version range | -| ------- | --------------- | ----------------------- | -| 3.0.0 | You can use BigDecimal with Ractor on Ruby 3.0 | 2.5 .. | -| 2.0.x | You cannot use BigDecimal.new and do subclassing | 2.4 .. | -| 1.4.x | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.7 | -| 1.3.5 | You can use BigDecimal.new and subclassing without warning | .. 2.5 | - -You can select the version you want to use using `gem` method in Gemfile or scripts. -For example, you want to stick bigdecimal version 1.3.5, it works file to put the following `gem` call in you Gemfile. - -```ruby -gem 'bigdecimal', '1.3.5' -``` - ## Usage TODO: Write usage instructions here From 6d01c36419c7436d6d4fdf9603a032d7326ad6c8 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 18 Dec 2025 01:38:23 +0900 Subject: [PATCH 504/546] Bump version to v4.0.1 (#477) Fix unused variable warning included in v4.0.0 --- CHANGES.md | 6 ++++++ ext/bigdecimal/bigdecimal.c | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6058cce9..2dc17cdd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGES +## 4.0.1 + +* Fix warning [GH-475] + + **@tompng** + ## 4.0.0 * `BigDecimal#divmod` return value changed to `[Integer, BigDecimal]` [GH-312] diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c0581fd5..78449fbb 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -31,7 +31,7 @@ #include "bits.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "4.0.0" +#define BIGDECIMAL_VERSION "4.0.1" /* #define ENABLE_NUMERIC_STRING */ From d2c419687cb329be48e74f092370ed1d2214ee3e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 26 Dec 2025 03:21:23 +0900 Subject: [PATCH 505/546] Remove ENABLE_NUMERIC_STRING flag (#479) --- ext/bigdecimal/bigdecimal.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 78449fbb..2064d143 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -33,8 +33,6 @@ #define BIGDECIMAL_VERSION "4.0.1" -/* #define ENABLE_NUMERIC_STRING */ - #define SIGNED_VALUE_MAX INTPTR_MAX #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) @@ -336,14 +334,6 @@ GetBDValueWithPrecInternal(VALUE v, size_t prec, int must) break; } -#ifdef ENABLE_NUMERIC_STRING - case T_STRING: { - const char *c_str = StringValueCStr(v); - v = rb_cstr_convert_to_BigDecimal(c_str, must); - break; - } -#endif /* ENABLE_NUMERIC_STRING */ - default: goto SomeOneMayDoIt; } From 1350fa59e5d03a23ece87b48131aee48f2fd9297 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 28 Dec 2025 01:02:45 +0900 Subject: [PATCH 506/546] Sample code without deprecated modules (#480) * sample/pi Remove shebang and `include BigMath` * Rewrite sample/linear.rb using gem matrix instead of bigdecimal/ludcmp * Implement sample/nlsolve.rb without deprecated bigdecimal/newton --- sample/linear.rb | 108 +++++++++++++++++++++++++++++++--------------- sample/nlsolve.rb | 77 ++++++++++++++++++++------------- sample/pi.rb | 5 +-- 3 files changed, 122 insertions(+), 68 deletions(-) diff --git a/sample/linear.rb b/sample/linear.rb index 516c2473..6cc72e25 100644 --- a/sample/linear.rb +++ b/sample/linear.rb @@ -1,4 +1,3 @@ -#!/usr/local/bin/ruby # frozen_string_literal: false # @@ -13,62 +12,101 @@ # :stopdoc: require "bigdecimal" -require "bigdecimal/ludcmp" -# -# NOTE: -# Change following BigDecimal.limit() if needed. -BigDecimal.limit(100) -# +# Requires gem matrix +require "matrix" + +class PrecisionSpecifiedValue + # NOTE: + # Change following PREC if needed. + + attr_reader :value + def initialize(value, prec) + @value = BigDecimal(value) + @prec = prec + end + + def unwrap(value) + PrecisionSpecifiedValue === value ? value.value : value + end + + def coerce(other) + [self.class.new(unwrap(other), @prec), self] + end + + def abs + self.class.new(@value.abs, @prec) + end + + def >(other) + @value > unwrap(other) + end + + def <(other) + @value < unwrap(other) + end + + def -(other) + self.class.new(@value.sub(unwrap(other), @prec), @prec) + end + + def +(other) + self.class.new(@value.add(unwrap(other), @prec), @prec) + end + + def *(other) + self.class.new(@value.mult(unwrap(other), @prec), @prec) + end + + def quo(other) + self.class.new(@value.div(unwrap(other), @prec), @prec) + end +end + +return if __FILE__ != $0 -include LUSolve def rd_order(na) - printf("Number of equations ?") if(na <= 0) - n = ARGF.gets().to_i + printf("Number of equations ?") if(na <= 0) + ARGF.gets().to_i end -na = ARGV.size -zero = BigDecimal("0.0") -one = BigDecimal("1.0") +na = ARGV.size while (n=rd_order(na))>0 a = [] - as= [] b = [] if na <= 0 # Read data from console. printf("\nEnter coefficient matrix element A[i,j]\n") for i in 0...n do - for j in 0...n do + a << n.times.map do |j| printf("A[%d,%d]? ",i,j); s = ARGF.gets - a << BigDecimal(s) - as << BigDecimal(s) + BigDecimal(s) end printf("Contatant vector element b[%d] ? ",i) b << BigDecimal(ARGF.gets) end else - # Read data from specified file. - printf("Coefficient matrix and constant vector.\n") - for i in 0...n do - s = ARGF.gets - printf("%d) %s",i,s) - s = s.split - for j in 0...n do - a << BigDecimal(s[j]) - as << BigDecimal(s[j]) - end - b << BigDecimal(s[n]) - end + # Read data from specified file. + printf("Coefficient matrix and constant vector.\n") + for i in 0...n do + s = ARGF.gets + printf("%d) %s",i,s) + s = s.split + a << n.times.map {|j| BigDecimal(s[j]) } + b << BigDecimal(s[n]) + end end - x = lusolve(a,b,ludecomp(a,n,zero,one),zero) + + prec = 100 + matrix = Matrix[*a.map {|row| row.map {|v| PrecisionSpecifiedValue.new(v, prec) } }] + vector = b.map {|v| PrecisionSpecifiedValue.new(v, prec) } + x = matrix.lup.solve(vector).map(&:value) + printf("Answer(x[i] & (A*x-b)[i]) follows\n") for i in 0...n do printf("x[%d]=%s ",i,x[i].to_s) - s = zero - for j in 0...n do - s = s + as[i*n+j]*x[j] - end - printf(" & %s\n",(s-b[i]).to_s) + diff = a[i].zip(x).sum {|aij, xj| aij*xj }.sub(b[i], 10) + printf(" & %s\n", diff.to_s) end end diff --git a/sample/nlsolve.rb b/sample/nlsolve.rb index c2227dac..1e63578b 100644 --- a/sample/nlsolve.rb +++ b/sample/nlsolve.rb @@ -1,4 +1,3 @@ -#!/usr/local/bin/ruby # frozen_string_literal: false # @@ -7,34 +6,54 @@ # require "bigdecimal" -require "bigdecimal/newton" -include Newton - -class Function # :nodoc: all - def initialize() - @zero = BigDecimal("0.0") - @one = BigDecimal("1.0") - @two = BigDecimal("2.0") - @ten = BigDecimal("10.0") - @eps = BigDecimal("1.0e-16") - end - def zero;@zero;end - def one ;@one ;end - def two ;@two ;end - def ten ;@ten ;end - def eps ;@eps ;end - def values(x) # <= defines functions solved - f = [] - f1 = x[0]*x[0] + x[1]*x[1] - @two # f1 = x**2 + y**2 - 2 => 0 - f2 = x[0] - x[1] # f2 = x - y => 0 - f <<= f1 - f <<= f2 - f +require_relative "linear" + +# Requires gem matrix +require "matrix" + +# :stopdoc: + +def func((x, y)) # defines functions solved + [ + x**2 + y**2 - 2, + (x - 1)**2 + (y + 1)**2 - 3 + ] +end + +def jacobian(x, f, delta, prec) + dim = x.size + dim.times.map do |i| + xplus = Array.new(dim) {|j| x[i] + (j == i ? delta : 0) } + xminus = Array.new(dim) {|j| x[i] - (j == i ? delta : 0) } + yplus = f.call(xplus) + yminus = f.call(xminus) + yplus.zip(yminus).map {|p, m| (p - m).div(2 * delta, prec) } + end.transpose +end + +def nlsolve(initial_x, prec:, max_iteration: 100, &f) + initial_x = initial_x.map {|v| BigDecimal(v) } + x = initial_x + delta = BigDecimal(0.01) + calc_prec = prec + 10 + max_iteration.times do |iteration| + # Newton step + jacobian = jacobian(x, f, delta, calc_prec) + matrix = Matrix[*jacobian.map {|row| row.map {|v| PrecisionSpecifiedValue.new(v, calc_prec) } }] + y = f.call(x) + vector = y.map {|v| PrecisionSpecifiedValue.new(v, calc_prec) } + dx = matrix.lup.solve(vector).map(&:value) + x_prev = x + x = x.zip(dx).map {|xi, di| xi.sub(di, prec) } + movement = x_prev.zip(x).map {|xn, xi| (xn - xi).abs }.max + delta = [movement, delta].min.mult(1, 10) + break if movement.zero? || movement.exponent < -prec end + x end -f = BigDecimal.limit(100) -f = Function.new -x = [f.zero,f.zero] # Initial values -n = nlsolve(f,x) -p x +initial_value = [1, 1] +ans = nlsolve(initial_value, prec: 100) {|x| func(x) } +diff = func(ans).map {|v| v.mult(1, 10) } +p(ans:) +p(diff:) diff --git a/sample/pi.rb b/sample/pi.rb index ea966389..3315d333 100644 --- a/sample/pi.rb +++ b/sample/pi.rb @@ -1,4 +1,3 @@ -#!/usr/local/bin/ruby # frozen_string_literal: false # @@ -11,11 +10,9 @@ require "bigdecimal" require "bigdecimal/math.rb" -include BigMath - if ARGV.size == 1 print "PI("+ARGV[0]+"):\n" - p PI(ARGV[0].to_i) + p BigMath.PI(ARGV[0].to_i) else print "TRY: ruby pi.rb 1000 \n" end From 27f0242342d4fda0e63764f94dccf33544f03081 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 28 Dec 2025 22:35:27 +0900 Subject: [PATCH 507/546] Improve performance of add/sub when exponent of two bigdecimals have huge difference (#478) * Improve performance of add/sub when exponent of two bigdecimals have huge difference BigDecimal('1e+10000000000').add(1, 10) allocates GB of memory. BigDecimal('1e+10000000000').add(BigDecimal('0.1e+9999999990'), 10) will give the same rounding result with less memory allocation and computation time. * Remove workaround of add/sub performance bug --- ext/bigdecimal/bigdecimal.c | 27 +++++++++++++++--- lib/bigdecimal/math.rb | 16 ++--------- test/bigdecimal/test_bigdecimal.rb | 46 ++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 2064d143..6f4249a4 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -986,7 +986,7 @@ BigDecimal_mode(int argc, VALUE *argv, VALUE self) static size_t GetAddSubPrec(Real *a, Real *b) { - if (!VpIsDef(a) || !VpIsDef(b)) return (size_t)-1L; + if (VpIsZero(a) || VpIsZero(b)) return Max(a->Prec, b->Prec); ssize_t min_a = a->exponent - a->Prec; ssize_t min_b = b->exponent - b->Prec; return Max(a->exponent, b->exponent) - Min(min_a, min_b); @@ -1290,13 +1290,32 @@ BigDecimal_addsub_with_coerce(VALUE self, VALUE r, size_t prec, int operation) if (VpIsNaN(a.real)) return CheckGetValue(a); if (VpIsNaN(b.real)) return CheckGetValue(b); - mx = GetAddSubPrec(a.real, b.real); - if (mx == (size_t)-1L) { - /* a or b is inf */ + if (VpIsInf(a.real) || VpIsInf(b.real)) { c = NewZeroWrap(1, BASE_FIG); VpAddSub(c.real, a.real, b.real, operation); } else { + + // Optimization when exponent difference is large + // (1.234e+1000).add(5.678e-1000, 10) == (1.234e+1000).add(0.1e+990, 10) in every rounding mode + if (prec && !VpIsZero(a.real) && !VpIsZero(b.real)) { + size_t precRoom = roomof(prec, BASE_FIG); + if (a.real->exponent - (ssize_t)Max(a.real->Prec, precRoom) - 1 > b.real->exponent) { + BDVALUE b2 = NewZeroWrap(1, BASE_FIG); + VpSetOne(b2.real) + VpSetSign(b2.real, b.real->sign); + b2.real->exponent = a.real->exponent - (ssize_t)Max(a.real->Prec, precRoom) - 1; + b = b2; + } else if (b.real->exponent - (ssize_t)Max(b.real->Prec, precRoom) - 1 > a.real->exponent) { + BDVALUE a2 = NewZeroWrap(1, BASE_FIG); + VpSetOne(a2.real) + VpSetSign(a2.real, a.real->sign); + a2.real->exponent = b.real->exponent - (ssize_t)Max(b.real->Prec, precRoom) - 1; + a = a2; + } + } + + mx = GetAddSubPrec(a.real, b.real); c = NewZeroWrap(1, (mx + 1) * BASE_FIG); size_t pl = VpGetPrecLimit(); if (prec) VpSetPrecLimit(prec); diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index a57ad115..a8534aa1 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -574,14 +574,7 @@ def expm1(x, prec) return BigDecimal(-1) if exp_prec <= 0 - exp = exp(x, exp_prec) - - if exp.exponent > prec + BigDecimal.double_fig - # Workaroudn for https://github.com/ruby/bigdecimal/issues/464 - exp - else - exp.sub(1, prec) - end + exp(x, exp_prec).sub(1, prec) end # erf(decimal, numeric) -> BigDecimal @@ -608,12 +601,7 @@ def erf(x, prec) log10_erfc = -xf ** 2 / Math.log(10) - Math.log10(xf * Math::PI ** 0.5) erfc_prec = [prec + log10_erfc.ceil, 1].max erfc = _erfc_asymptotic(x, erfc_prec) - if erfc - # Workaround for https://github.com/ruby/bigdecimal/issues/464 - return BigDecimal(1) if erfc.exponent < -prec - BigDecimal.double_fig - - return BigDecimal(1).sub(erfc, prec) - end + return BigDecimal(1).sub(erfc, prec) if erfc end prec2 = prec + BigDecimal.double_fig diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 676bc530..aff7a2f2 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -523,6 +523,52 @@ def test_add_sub_underflow assert_negative_zero((-x) - (-y)) end + def test_add_sub_huge_with_zero + huge = BigDecimal("0.1e#{EXPONENT_MAX}") + assert_equal(huge, huge + 0) + assert_equal(huge, huge - 0) + assert_equal(huge, huge.add(BigDecimal(0), 10)) + assert_equal(huge, huge.sub(BigDecimal(0), 10)) + end + + def test_add_sub_huge_exponent_difference + huge = BigDecimal('1e+1000000000000') + zero = BigDecimal(0) + small = BigDecimal('1e-1000000000000') + assert_equal(huge, huge.add(small, 10)) + assert_equal(huge, huge.sub(small, 10)) + assert_equal(huge, small.add(huge, 10)) + assert_equal(-huge, small.sub(huge, 10)) + assert_equal(huge, zero.add(huge, 10)) + assert_equal(huge, huge.add(zero, 10)) + assert_equal(small, zero.add(small, 10)) + assert_equal(small, small.add(zero, 10)) + + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_DOWN) + assert_equal(huge, huge.add(small, 10)) + assert_equal(huge * BigDecimal('0.9999999999'), huge.sub(small, 10)) + assert_equal(huge, huge.add(zero, 10)) + assert_equal(huge, huge.sub(zero, 10)) + + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_UP) + assert_equal(huge * BigDecimal('1.000000001'), huge.add(small, 10)) + assert_equal(huge, huge.sub(small, 10)) + assert_equal(huge, huge.add(zero, 10)) + assert_equal(huge, huge.sub(zero, 10)) + + [BigDecimal::ROUND_UP, BigDecimal::ROUND_DOWN, BigDecimal::ROUND_HALF_UP].each do |mode| + BigDecimal.mode(BigDecimal::ROUND_MODE, mode) + xs = [BigDecimal(1), BigDecimal(2)] + ys = [BigDecimal('0.5e-89'), BigDecimal('0.5e-90'), BigDecimal('0.5e-91')] + xs.product(ys).each do |x, y| + assert_equal((x + y).mult(1, 91), x.add(y, 91)) + assert_equal((y + x).mult(1, 91), y.add(x, 91)) + assert_equal((x - y).mult(1, 91), x.sub(y, 91)) + assert_equal((y - x).mult(1, 91), y.sub(x, 91)) + end + end + end + def test_mult_div_overflow_underflow_sign BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) From 0240a436088338c8382396beef3d5e46c243df9e Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 28 Dec 2025 23:20:47 +0900 Subject: [PATCH 508/546] Change frozen_string_literal from false to true (#481) --- ext/bigdecimal/extconf.rb | 2 +- lib/bigdecimal/math.rb | 2 +- lib/bigdecimal/util.rb | 2 +- sample/linear.rb | 2 -- sample/nlsolve.rb | 2 -- sample/pi.rb | 2 -- test/bigdecimal/helper.rb | 2 +- test/bigdecimal/test_bigdecimal.rb | 4 ++-- test/bigdecimal/test_bigdecimal_util.rb | 2 +- test/bigdecimal/test_bigmath.rb | 2 +- test/bigdecimal/test_jruby.rb | 2 +- test/bigdecimal/test_vp_operation.rb | 2 +- 12 files changed, 10 insertions(+), 16 deletions(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 0fcc7c90..47f26381 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require 'mkmf' def have_builtin_func(name, check_expr, opt = "", &b) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index a8534aa1..9dff366d 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require 'bigdecimal' # diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index 7c5f32eb..7f2232db 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # #-- # bigdecimal/util extends various native classes to provide the #to_d method, diff --git a/sample/linear.rb b/sample/linear.rb index 6cc72e25..0cfac03e 100644 --- a/sample/linear.rb +++ b/sample/linear.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: false - # # linear.rb # diff --git a/sample/nlsolve.rb b/sample/nlsolve.rb index 1e63578b..79536533 100644 --- a/sample/nlsolve.rb +++ b/sample/nlsolve.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: false - # # nlsolve.rb # An example for solving nonlinear algebraic equation system. diff --git a/sample/pi.rb b/sample/pi.rb index 3315d333..0dc27c43 100644 --- a/sample/pi.rb +++ b/sample/pi.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: false - # # pi.rb # diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 37089bd8..f624edf8 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require "test/unit" require "bigdecimal" require 'rbconfig/sizeof' diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index aff7a2f2..03f7f2ae 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "helper" require 'bigdecimal/math' @@ -266,7 +266,7 @@ def test_BigDecimal_with_big_decimal def test_BigDecimal_with_tainted_string Thread.new { $SAFE = 1 - BigDecimal('1'.taint) + BigDecimal('1'.dup.taint) }.join ensure $SAFE = 0 diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 19790287..34c46bc9 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "helper" require 'bigdecimal/util' diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 903fd7f9..5a6f4ee0 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "helper" require "bigdecimal/math" diff --git a/test/bigdecimal/test_jruby.rb b/test/bigdecimal/test_jruby.rb index e02cbf08..5886b245 100644 --- a/test/bigdecimal/test_jruby.rb +++ b/test/bigdecimal/test_jruby.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'helper' require 'bigdecimal/math' diff --git a/test/bigdecimal/test_vp_operation.rb b/test/bigdecimal/test_vp_operation.rb index 075df0b6..b2f1d75a 100644 --- a/test/bigdecimal/test_vp_operation.rb +++ b/test/bigdecimal/test_vp_operation.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'helper' require 'bigdecimal' From b6cb62cb1066f18fd5e724bb4a8b9ef36c2dc9d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:26:29 +0000 Subject: [PATCH 509/546] Bump step-security/harden-runner from 2.14.0 to 2.14.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.14.0 to 2.14.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/20cf305ff2072d973412fa9b1e3a4f227bda3c76...e3f713f2d8f53843e71c69a996d56f51aa9adfb9) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.14.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 33b84345..9040419f 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1 with: egress-policy: audit From 1f74c55119529beebfcfa9345409916d51a061e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:26:37 +0000 Subject: [PATCH 510/546] Bump actions/checkout from 6.0.1 to 6.0.2 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v6.0.1...v6.0.2) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/jruby_test.yml | 2 +- .github/workflows/push_gem.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index a585adb8..5034c322 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -43,7 +43,7 @@ jobs: - { os: windows-latest , ruby: "3.2" } steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68816212..9535341d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: BIGDECIMAL_USE_VP_TEST_METHODS: true steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/jruby_test.yml b/.github/workflows/jruby_test.yml index 4b0be8b3..ffabbe6f 100644 --- a/.github/workflows/jruby_test.yml +++ b/.github/workflows/jruby_test.yml @@ -22,7 +22,7 @@ jobs: - jruby-head steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 33b84345..256b8bb3 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -31,7 +31,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.1 + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v5.0.1 - name: Set up Ruby uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 From 95437210856806cb31a23805bd9960c580d7e9ed Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 28 Jan 2026 14:50:00 +0900 Subject: [PATCH 511/546] NTT multiplication and Newton-Raphson division (#407) * Implement faster multiplication using Number Theoretic Transform Performs ntt with three primes (29<<27|1, 26<<27|1, 24<<27|1) * Implement Newton-Raphson division Improve performance of huge divisions --- bigdecimal.gemspec | 2 + ext/bigdecimal/bigdecimal.c | 86 ++++++++++-- ext/bigdecimal/bigdecimal.h | 26 ++++ ext/bigdecimal/div.h | 192 +++++++++++++++++++++++++++ ext/bigdecimal/ntt.h | 191 ++++++++++++++++++++++++++ test/bigdecimal/test_vp_operation.rb | 120 +++++++++++++---- 6 files changed, 578 insertions(+), 39 deletions(-) create mode 100644 ext/bigdecimal/div.h create mode 100644 ext/bigdecimal/ntt.h diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index b6ef8fd9..6b20ac08 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -43,9 +43,11 @@ Gem::Specification.new do |s| ext/bigdecimal/bigdecimal.c ext/bigdecimal/bigdecimal.h ext/bigdecimal/bits.h + ext/bigdecimal/div.h ext/bigdecimal/feature.h ext/bigdecimal/missing.c ext/bigdecimal/missing.h + ext/bigdecimal/ntt.h ext/bigdecimal/missing/dtoa.c ext/bigdecimal/static_assert.h ] diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6f4249a4..baf34fdf 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -29,10 +29,18 @@ #endif #include "bits.h" +#include "div.h" #include "static_assert.h" #define BIGDECIMAL_VERSION "4.0.1" +#if SIZEOF_DECDIG == 4 +#define USE_NTT_MULTIPLICATION 1 +#include "ntt.h" +#define NTT_MULTIPLICATION_THRESHOLD 100 +#define NEWTON_RAPHSON_DIVISION_THRESHOLD 200 +#endif + #define SIGNED_VALUE_MAX INTPTR_MAX #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) @@ -73,11 +81,6 @@ static struct { uint8_t mode; } rbd_rounding_modes[RBD_NUM_ROUNDING_MODES]; -typedef struct { - VALUE bigdecimal; - Real *real; -} BDVALUE; - typedef struct { VALUE bigdecimal_or_nil; Real *real_or_null; @@ -205,7 +208,6 @@ rbd_allocate_struct_zero(int sign, size_t const digits) static unsigned short VpGetException(void); static void VpSetException(unsigned short f); static void VpCheckException(Real *p, bool always); -static int AddExponent(Real *a, SIGNED_VALUE n); static VALUE CheckGetValue(BDVALUE v); static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); @@ -1071,9 +1073,6 @@ BigDecimal_check_num(Real *p) VpCheckException(p, true); } -static VALUE BigDecimal_fix(VALUE self); -static VALUE BigDecimal_split(VALUE self); - /* Returns the value as an Integer. * * If the BigDecimal is infinity or NaN, raises FloatDomainError. @@ -3235,19 +3234,39 @@ BigDecimal_literal(const char *str) #ifdef BIGDECIMAL_USE_VP_TEST_METHODS VALUE -BigDecimal_vpdivd(VALUE self, VALUE r, VALUE cprec) { - BDVALUE a,b,c,d; +BigDecimal_vpdivd_generic(VALUE self, VALUE r, VALUE cprec, void (*vpdivd_func)(Real*, Real*, Real*, Real*)) { + BDVALUE a, b, c, d; size_t cn = NUM2INT(cprec); a = GetBDValueMust(self); b = GetBDValueMust(r); c = NewZeroWrap(1, cn * BASE_FIG); d = NewZeroWrap(1, VPDIVD_REM_PREC(a.real, b.real, c.real) * BASE_FIG); - VpDivd(c.real, d.real, a.real, b.real); + vpdivd_func(c.real, d.real, a.real, b.real); RB_GC_GUARD(a.bigdecimal); RB_GC_GUARD(b.bigdecimal); return rb_assoc_new(c.bigdecimal, d.bigdecimal); } +void +VpDivdNormal(Real *c, Real *r, Real *a, Real *b) { + VpDivd(c, r, a, b); +} + +VALUE +BigDecimal_vpdivd(VALUE self, VALUE r, VALUE cprec) { + return BigDecimal_vpdivd_generic(self, r, cprec, VpDivdNormal); +} + +VALUE +BigDecimal_vpdivd_newton(VALUE self, VALUE r, VALUE cprec) { + return BigDecimal_vpdivd_generic(self, r, cprec, VpDivdNewton); +} + +VALUE +BigDecimal_newton_raphson_inverse(VALUE self, VALUE prec) { + return newton_raphson_inverse(self, NUM2SIZET(prec)); +} + VALUE BigDecimal_vpmult(VALUE self, VALUE v) { BDVALUE a,b,c; @@ -3259,6 +3278,25 @@ BigDecimal_vpmult(VALUE self, VALUE v) { RB_GC_GUARD(b.bigdecimal); return c.bigdecimal; } + +#if SIZEOF_DECDIG == 4 +VALUE +BigDecimal_nttmult(VALUE self, VALUE v) { + BDVALUE a,b,c; + a = GetBDValueMust(self); + b = GetBDValueMust(v); + c = NewZeroWrap(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG); + ntt_multiply(a.real->Prec, b.real->Prec, a.real->frac, b.real->frac, c.real->frac); + VpSetSign(c.real, a.real->sign * b.real->sign); + c.real->exponent = a.real->exponent + b.real->exponent; + c.real->Prec = a.real->Prec + b.real->Prec; + VpNmlz(c.real); + RB_GC_GUARD(a.bigdecimal); + RB_GC_GUARD(b.bigdecimal); + return c.bigdecimal; +} +#endif + #endif /* BIGDECIMAL_USE_VP_TEST_METHODS */ /* Document-class: BigDecimal @@ -3629,7 +3667,12 @@ Init_bigdecimal(void) #ifdef BIGDECIMAL_USE_VP_TEST_METHODS rb_define_method(rb_cBigDecimal, "vpdivd", BigDecimal_vpdivd, 2); + rb_define_method(rb_cBigDecimal, "vpdivd_newton", BigDecimal_vpdivd_newton, 2); + rb_define_method(rb_cBigDecimal, "newton_raphson_inverse", BigDecimal_newton_raphson_inverse, 1); rb_define_method(rb_cBigDecimal, "vpmult", BigDecimal_vpmult, 1); +#ifdef USE_NTT_MULTIPLICATION + rb_define_method(rb_cBigDecimal, "nttmult", BigDecimal_nttmult, 1); +#endif #endif /* BIGDECIMAL_USE_VP_TEST_METHODS */ #define ROUNDING_MODE(i, name, value) \ @@ -4912,6 +4955,15 @@ VpMult(Real *c, Real *a, Real *b) c->exponent = a->exponent; /* set exponent */ VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ if (!AddExponent(c, b->exponent)) return 0; + +#ifdef USE_NTT_MULTIPLICATION + if (b->Prec >= NTT_MULTIPLICATION_THRESHOLD) { + ntt_multiply((uint32_t)a->Prec, (uint32_t)b->Prec, a->frac, b->frac, c->frac); + c->Prec = a->Prec + b->Prec; + goto Cleanup; + } +#endif + carry = 0; nc = ind_c = MxIndAB; memset(c->frac, 0, (nc + 1) * sizeof(DECDIG)); /* Initialize c */ @@ -4958,6 +5010,8 @@ VpMult(Real *c, Real *a, Real *b) } } } + +Cleanup: VpNmlz(c); Exit: @@ -5005,6 +5059,14 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) if (word_a > word_r || word_b + word_c - 2 >= word_r) goto space_error; +#ifdef USE_NTT_MULTIPLICATION + // Newton-Raphson division requires multiplication to be faster than O(n^2) + if (word_c >= NEWTON_RAPHSON_DIVISION_THRESHOLD && word_b >= NEWTON_RAPHSON_DIVISION_THRESHOLD) { + VpDivdNewton(c, r, a, b); + goto Exit; + } +#endif + for (i = 0; i < word_a; ++i) r->frac[i] = a->frac[i]; for (i = word_a; i < word_r; ++i) r->frac[i] = 0; for (i = 0; i < word_c; ++i) c->frac[i] = 0; diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 82c88a2a..71ddb21f 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -188,6 +188,11 @@ typedef struct { DECDIG frac[FLEXIBLE_ARRAY_SIZE]; /* Array of fraction part. */ } Real; +typedef struct { + VALUE bigdecimal; + Real *real; +} BDVALUE; + /* * ------------------ * EXPORTables. @@ -232,10 +237,31 @@ VP_EXPORT int VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t il); VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf); VP_EXPORT int VpLeftRound(Real *y, unsigned short f, ssize_t nf); VP_EXPORT void VpFrac(Real *y, Real *x); +VP_EXPORT int AddExponent(Real *a, SIGNED_VALUE n); /* VP constants */ VP_EXPORT Real *VpOne(void); +/* + * **** BigDecimal part **** + */ +VP_EXPORT VALUE BigDecimal_lt(VALUE self, VALUE r); +VP_EXPORT VALUE BigDecimal_ge(VALUE self, VALUE r); +VP_EXPORT VALUE BigDecimal_exponent(VALUE self); +VP_EXPORT VALUE BigDecimal_fix(VALUE self); +VP_EXPORT VALUE BigDecimal_frac(VALUE self); +VP_EXPORT VALUE BigDecimal_add(VALUE self, VALUE b); +VP_EXPORT VALUE BigDecimal_sub(VALUE self, VALUE b); +VP_EXPORT VALUE BigDecimal_mult(VALUE self, VALUE b); +VP_EXPORT VALUE BigDecimal_add2(VALUE self, VALUE b, VALUE n); +VP_EXPORT VALUE BigDecimal_sub2(VALUE self, VALUE b, VALUE n); +VP_EXPORT VALUE BigDecimal_mult2(VALUE self, VALUE b, VALUE n); +VP_EXPORT VALUE BigDecimal_split(VALUE self); +VP_EXPORT VALUE BigDecimal_decimal_shift(VALUE self, VALUE v); +VP_EXPORT inline BDVALUE GetBDValueMust(VALUE v); +VP_EXPORT inline BDVALUE rbd_allocate_struct_zero_wrap(int sign, size_t const digits); +#define NewZeroWrap rbd_allocate_struct_zero_wrap + /* * ------------------ * MACRO definitions. diff --git a/ext/bigdecimal/div.h b/ext/bigdecimal/div.h new file mode 100644 index 00000000..e6dd89c9 --- /dev/null +++ b/ext/bigdecimal/div.h @@ -0,0 +1,192 @@ +// Calculate the inverse of x using the Newton-Raphson method. +static VALUE +newton_raphson_inverse(VALUE x, size_t prec) { + BDVALUE bdone = NewZeroWrap(1, 1); + VpSetOne(bdone.real); + VALUE one = bdone.bigdecimal; + + // Initial approximation in 2 digits + BDVALUE bdx = GetBDValueMust(x); + BDVALUE inv0 = NewZeroWrap(1, 2 * BIGDECIMAL_COMPONENT_FIGURES); + VpSetOne(inv0.real); + DECDIG_DBL numerator = (DECDIG_DBL)BIGDECIMAL_BASE * 100; + DECDIG_DBL denominator = (DECDIG_DBL)bdx.real->frac[0] * 100 + (DECDIG_DBL)(bdx.real->Prec >= 2 ? bdx.real->frac[1] : 0) * 100 / BIGDECIMAL_BASE; + inv0.real->frac[0] = (DECDIG)(numerator / denominator); + inv0.real->frac[1] = (DECDIG)((numerator % denominator) * (BIGDECIMAL_BASE / 100) / denominator * 100); + inv0.real->Prec = 2; + inv0.real->exponent = 1 - bdx.real->exponent; + VpNmlz(inv0.real); + RB_GC_GUARD(bdx.bigdecimal); + VALUE inv = inv0.bigdecimal; + + int bl = 1; + while (((size_t)1 << bl) < prec) bl++; + + for (int i = bl; i >= 0; i--) { + size_t n = (prec >> i) + 2; + if (n > prec) n = prec; + // Newton-Raphson iteration: inv_next = inv + inv * (1 - x * inv) + VALUE one_minus_x_inv = BigDecimal_sub2( + one, + BigDecimal_mult(BigDecimal_mult2(x, one, SIZET2NUM(n + 1)), inv), + SIZET2NUM(SIZET2NUM(n / 2)) + ); + inv = BigDecimal_add2( + inv, + BigDecimal_mult(inv, one_minus_x_inv), + SIZET2NUM(n) + ); + } + return inv; +} + +// Calculates divmod by multiplying approximate reciprocal of y +static void +divmod_by_inv_mul(VALUE x, VALUE y, VALUE inv, VALUE *res_div, VALUE *res_mod) { + VALUE div = BigDecimal_fix(BigDecimal_mult(x, inv)); + VALUE mod = BigDecimal_sub(x, BigDecimal_mult(div, y)); + while (RTEST(BigDecimal_lt(mod, INT2FIX(0)))) { + mod = BigDecimal_add(mod, y); + div = BigDecimal_sub(div, INT2FIX(1)); + } + while (RTEST(BigDecimal_ge(mod, y))) { + mod = BigDecimal_sub(mod, y); + div = BigDecimal_add(div, INT2FIX(1)); + } + *res_div = div; + *res_mod = mod; +} + +static void +slice_copy(DECDIG *dest, Real *src, size_t rshift, size_t length) { + ssize_t start = src->exponent - rshift - length; + if (start >= (ssize_t)src->Prec) return; + if (start < 0) { + dest -= start; + length += start; + start = 0; + } + size_t max_length = src->Prec - start; + memcpy(dest, src->frac + start, Min(length, max_length) * sizeof(DECDIG)); +} + +/* Calculates divmod using Newton-Raphson method. + * x and y must be a BigDecimal representing an integer value. + * + * To calculate with low cost, we need to split x into blocks and perform divmod for each block. + * x_digits = remaining_digits(<= y_digits) + block_digits * num_blocks + * + * Example: + * xxx_xxxxx_xxxxx_xxxxx(18 digits) / yyyyy(5 digits) + * remaining_digits = 3, block_digits = 5, num_blocks = 3 + * repeating xxxxx_xxxxxx.divmod(yyyyy) calculation 3 times. + * + * In each divmod step, dividend is at most (y_digits + block_digits) digits and divisor is y_digits digits. + * Reciprocal of y needs block_digits + 1 precision. + */ +static void +divmod_newton(VALUE x, VALUE y, VALUE *div_out, VALUE *mod_out) { + size_t x_digits = NUM2SIZET(BigDecimal_exponent(x)); + size_t y_digits = NUM2SIZET(BigDecimal_exponent(y)); + if (x_digits <= y_digits) x_digits = y_digits + 1; + + size_t n = x_digits / y_digits; + size_t block_figs = (x_digits - y_digits) / n / BIGDECIMAL_COMPONENT_FIGURES + 1; + size_t block_digits = block_figs * BIGDECIMAL_COMPONENT_FIGURES; + size_t num_blocks = (x_digits - y_digits + block_digits - 1) / block_digits; + size_t y_figs = (y_digits - 1) / BIGDECIMAL_COMPONENT_FIGURES + 1; + VALUE yinv = newton_raphson_inverse(y, block_digits + 1); + + BDVALUE divident = NewZeroWrap(1, BIGDECIMAL_COMPONENT_FIGURES * (y_figs + block_figs)); + BDVALUE div_result = NewZeroWrap(1, BIGDECIMAL_COMPONENT_FIGURES * (num_blocks * block_figs + 1)); + BDVALUE bdx = GetBDValueMust(x); + + VALUE mod = BigDecimal_fix(BigDecimal_decimal_shift(x, SSIZET2NUM(-num_blocks * block_digits))); + for (ssize_t i = num_blocks - 1; i >= 0; i--) { + memset(divident.real->frac, 0, (y_figs + block_figs) * sizeof(DECDIG)); + + BDVALUE bdmod = GetBDValueMust(mod); + slice_copy(divident.real->frac, bdmod.real, 0, y_figs); + slice_copy(divident.real->frac + y_figs, bdx.real, i * block_figs, block_figs); + RB_GC_GUARD(bdmod.bigdecimal); + + VpSetSign(divident.real, 1); + divident.real->exponent = y_figs + block_figs; + divident.real->Prec = y_figs + block_figs; + VpNmlz(divident.real); + + VALUE div; + divmod_by_inv_mul(divident.bigdecimal, y, yinv, &div, &mod); + BDVALUE bddiv = GetBDValueMust(div); + slice_copy(div_result.real->frac + (num_blocks - i - 1) * block_figs, bddiv.real, 0, block_figs + 1); + RB_GC_GUARD(bddiv.bigdecimal); + } + VpSetSign(div_result.real, 1); + div_result.real->exponent = num_blocks * block_figs + 1; + div_result.real->Prec = num_blocks * block_figs + 1; + VpNmlz(div_result.real); + RB_GC_GUARD(bdx.bigdecimal); + RB_GC_GUARD(divident.bigdecimal); + RB_GC_GUARD(div_result.bigdecimal); + *div_out = div_result.bigdecimal; + *mod_out = mod; +} + +static VALUE +VpDivdNewtonInner(VALUE args_ptr) +{ + Real **args = (Real**)args_ptr; + Real *c = args[0], *r = args[1], *a = args[2], *b = args[3]; + BDVALUE a2, b2, c2, r2; + VALUE div, mod, a2_frac = Qnil; + size_t div_prec = c->MaxPrec - 1; + size_t base_prec = b->Prec; + + a2 = NewZeroWrap(1, a->Prec * BIGDECIMAL_COMPONENT_FIGURES); + b2 = NewZeroWrap(1, b->Prec * BIGDECIMAL_COMPONENT_FIGURES); + VpAsgn(a2.real, a, 1); + VpAsgn(b2.real, b, 1); + VpSetSign(a2.real, 1); + VpSetSign(b2.real, 1); + a2.real->exponent = base_prec + div_prec; + b2.real->exponent = base_prec; + + if ((ssize_t)a2.real->Prec > a2.real->exponent) { + a2_frac = BigDecimal_frac(a2.bigdecimal); + VpMidRound(a2.real, VP_ROUND_DOWN, 0); + } + divmod_newton(a2.bigdecimal, b2.bigdecimal, &div, &mod); + if (a2_frac != Qnil) mod = BigDecimal_add(mod, a2_frac); + + c2 = GetBDValueMust(div); + r2 = GetBDValueMust(mod); + VpAsgn(c, c2.real, VpGetSign(a) * VpGetSign(b)); + VpAsgn(r, r2.real, VpGetSign(a)); + AddExponent(c, a->exponent); + AddExponent(c, -b->exponent); + AddExponent(c, -div_prec); + AddExponent(r, a->exponent); + AddExponent(r, -base_prec - div_prec); + RB_GC_GUARD(a2.bigdecimal); + RB_GC_GUARD(a2.bigdecimal); + RB_GC_GUARD(c2.bigdecimal); + RB_GC_GUARD(r2.bigdecimal); + return Qnil; +} + +static VALUE +ensure_restore_prec_limit(VALUE limit) +{ + VpSetPrecLimit(NUM2SIZET(limit)); + return Qnil; +} + +static void +VpDivdNewton(Real *c, Real *r, Real *a, Real *b) +{ + Real *args[4] = {c, r, a, b}; + size_t pl = VpGetPrecLimit(); + VpSetPrecLimit(0); + // Ensure restoring prec limit because some methods used in VpDivdNewtonInner may raise an exception + rb_ensure(VpDivdNewtonInner, (VALUE)args, ensure_restore_prec_limit, SIZET2NUM(pl)); +} diff --git a/ext/bigdecimal/ntt.h b/ext/bigdecimal/ntt.h new file mode 100644 index 00000000..941f23f7 --- /dev/null +++ b/ext/bigdecimal/ntt.h @@ -0,0 +1,191 @@ +// NTT (Number Theoretic Transform) implementation for BigDecimal multiplication + +#define NTT_PRIMITIVE_ROOT 17 +#define NTT_PRIME_BASE1 24 +#define NTT_PRIME_BASE2 26 +#define NTT_PRIME_BASE3 29 +#define NTT_PRIME_SHIFT 27 +#define NTT_PRIME1 (((uint32_t)NTT_PRIME_BASE1 << NTT_PRIME_SHIFT) | 1) +#define NTT_PRIME2 (((uint32_t)NTT_PRIME_BASE2 << NTT_PRIME_SHIFT) | 1) +#define NTT_PRIME3 (((uint32_t)NTT_PRIME_BASE3 << NTT_PRIME_SHIFT) | 1) +#define MAX_NTT32_BITS 27 +#define NTT_DECDIG_BASE 1000000000 + +// Calculates base**ex % mod +static uint32_t +mod_pow(uint32_t base, uint32_t ex, uint32_t mod) { + uint32_t res = 1; + uint32_t bit = 1; + while (true) { + if (ex & bit) { + ex ^= bit; + res = ((uint64_t)res * base) % mod; + } + if (!ex) break; + base = ((uint64_t)base * base) % mod; + bit <<= 1; + } + return res; +} + +// Recursively performs butterfly operations of NTT +static void +ntt_recursive(int size_bits, uint32_t *input, uint32_t *output, uint32_t *tmp, int depth, uint32_t r, uint32_t prime) { + if (depth > 0) { + ntt_recursive(size_bits, input, tmp, output, depth - 1, ((uint64_t)r * r) % prime, prime); + } else { + tmp = input; + } + uint32_t size_half = (uint32_t)1 << (size_bits - 1); + uint32_t stride = (uint32_t)1 << (size_bits - depth - 1); + uint32_t n = size_half / stride; + uint32_t rn = 1, rm = prime - 1; + uint32_t idx = 0; + for (uint32_t i = 0; i < n; i++) { + uint32_t j = i * 2 * stride; + for (uint32_t k = 0; k < stride; k++, j++, idx++) { + uint32_t a = tmp[j], b = tmp[j + stride]; + output[idx] = (a + (uint64_t)rn * b) % prime; + output[idx + size_half] = (a + (uint64_t)rm * b) % prime; + } + rn = ((uint64_t)rn * r) % prime; + rm = ((uint64_t)rm * r) % prime; + } +} + +/* Perform NTT on input array. + * base, shift: Represent the prime number as (base << shift | 1) + * r_base: Primitive root of unity modulo prime + * size_bits: log2 of the size of the input array. Should be less or equal to shift + * input: input array of size (1 << size_bits) + */ +static void +ntt(int size_bits, uint32_t *input, uint32_t *output, uint32_t *tmp, int r_base, int base, int shift, int dir) { + uint32_t size = (uint32_t)1 << size_bits; + uint32_t prime = ((uint32_t)base << shift) | 1; + + // rmax**(1 << shift) % prime == 1 + // r**size % prime == 1 + uint32_t rmax = mod_pow(r_base, base, prime); + uint32_t r = mod_pow(rmax, (uint32_t)1 << (shift - size_bits), prime); + + if (dir < 0) r = mod_pow(r, prime - 2, prime); + ntt_recursive(size_bits, input, output, tmp, size_bits - 1, r, prime); + if (dir < 0) { + uint32_t n_inv = mod_pow((uint32_t)size, prime - 2, prime); + for (uint32_t i = 0; i < size; i++) { + output[i] = ((uint64_t)output[i] * n_inv) % prime; + } + } +} + +/* Calculate c that satisfies: c % PRIME1 == mod1 && c % PRIME2 == mod2 && c % PRIME3 == mod3 + * c = (mod1 * 35002755423056150739595925972 + mod2 * 14584479687667766215746868453 + mod3 * 37919651490985126265126719818) % (PRIME1 * PRIME2 * PRIME3) + * Assume c <= 999999999**2*(1<<27) + */ +static inline void +mod_restore_prime_24_26_29_shift_27(uint32_t mod1, uint32_t mod2, uint32_t mod3, uint32_t *digits) { + // Use mixed radix notation to eliminate modulo by PRIME1 * PRIME2 * PRIME3 + // [DIG0, DIG1, DIG2] = DIG0 + DIG1 * PRIME1 + DIG2 * PRIME1 * PRIME2 + // DIG0: 0...PRIME1, DIG1: 0...PRIME2, DIG2: 0...PRIME3 + // 35002755423056150739595925972 = [1, 3489660916, 3113851359] + // 14584479687667766215746868453 = [0, 13, 1297437912] + // 37919651490985126265126719818 = [0, 0, 3373338954] + uint64_t c0 = mod1; + uint64_t c1 = (uint64_t)mod2 * 13 + (uint64_t)mod1 * 3489660916; + uint64_t c2 = (uint64_t)mod3 * 3373338954 % NTT_PRIME3 + (uint64_t)mod2 * 1297437912 % NTT_PRIME3 + (uint64_t)mod1 * 3113851359 % NTT_PRIME3; + c2 += c1 / NTT_PRIME2; + c1 %= NTT_PRIME2; + c2 %= NTT_PRIME3; + // Base conversion. c fits in 3 digits. + c1 += c2 % NTT_DECDIG_BASE * NTT_PRIME2; + c0 += c1 % NTT_DECDIG_BASE * NTT_PRIME1; + c1 /= NTT_DECDIG_BASE; + digits[0] = c0 % NTT_DECDIG_BASE; + c0 /= NTT_DECDIG_BASE; + c1 += c2 / NTT_DECDIG_BASE % NTT_DECDIG_BASE * NTT_PRIME2; + c0 += c1 % NTT_DECDIG_BASE * NTT_PRIME1; + c1 /= NTT_DECDIG_BASE; + digits[1] = c0 % NTT_DECDIG_BASE; + digits[2] = (uint32_t)(c0 / NTT_DECDIG_BASE + c1 % NTT_DECDIG_BASE * NTT_PRIME1); +} + +/* + * NTT multiplication + * Uses three NTTs with mod (24 << 27 | 1), (26 << 27 | 1), and (29 << 27 | 1) + */ +static void +ntt_multiply(size_t a_size, size_t b_size, uint32_t *a, uint32_t *b, uint32_t *c) { + if (a_size < b_size) { + ntt_multiply(b_size, a_size, b, a, c); + return; + } + + int b_bits = 0; + while (((uint32_t)1 << b_bits) < (uint32_t)b_size) b_bits++; + int ntt_size_bits = b_bits + 1; + if (ntt_size_bits > MAX_NTT32_BITS) { + rb_raise(rb_eArgError, "Multiply size too large"); + } + + // To calculate large_a * small_b faster, split into several batches. + uint32_t ntt_size = (uint32_t)1 << ntt_size_bits; + uint32_t batch_size = ntt_size - (uint32_t)b_size; + uint32_t batch_count = (uint32_t)((a_size + batch_size - 1) / batch_size); + + uint32_t *mem = ruby_xcalloc(sizeof(uint32_t), ntt_size * 9); + uint32_t *ntt1 = mem; + uint32_t *ntt2 = mem + ntt_size; + uint32_t *ntt3 = mem + ntt_size * 2; + uint32_t *tmp1 = mem + ntt_size * 3; + uint32_t *tmp2 = mem + ntt_size * 4; + uint32_t *tmp3 = mem + ntt_size * 5; + uint32_t *conv1 = mem + ntt_size * 6; + uint32_t *conv2 = mem + ntt_size * 7; + uint32_t *conv3 = mem + ntt_size * 8; + + // Calculate NTT for b in three primes. Result is reused for each batch of a. + memcpy(tmp1, b, b_size * sizeof(uint32_t)); + memset(tmp1 + b_size, 0, (ntt_size - b_size) * sizeof(uint32_t)); + ntt(ntt_size_bits, tmp1, ntt1, tmp2, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE1, NTT_PRIME_SHIFT, +1); + ntt(ntt_size_bits, tmp1, ntt2, tmp2, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE2, NTT_PRIME_SHIFT, +1); + ntt(ntt_size_bits, tmp1, ntt3, tmp2, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE3, NTT_PRIME_SHIFT, +1); + + memset(c, 0, (a_size + b_size) * sizeof(uint32_t)); + for (uint32_t idx = 0; idx < batch_count; idx++) { + uint32_t len = idx == batch_count - 1 ? (uint32_t)a_size - idx * batch_size : batch_size; + memcpy(tmp1, a + idx * batch_size, len * sizeof(uint32_t)); + memset(tmp1 + len, 0, (ntt_size - len) * sizeof(uint32_t)); + // Calculate convolution for this batch in three primes + ntt(ntt_size_bits, tmp1, tmp2, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE1, NTT_PRIME_SHIFT, +1); + for (uint32_t i = 0; i < ntt_size; i++) tmp2[i] = ((uint64_t)tmp2[i] * ntt1[i]) % NTT_PRIME1; + ntt(ntt_size_bits, tmp2, conv1, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE1, NTT_PRIME_SHIFT, -1); + ntt(ntt_size_bits, tmp1, tmp2, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE2, NTT_PRIME_SHIFT, +1); + for (uint32_t i = 0; i < ntt_size; i++) tmp2[i] = ((uint64_t)tmp2[i] * ntt2[i]) % NTT_PRIME2; + ntt(ntt_size_bits, tmp2, conv2, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE2, NTT_PRIME_SHIFT, -1); + ntt(ntt_size_bits, tmp1, tmp2, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE3, NTT_PRIME_SHIFT, +1); + for (uint32_t i = 0; i < ntt_size; i++) tmp2[i] = ((uint64_t)tmp2[i] * ntt3[i]) % NTT_PRIME3; + ntt(ntt_size_bits, tmp2, conv3, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE3, NTT_PRIME_SHIFT, -1); + + // Restore the original convolution value from three convolutions calculated in three primes. + // Each convolution value is maximum 999999999**2*(1<<27)/2 + for (uint32_t i = 0; i < ntt_size; i++) { + uint32_t dig[3]; + mod_restore_prime_24_26_29_shift_27(conv1[i], conv2[i], conv3[i], dig); + // Maximum values of dig[0], dig[1], and dig[2] are 999999999, 999999999 and 67108863 respectively + // Maximum overlapped sum (considering overlaps between 2 batches) is less than 4134217722 + // so this sum doesn't overflow uint32_t. + for (int j = 0; j < 3; j++) { + // Index check: if dig[j] is non-zero, assign index is within valid range. + if (dig[j]) c[idx * batch_size + i + 1 - j] += dig[j]; + } + } + } + uint32_t carry = 0; + for (int32_t i = (uint32_t)(a_size + b_size - 1); i >= 0; i--) { + uint32_t v = c[i] + carry; + c[i] = v % NTT_DECDIG_BASE; + carry = v / NTT_DECDIG_BASE; + } + ruby_xfree(mem); +} diff --git a/test/bigdecimal/test_vp_operation.rb b/test/bigdecimal/test_vp_operation.rb index b2f1d75a..3d527edb 100644 --- a/test/bigdecimal/test_vp_operation.rb +++ b/test/bigdecimal/test_vp_operation.rb @@ -13,6 +13,10 @@ def setup end end + def ntt_mult_available? + BASE_FIG == 9 + end + def test_vpmult assert_equal(BigDecimal('121932631112635269'), BigDecimal('123456789').vpmult(BigDecimal('987654321'))) assert_equal(BigDecimal('12193263.1112635269'), BigDecimal('123.456789').vpmult(BigDecimal('98765.4321'))) @@ -21,6 +25,68 @@ def test_vpmult assert_equal(BigDecimal("#{x * y}e-300"), BigDecimal("#{x}e-100").vpmult(BigDecimal("#{y}e-200"))) end + def test_nttmult + omit 'NTT multiplication is only available for 32-bit DECDIG' unless ntt_mult_available? + [*1..32].repeated_permutation(2) do |a, b| + x = BigDecimal(10 ** (BASE_FIG * a) / 7) + y = BigDecimal(10 ** (BASE_FIG * b) / 13) + assert_equal(x.to_i * y.to_i, x.nttmult(y)) + end + end + + def test_newton_inverse + xs = [BigDecimal(3), BigDecimal('123e50'), BigDecimal('13' * 44), BigDecimal('17' * 45), BigDecimal('19' * 46)] + %i[up half_up down].each do |rounding_mode| + BigDecimal.save_rounding_mode do + BigDecimal.mode(BigDecimal::ROUND_MODE, rounding_mode) + [*1..32, 50, 100, 200, 300].each do |prec| + xs.each do |x| + inv = x.newton_raphson_inverse(prec) + assert_in_delta(1, x * inv, BigDecimal("1e#{1 - prec}")) + + high_precision_inv = inv * (2 - x * inv) + expected_inv = high_precision_inv.mult(1, prec) + last_digit = BigDecimal("1e#{expected_inv.exponent - prec}") + assert_include([expected_inv - last_digit, expected_inv, expected_inv + last_digit], inv) + end + end + end + end + end + + def test_not_affected_by_limit + x_int = 123**135 + y_int = 135**123 + xy_int = x_int * y_int + mod_int = 111**111 + x = BigDecimal(x_int) + y = BigDecimal(y_int) + xy = BigDecimal(xy_int) + mod = BigDecimal(mod_int) + z = BigDecimal(xy_int + mod_int) + BigDecimal.save_limit do + BigDecimal.limit 3 + assert_equal(xy, x.vpmult(y)) + assert_equal(3, BigDecimal.limit) + if ntt_mult_available? + assert_equal(xy, x.nttmult(y)) + assert_equal(3, BigDecimal.limit) + end + + prec = (z.exponent - 1) / BASE_FIG - (y.exponent - 1) / BASE_FIG + 1 + assert_equal([x, mod], z.vpdivd(y, prec)) + assert_equal(3, BigDecimal.limit) + assert_equal([x, mod], z.vpdivd_newton(y, prec)) + assert_equal(3, BigDecimal.limit) + end + end + + def assert_vpdivd_equal(expected_divmod, x_y_n) + x, *args = x_y_n + assert_equal(expected_divmod, x.vpdivd(*args)) + assert_equal(expected_divmod, x.vpdivd_newton(*args)) + end + def test_vpdivd # a[0] > b[0] # XXXX_YYYY_ZZZZ / 1111 #=> 000X_000Y_000Z @@ -31,11 +97,11 @@ def test_vpdivd d3 = BigDecimal("4e#{BASE_FIG * 2}") + d2 d4 = BigDecimal("5e#{BASE_FIG}") + d3 d5 = BigDecimal(6) + d4 - assert_equal([d1, x1 - d1 * y], x1.vpdivd(y, 1)) - assert_equal([d2, x1 - d2 * y], x1.vpdivd(y, 2)) - assert_equal([d3, x1 - d3 * y], x1.vpdivd(y, 3)) - assert_equal([d4, x1 - d4 * y], x1.vpdivd(y, 4)) - assert_equal([d5, x1 - d5 * y], x1.vpdivd(y, 5)) + assert_vpdivd_equal([d1, x1 - d1 * y], [x1, y, 1]) + assert_vpdivd_equal([d2, x1 - d2 * y], [x1, y, 2]) + assert_vpdivd_equal([d3, x1 - d3 * y], [x1, y, 3]) + assert_vpdivd_equal([d4, x1 - d4 * y], [x1, y, 4]) + assert_vpdivd_equal([d5, x1 - d5 * y], [x1, y, 5]) # a[0] < b[0] # 00XX_XXYY_YYZZ_ZZ00 / 1111 #=> 0000_0X00_0Y00_0Z00 @@ -46,28 +112,28 @@ def test_vpdivd d3 = BigDecimal("4e#{2 * BASE_FIG + shift}") + d2 d4 = BigDecimal("5e#{BASE_FIG + shift}") + d3 d5 = BigDecimal("6e#{shift}") + d4 - assert_equal([0, x2], x2.vpdivd(y, 1)) - assert_equal([d1, x2 - d1 * y], x2.vpdivd(y, 2)) - assert_equal([d2, x2 - d2 * y], x2.vpdivd(y, 3)) - assert_equal([d3, x2 - d3 * y], x2.vpdivd(y, 4)) - assert_equal([d4, x2 - d4 * y], x2.vpdivd(y, 5)) - assert_equal([d5, x2 - d5 * y], x2.vpdivd(y, 6)) + assert_vpdivd_equal([0, x2], [x2, y, 1]) + assert_vpdivd_equal([d1, x2 - d1 * y], [x2, y, 2]) + assert_vpdivd_equal([d2, x2 - d2 * y], [x2, y, 3]) + assert_vpdivd_equal([d3, x2 - d3 * y], [x2, y, 4]) + assert_vpdivd_equal([d4, x2 - d4 * y], [x2, y, 5]) + assert_vpdivd_equal([d5, x2 - d5 * y], [x2, y, 6]) end def test_vpdivd_large_quotient_prec # 0001 / 0003 = 0000_3333_3333 - assert_equal([BigDecimal('0.' + '3' * BASE_FIG * 9), BigDecimal("1e-#{9 * BASE_FIG}")], BigDecimal(1).vpdivd(BigDecimal(3), 10)) + assert_vpdivd_equal([BigDecimal('0.' + '3' * BASE_FIG * 9), BigDecimal("1e-#{9 * BASE_FIG}")], [BigDecimal(1), BigDecimal(3), 10]) # 1000 / 0003 = 0333_3333_3333 - assert_equal([BigDecimal('3' * (BASE_FIG - 1) + '.' + '3' * BASE_FIG * 9), BigDecimal("1e-#{9 * BASE_FIG}")], BigDecimal(BASE / 10).vpdivd(BigDecimal(3), 10)) + assert_vpdivd_equal([BigDecimal('3' * (BASE_FIG - 1) + '.' + '3' * BASE_FIG * 9), BigDecimal("1e-#{9 * BASE_FIG}")], [BigDecimal(BASE / 10), BigDecimal(3), 10]) end def test_vpdivd_with_one x = BigDecimal('1234.2468000001234') - assert_equal([BigDecimal('1234'), BigDecimal('0.2468000001234')], x.vpdivd(BigDecimal(1), 1)) - assert_equal([BigDecimal('+1234.2468'), BigDecimal('+0.1234e-9')], (+x).vpdivd(BigDecimal(+1), 2)) - assert_equal([BigDecimal('-1234.2468'), BigDecimal('+0.1234e-9')], (+x).vpdivd(BigDecimal(-1), 2)) - assert_equal([BigDecimal('-1234.2468'), BigDecimal('-0.1234e-9')], (-x).vpdivd(BigDecimal(+1), 2)) - assert_equal([BigDecimal('+1234.2468'), BigDecimal('-0.1234e-9')], (-x).vpdivd(BigDecimal(-1), 2)) + assert_vpdivd_equal([BigDecimal('1234'), BigDecimal('0.2468000001234')], [x, BigDecimal(1), 1]) + assert_vpdivd_equal([BigDecimal('+1234.2468'), BigDecimal('+0.1234e-9')], [+x, BigDecimal(+1), 2]) + assert_vpdivd_equal([BigDecimal('-1234.2468'), BigDecimal('+0.1234e-9')], [+x, BigDecimal(-1), 2]) + assert_vpdivd_equal([BigDecimal('-1234.2468'), BigDecimal('-0.1234e-9')], [-x, BigDecimal(+1), 2]) + assert_vpdivd_equal([BigDecimal('+1234.2468'), BigDecimal('-0.1234e-9')], [-x, BigDecimal(-1), 2]) end def test_vpdivd_precisions @@ -79,7 +145,7 @@ def test_vpdivd_precisions yn = (y.digits.size + BASE_FIG - 1) / BASE_FIG base = BASE ** (n - xn + yn - 1) div = BigDecimal((x * base / y).to_i) / base - assert_equal([div, x - y * div], BigDecimal(x).vpdivd(y, n)) + assert_vpdivd_equal([div, x - y * div], [BigDecimal(x), BigDecimal(y), n]) end end end @@ -92,7 +158,7 @@ def test_vpdivd_borrow x = y * (3 * BASE**4 + a * BASE**3 + b * BASE**2 + c * BASE + d) / BASE div = BigDecimal(x * BASE / y) / BASE mod = BigDecimal(x) - div * y - assert_equal([div, mod], BigDecimal(x).vpdivd(BigDecimal(y), 5)) + assert_vpdivd_equal([div, mod], [BigDecimal(x), BigDecimal(y), 5]) end end end @@ -104,22 +170,22 @@ def test_vpdivd_large_prec_divisor divy1_1 = BigDecimal(2) divy2_1 = BigDecimal(1) divy2_2 = BigDecimal('1.' + '9' * BASE_FIG) - assert_equal([divy1_1, x - y1 * divy1_1], x.vpdivd(y1, 1)) - assert_equal([divy2_1, x - y2 * divy2_1], x.vpdivd(y2, 1)) - assert_equal([divy2_2, x - y2 * divy2_2], x.vpdivd(y2, 2)) + assert_vpdivd_equal([divy1_1, x - y1 * divy1_1], [x, y1, 1]) + assert_vpdivd_equal([divy2_1, x - y2 * divy2_1], [x, y2, 1]) + assert_vpdivd_equal([divy2_2, x - y2 * divy2_2], [x, y2, 2]) end def test_vpdivd_intermediate_zero if BASE_FIG == 9 x = BigDecimal('123456789.246913578000000000123456789') y = BigDecimal('123456789') - assert_equal([BigDecimal('1.000000002000000000000000001'), BigDecimal(0)], x.vpdivd(y, 4)) - assert_equal([BigDecimal('1.000000000049999999'), BigDecimal('1e-18')], BigDecimal("2.000000000099999999").vpdivd(2, 3)) + assert_vpdivd_equal([BigDecimal('1.000000002000000000000000001'), BigDecimal(0)], [x, y, 4]) + assert_vpdivd_equal([BigDecimal('1.000000000049999999'), BigDecimal('1e-18')], [BigDecimal("2.000000000099999999"), 2, 3]) else x = BigDecimal('1234.246800001234') y = BigDecimal('1234') - assert_equal([BigDecimal('1.000200000001'), BigDecimal(0)], x.vpdivd(y, 4)) - assert_equal([BigDecimal('1.00000499'), BigDecimal('1e-8')], BigDecimal("2.00000999").vpdivd(2, 3)) + assert_vpdivd_equal([BigDecimal('1.000200000001'), BigDecimal(0)], [x, y, 4]) + assert_vpdivd_equal([BigDecimal('1.00000499'), BigDecimal('1e-8')], [BigDecimal("2.00000999"), 2, 3]) end end end From 13aacf903a6cecf89deeb2be09a430206027ff1b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 28 Jan 2026 14:50:48 +0900 Subject: [PATCH 512/546] Implement BigMath::PI with Gauss-Legendre algorithm (#434) It's fast and simple. Pi calculation will be the bottleneck when we improve sin/cos or erf/erfc with bit burst algorithm. Requires multiplication cost to be less than O(n^2). --- lib/bigdecimal/math.rb | 43 ++++++++++++------------------------------ sample/pi.rb | 2 +- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 9dff366d..e2bac544 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -885,39 +885,20 @@ def ldexp(x, exponent) # #=> "0.31415926535897932384626433832795e1" # def PI(prec) + # Gauss–Legendre algorithm prec = BigDecimal::Internal.coerce_validate_prec(prec, :PI) - n = prec + BigDecimal.double_fig - zero = BigDecimal("0") - one = BigDecimal("1") - two = BigDecimal("2") - - m25 = BigDecimal("-0.04") - m57121 = BigDecimal("-57121") - - pi = zero - - d = one - k = one - t = BigDecimal("-80") - while d.nonzero? && ((m = n - (pi.exponent - d.exponent).abs) > 0) - m = BigDecimal.double_fig if m < BigDecimal.double_fig - t = t*m25 - d = t.div(k,m) - k = k+two - pi = pi + d - end - - d = one - k = one - t = BigDecimal("956") - while d.nonzero? && ((m = n - (pi.exponent - d.exponent).abs) > 0) - m = BigDecimal.double_fig if m < BigDecimal.double_fig - t = t.div(m57121,n) - d = t.div(k,m) - pi = pi + d - k = k+two + n = prec + BigDecimal.double_fig + a = BigDecimal(1) + b = BigDecimal(0.5, 0).sqrt(n) + s = BigDecimal(0.25, 0) + t = 1 + while a != b && (a - b).exponent > 1 - n + c = (a - b).div(2, n) + a, b = (a + b).div(2, n), (a * b).sqrt(n) + s = s.sub(c * c * t, n) + t *= 2 end - pi.mult(1, prec) + (a * b).div(s, prec) end # call-seq: diff --git a/sample/pi.rb b/sample/pi.rb index 0dc27c43..4b17ed00 100644 --- a/sample/pi.rb +++ b/sample/pi.rb @@ -2,7 +2,7 @@ # pi.rb # # Calculates 3.1415.... (the number of times that a circle's diameter -# will fit around the circle) using J. Machin's formula. +# will fit around the circle) # require "bigdecimal" From d98216d6be55b2d51e659c94d952cea9b115b1ce Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 28 Jan 2026 15:00:36 +0900 Subject: [PATCH 513/546] Improve taylor series calculation of exp and sin by bit burst algorithm (#433) * Improve taylor series calculation of exp and sin by bit burst algorithm exp and sin becomes orders of magnitude faster. To make log and atan also fast, log and atan now depends on exp and sin. log(x): solve exp(y)-x=0 by Newton's method atan(x): solve tan(y)-x=0 by Newton's method * Drop Ruby 2.5 support bsearch for endless range is only available in ruby >= 2.6 --- .github/workflows/ci.yml | 2 +- bigdecimal.gemspec | 2 +- lib/bigdecimal.rb | 133 ++++++++++++++++++-------------- lib/bigdecimal/math.rb | 92 ++++++++++++---------- test/bigdecimal/test_bigmath.rb | 34 ++++---- 5 files changed, 148 insertions(+), 115 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9535341d..411d67d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby-truffleruby - min_version: 2.5 + min_version: 2.6 versions: '["debug"]' host: diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 6b20ac08..774fd223 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -53,7 +53,7 @@ Gem::Specification.new do |s| ] end - s.required_ruby_version = Gem::Requirement.new(">= 2.5.0") + s.required_ruby_version = Gem::Requirement.new(">= 2.6.0") s.metadata["changelog_uri"] = s.homepage + "/blob/master/CHANGES.md" end diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 12250ce9..998087d8 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -60,6 +60,46 @@ def self.nan_computation_result # :nodoc: end BigDecimal::NAN end + + # Iteration for Newton's method with increasing precision + def self.newton_loop(prec, initial_precision: BigDecimal.double_fig / 2, safe_margin: 2) # :nodoc: + precs = [] + while prec > initial_precision + precs << prec + prec = (precs.last + 1) / 2 + safe_margin + end + precs.reverse_each do |p| + yield p + end + end + + # Calculates Math.log(x.to_f) considering large or small exponent + def self.float_log(x) # :nodoc: + Math.log(x._decimal_shift(-x.exponent).to_f) + x.exponent * Math.log(10) + end + + # Calculating Taylor series sum using binary splitting method + # Calculates f(x) = (x/d0)*(1+(x/d1)*(1+(x/d2)*(1+(x/d3)*(1+...)))) + # x.n_significant_digits or ds.size must be small to be performant. + def self.taylor_sum_binary_splitting(x, ds, prec) # :nodoc: + fs = ds.map {|d| [0, BigDecimal(d)] } + # fs = [[a0, a1], [b0, b1], [c0, c1], ...] + # f(x) = a0/a1+(x/a1)*(1+b0/b1+(x/b1)*(1+c0/c1+(x/c1)*(1+d0/d1+(x/d1)*(1+...)))) + while fs.size > 1 + # Merge two adjacent fractions + # from: (1 + a0/a1 + x/a1 * (1 + b0/b1 + x/b1 * rest)) + # to: (1 + (a0*b1+x*(b0+b1))/(a1*b1) + (x*x)/(a1*b1) * rest) + xn = xn ? xn.mult(xn, prec) : x + fs = fs.each_slice(2).map do |(a, b)| + b ||= [0, BigDecimal(1)._decimal_shift([xn.exponent, 0].max + 2)] + [ + (a[0] * b[1]).add(xn * (b[0] + b[1]), prec), + a[1].mult(b[1], prec) + ] + end + end + BigDecimal(fs[0][0]).div(fs[0][1], prec) + end end # call-seq: @@ -226,9 +266,7 @@ def sqrt(prec) ex = exponent / 2 x = _decimal_shift(-2 * ex) y = BigDecimal(Math.sqrt(x.to_f), 0) - precs = [prec + BigDecimal.double_fig] - precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig - precs.reverse_each do |p| + Internal.newton_loop(prec + BigDecimal.double_fig) do |p| y = y.add(x.div(y, p), p).div(2, p) end y._decimal_shift(ex).mult(1, prec) @@ -264,59 +302,32 @@ def log(x, prec) return BigDecimal(0) if x == 1 prec2 = prec + BigDecimal.double_fig - BigDecimal.save_limit do - BigDecimal.limit(0) - if x > 10 || x < 0.1 - log10 = log(BigDecimal(10), prec2) - exponent = x.exponent - x = x._decimal_shift(-exponent) - if x < 0.3 - x *= 10 - exponent -= 1 - end - return (log10 * exponent).add(log(x, prec2), prec) - end - - x_minus_one_exponent = (x - 1).exponent - # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps - sqrt_steps = [Integer.sqrt(prec2) + 3 * x_minus_one_exponent, 0].max - - lg2 = 0.3010299956639812 - sqrt_prec = prec2 + [-x_minus_one_exponent, 0].max + (sqrt_steps * lg2).ceil - - sqrt_steps.times do - x = x.sqrt(sqrt_prec) - end - - # Taylor series for log(x) around 1 - # log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1) - # log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...) - x = (x - 1).div(x + 1, sqrt_prec) - y = x - x2 = x.mult(x, prec2) - 1.step do |i| - n = prec2 + x.exponent - y.exponent + x2.exponent - break if n <= 0 || x.zero? - x = x.mult(x2.round(n - x2.exponent), n) - y = y.add(x.div(2 * i + 1, n), prec2) - end + if x < 0.1 || x > 10 + exponent = (3 * x).exponent - 1 + x = x._decimal_shift(-exponent) + return log(10, prec2).mult(exponent, prec2).add(log(x, prec2), prec) + end - y.mult(2 ** (sqrt_steps + 1), prec) + # Solve exp(y) - x = 0 with Newton's method + # Repeat: y -= (exp(y) - x) / exp(y) + y = BigDecimal(BigDecimal::Internal.float_log(x), 0) + exp_additional_prec = [-(x - 1).exponent, 0].max + BigDecimal::Internal.newton_loop(prec2) do |p| + expy = exp(y, p + exp_additional_prec) + y = y.sub(expy.sub(x, p).div(expy, p), p) end + y.mult(1, prec) end - # Taylor series for exp(x) around 0 - private_class_method def _exp_taylor(x, prec) # :nodoc: - xn = BigDecimal(1) - y = BigDecimal(1) - 1.step do |i| - n = prec + xn.exponent - break if n <= 0 || xn.zero? - xn = xn.mult(x, n).div(i, n) - y = y.add(xn, prec) - end - y + private_class_method def _exp_binary_splitting(x, prec) # :nodoc: + return BigDecimal(1) if x.zero? + # Find k that satisfies x**k / k! < 10**(-prec) + log10 = Math.log(10) + logx = BigDecimal::Internal.float_log(x.abs) + step = (1..).bsearch { |k| Math.lgamma(k + 1)[0] - k * logx > prec * log10 } + # exp(x)-1 = x*(1+x/2*(1+x/3*(1+x/4*(1+x/5*(1+...))))) + 1 + BigDecimal::Internal.taylor_sum_binary_splitting(x, [*1..step], prec) end # call-seq: @@ -341,11 +352,21 @@ def exp(x, prec) prec2 = prec + BigDecimal.double_fig + cnt x = x._decimal_shift(-cnt) - # Calculation of exp(small_prec) is fast because calculation of x**n is fast - # Calculation of exp(small_abs) converges fast. - # exp(x) = exp(small_prec_part + small_abs_part) = exp(small_prec_part) * exp(small_abs_part) - x_small_prec = x.round(Integer.sqrt(prec2)) - y = _exp_taylor(x_small_prec, prec2).mult(_exp_taylor(x.sub(x_small_prec, prec2), prec2), prec2) + # Decimal form of bit-burst algorithm + # Calculate exp(x.xxxxxxxxxxxxxxxx) as + # exp(x.xx) * exp(0.00xx) * exp(0.0000xxxx) * exp(0.00000000xxxxxxxx) + x = x.mult(1, prec2) + n = 2 + y = BigDecimal(1) + BigDecimal.save_limit do + BigDecimal.limit(0) + while x != 0 do + partial_x = x.truncate(n) + x -= partial_x + y = y.mult(_exp_binary_splitting(partial_x, prec2), prec2) + n *= 2 + end + end # calculate exp(x * 10**cnt) from exp(x) # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10 diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index e2bac544..83bba449 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -94,6 +94,37 @@ def sqrt(x, prec) end end + private_class_method def _sin_binary_splitting(x, prec) # :nodoc: + return x if x.zero? + x2 = x.mult(x, prec) + # Find k that satisfies x2**k / (2k+1)! < 10**(-prec) + log10 = Math.log(10) + logx = BigDecimal::Internal.float_log(x.abs) + step = (1..).bsearch { |k| Math.lgamma(2 * k + 1)[0] - 2 * k * logx > prec * log10 } + # Construct denominator sequence for binary splitting + # sin(x) = x*(1-x2/(2*3)*(1-x2/(4*5)*(1-x2/(6*7)*(1-x2/(8*9)*(1-...))))) + ds = (1..step).map {|i| -(2 * i) * (2 * i + 1) } + x.mult(1 + BigDecimal::Internal.taylor_sum_binary_splitting(x2, ds, prec), prec) + end + + private_class_method def _sin_around_zero(x, prec) # :nodoc: + # Divide x into several parts + # sin(x.xxxxxxxx...) = sin(x.xx + 0.00xx + 0.0000xxxx + ...) + # Calculate sin of each part and restore sin(0.xxxxxxxx...) using addition theorem. + sin = BigDecimal(0) + cos = BigDecimal(1) + n = 2 + while x != 0 do + partial_x = x.truncate(n) + x -= partial_x + s = _sin_binary_splitting(partial_x, prec) + c = (1 - s * s).sqrt(prec) + sin, cos = (sin * c).add(cos * s, prec), (cos * c).sub(sin * s, prec) + n *= 2 + end + sin.clamp(BigDecimal(-1), BigDecimal(1)) + end + # call-seq: # cbrt(decimal, numeric) -> BigDecimal # @@ -156,26 +187,9 @@ def sin(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :sin) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sin) return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? - n = prec + BigDecimal.double_fig - one = BigDecimal("1") - two = BigDecimal("2") + n = prec + BigDecimal.double_fig sign, x = _sin_periodic_reduction(x, n) - x1 = x - x2 = x.mult(x,n) - y = x - d = y - i = one - z = one - while d.nonzero? && ((m = n - (y.exponent - d.exponent).abs) > 0) - m = BigDecimal.double_fig if m < BigDecimal.double_fig - x1 = -x2.mult(x1,n) - i += two - z *= (i-one) * i - d = x1.div(z,m) - y += d - end - y = BigDecimal("1") if y > 1 - y.mult(sign, prec) + _sin_around_zero(x, n).mult(sign, prec) end # call-seq: @@ -193,8 +207,9 @@ def cos(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :cos) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cos) return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? - sign, x = _sin_periodic_reduction(x, prec + BigDecimal.double_fig, add_half_pi: true) - sign * sin(x, prec) + n = prec + BigDecimal.double_fig + sign, x = _sin_periodic_reduction(x, n, add_half_pi: true) + _sin_around_zero(x, n).mult(sign, prec) end # call-seq: @@ -283,28 +298,21 @@ def atan(x, prec) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atan) return BigDecimal::Internal.nan_computation_result if x.nan? n = prec + BigDecimal.double_fig - pi = PI(n) + return PI(n).div(2 * x.infinite?, prec) if x.infinite? + x = -x if neg = x < 0 - return pi.div(neg ? -2 : 2, prec) if x.infinite? - return pi.div(neg ? -4 : 4, prec) if x.round(n) == 1 - x = BigDecimal("1").div(x, n) if inv = x > 1 - x = (-1 + sqrt(1 + x.mult(x, n), n)).div(x, n) if dbl = x > 0.5 - y = x - d = y - t = x - r = BigDecimal("3") - x2 = x.mult(x,n) - while d.nonzero? && ((m = n - (y.exponent - d.exponent).abs) > 0) - m = BigDecimal.double_fig if m < BigDecimal.double_fig - t = -t.mult(x2,n) - d = t.div(r,m) - y += d - r += 2 + x = BigDecimal(1).div(x, n) if inv = x < -1 || x > 1 + + # Solve tan(y) - x = 0 with Newton's method + # Repeat: y -= (tan(y) - x) * cos(y)**2 + y = BigDecimal(Math.atan(x.to_f), 0) + BigDecimal::Internal.newton_loop(n) do |p| + s = sin(y, p) + c = (1 - s * s).sqrt(p) + y = y.sub(c * (s.sub(c * x.mult(1, p), p)), p) end - y *= 2 if dbl - y = pi / 2 - y if inv - y = -y if neg - y.mult(1, prec) + y = PI(n) / 2 - y if inv + y.mult(neg ? -1 : 1, prec) end # call-seq: @@ -804,7 +812,7 @@ def lgamma(x, prec) loggamma_k = 0 ck_exponents = (1..a-1).map do |k| loggamma_k += Math.log10(k - 1) if k > 1 - -loggamma_k - k / log10f + (k - 0.5) * Math.log10(a - k) - BigMath.log10(x_low_prec.add(k, low_prec), low_prec) + -loggamma_k - k / log10f + (k - 0.5) * Math.log10(a - k) - BigDecimal::Internal.float_log(x_low_prec.add(k, low_prec)) / log10f end # Estimate exponent of sum by Stirling's approximation diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 5a6f4ee0..37f24e35 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -197,8 +197,13 @@ def test_sin assert_converge_in_precision {|n| sin(BigDecimal("1e-30"), n) } assert_converge_in_precision {|n| sin(BigDecimal(PI(50)), n) } assert_converge_in_precision {|n| sin(BigDecimal(PI(50) * 100), n) } - assert_operator(sin(PI(30) / 2, 30), :<=, 1) - assert_operator(sin(-PI(30) / 2, 30), :>=, -1) + [:up, :down].each do |mode| + BigDecimal.save_rounding_mode do + BigDecimal.mode(BigDecimal::ROUND_MODE, mode) + assert_operator(sin(PI(30) / 2, 30), :<=, 1) + assert_operator(sin(-PI(30) / 2, 30), :>=, -1) + end + end end def test_cos @@ -220,8 +225,13 @@ def test_cos assert_converge_in_precision {|n| cos(BigDecimal("1e50"), n) } assert_converge_in_precision {|n| cos(BigDecimal(PI(50) / 2), n) } assert_converge_in_precision {|n| cos(BigDecimal(PI(50) * 201 / 2), n) } - assert_operator(cos(PI(30), 30), :>=, -1) - assert_operator(cos(PI(30) * 2, 30), :<=, 1) + [:up, :down].each do |mode| + BigDecimal.save_rounding_mode do + BigDecimal.mode(BigDecimal::ROUND_MODE, mode) + assert_operator(cos(PI(30), 30), :>=, -1) + assert_operator(cos(PI(30) * 2, 30), :<=, 1) + end + end end def test_tan @@ -404,26 +414,20 @@ def test_exp def test_log assert_equal(0, log(BigDecimal("1.0"), 10)) - assert_in_epsilon(Math.log(10)*1000, log(BigDecimal("1e1000"), 10)) + assert_in_epsilon(1000 * Math.log(10), log(BigDecimal("1e1000"), 10)) + assert_in_epsilon(19999999999999 * Math.log(10), log(BigDecimal("1E19999999999999"), 10)) + assert_in_epsilon(-19999999999999 * Math.log(10), log(BigDecimal("1E-19999999999999"), 10)) assert_in_exact_precision( BigDecimal("2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862"), log(BigDecimal("10"), 100), 100 ) assert_converge_in_precision {|n| log(BigDecimal("2"), n) } - assert_converge_in_precision {|n| log(BigDecimal("1e-30") + 1, n) } - assert_converge_in_precision {|n| log(BigDecimal("1e-30"), n) } + assert_converge_in_precision {|n| log(1 + SQRT2 * BigDecimal("1e-30"), n) } + assert_converge_in_precision {|n| log(SQRT2 * BigDecimal("1e-30"), n) } assert_converge_in_precision {|n| log(BigDecimal("1e30"), n) } assert_converge_in_precision {|n| log(SQRT2, n) } assert_raise(Math::DomainError) {log(BigDecimal("-0.1"), 10)} - begin - x = BigDecimal("1E19999999999999") - rescue FloatDomainError - else - unless x.infinite? - assert_in_epsilon(Math.log(10) * 19999999999999, BigMath.log(x, 10)) - end - end end def test_log2 From bd4220f449370103f3c9ec938aac3acd0f13a2bb Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 29 Jan 2026 01:26:50 +0900 Subject: [PATCH 514/546] Remove calculating log(10) in BigMath.log for large/small x (#484) Remove slow calculation of log(10). Before: log(x) = log(10)*n + log(x/10**n) After: log(x) = log(x/exp(logx_approx)) + logx_approx --- lib/bigdecimal.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 998087d8..334161c4 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -303,10 +303,13 @@ def log(x, prec) prec2 = prec + BigDecimal.double_fig - if x < 0.1 || x > 10 - exponent = (3 * x).exponent - 1 - x = x._decimal_shift(-exponent) - return log(10, prec2).mult(exponent, prec2).add(log(x, prec2), prec) + # Reduce x to near 1 + if x > 1.01 || x < 0.99 + # log(x) = log(x/exp(logx_approx)) + logx_approx + logx_approx = BigDecimal(BigDecimal::Internal.float_log(x), 0) + x = x.div(exp(logx_approx, prec2), prec2) + else + logx_approx = BigDecimal(0) end # Solve exp(y) - x = 0 with Newton's method @@ -317,7 +320,7 @@ def log(x, prec) expy = exp(y, p + exp_additional_prec) y = y.sub(expy.sub(x, p).div(expy, p), p) end - y.mult(1, prec) + y.add(logx_approx, prec) end private_class_method def _exp_binary_splitting(x, prec) # :nodoc: From c5fbed8c7960253f5ebb3a480427ccd58df3717c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 29 Jan 2026 02:23:55 +0900 Subject: [PATCH 515/546] Add missing call-seq (#485) --- lib/bigdecimal/math.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 83bba449..38286944 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -585,6 +585,7 @@ def expm1(x, prec) exp(x, exp_prec).sub(1, prec) end + # call-seq: # erf(decimal, numeric) -> BigDecimal # # Computes the error function of +decimal+ to the specified number of digits of From 57cdef6f478ea011a9e076c5739e8b00d8e17dec Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 31 Jan 2026 02:12:57 +0900 Subject: [PATCH 516/546] Split internal extra calculation prec and BigDecimal.double_fig usage (#486) BigDecimal's code was mixing up BigDecimal.double_fig(exactly 16) and internal extra calculation prec(Could be any number around 10). This change will clearly split this two essentially different constant usage. --- lib/bigdecimal.rb | 11 ++++--- lib/bigdecimal/math.rb | 69 +++++++++++++++++++++--------------------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 334161c4..370e9c27 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -12,6 +12,9 @@ def _decimal_shift(i) # :nodoc: class BigDecimal module Internal # :nodoc: + # Default extra precision for intermediate calculations + # This value is currently the same as BigDecimal.double_fig, but defined separately for future changes. + EXTRA_PREC = 16 # Coerce x to BigDecimal with the specified precision. # TODO: some methods (example: BigMath.exp) require more precision than specified to coerce. @@ -210,7 +213,7 @@ def power(y, prec = 0) result_prec = prec.nonzero? || [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig result_prec = [result_prec, limit].min if prec.zero? && limit.nonzero? - prec2 = result_prec + BigDecimal.double_fig + prec2 = result_prec + BigDecimal::Internal::EXTRA_PREC if y < 0 inv = x.power(-y, prec2) @@ -266,7 +269,7 @@ def sqrt(prec) ex = exponent / 2 x = _decimal_shift(-2 * ex) y = BigDecimal(Math.sqrt(x.to_f), 0) - Internal.newton_loop(prec + BigDecimal.double_fig) do |p| + Internal.newton_loop(prec + BigDecimal::Internal::EXTRA_PREC) do |p| y = y.add(x.div(y, p), p).div(2, p) end y._decimal_shift(ex).mult(1, prec) @@ -301,7 +304,7 @@ def log(x, prec) return BigDecimal::Internal.infinity_computation_result if x.infinite? return BigDecimal(0) if x == 1 - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC # Reduce x to near 1 if x > 1.01 || x < 0.99 @@ -352,7 +355,7 @@ def exp(x, prec) # exp(x * 10**cnt) = exp(x)**(10**cnt) cnt = x < -1 || x > 1 ? x.exponent : 0 - prec2 = prec + BigDecimal.double_fig + cnt + prec2 = prec + BigDecimal::Internal::EXTRA_PREC + cnt x = x._decimal_shift(-cnt) # Decimal form of bit-burst algorithm diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 38286944..bbf8fcfd 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -75,8 +75,8 @@ def sqrt(x, prec) private_class_method def _sin_periodic_reduction(x, prec, add_half_pi: false) # :nodoc: return [1, x] if -Math::PI/2 <= x && x <= Math::PI/2 && !add_half_pi - mod_prec = prec + BigDecimal.double_fig - pi_extra_prec = [x.exponent, 0].max + BigDecimal.double_fig + mod_prec = prec + BigDecimal::Internal::EXTRA_PREC + pi_extra_prec = [x.exponent, 0].max + BigDecimal::Internal::EXTRA_PREC while true pi = PI(mod_prec + pi_extra_prec) half_pi = pi / 2 @@ -84,10 +84,10 @@ def sqrt(x, prec) mod -= half_pi if mod.zero? || mod_prec + mod.exponent <= 0 # mod is too small to estimate required pi precision - mod_prec = mod_prec * 3 / 2 + BigDecimal.double_fig + mod_prec = mod_prec * 3 / 2 + BigDecimal::Internal::EXTRA_PREC elsif mod_prec + mod.exponent < prec # Estimate required precision of pi - mod_prec = prec - mod.exponent + BigDecimal.double_fig + mod_prec = prec - mod.exponent + BigDecimal::Internal::EXTRA_PREC else return [div % 2 == 0 ? 1 : -1, mod.mult(1, prec)] end @@ -145,9 +145,7 @@ def cbrt(x, prec) ex = x.exponent / 3 x = x._decimal_shift(-3 * ex) y = BigDecimal(Math.cbrt(x.to_f), 0) - precs = [prec + BigDecimal.double_fig] - precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig - precs.reverse_each do |p| + BigDecimal::Internal.newton_loop(prec + BigDecimal::Internal::EXTRA_PREC) do |p| y = (2 * y + x.div(y, p).div(y, p)).div(3, p) end y._decimal_shift(ex).mult(neg ? -1 : 1, prec) @@ -168,7 +166,7 @@ def hypot(x, y, prec) y = BigDecimal::Internal.coerce_to_bigdecimal(y, prec, :hypot) return BigDecimal::Internal.nan_computation_result if x.nan? || y.nan? return BigDecimal::Internal.infinity_computation_result if x.infinite? || y.infinite? - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC sqrt(x.mult(x, prec2) + y.mult(y, prec2), prec) end @@ -187,7 +185,7 @@ def sin(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :sin) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sin) return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? - n = prec + BigDecimal.double_fig + n = prec + BigDecimal::Internal::EXTRA_PREC sign, x = _sin_periodic_reduction(x, n) _sin_around_zero(x, n).mult(sign, prec) end @@ -207,7 +205,7 @@ def cos(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :cos) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cos) return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? - n = prec + BigDecimal.double_fig + n = prec + BigDecimal::Internal::EXTRA_PREC sign, x = _sin_periodic_reduction(x, n, add_half_pi: true) _sin_around_zero(x, n).mult(sign, prec) end @@ -228,7 +226,8 @@ def cos(x, prec) # def tan(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :tan) - sin(x, prec + BigDecimal.double_fig).div(cos(x, prec + BigDecimal.double_fig), prec) + prec2 = prec + BigDecimal::Internal::EXTRA_PREC + sin(x, prec2).div(cos(x, prec2), prec) end # call-seq: @@ -248,7 +247,7 @@ def asin(x, prec) raise Math::DomainError, "Out of domain argument for asin" if x < -1 || x > 1 return BigDecimal::Internal.nan_computation_result if x.nan? - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC cos = (1 - x**2).sqrt(prec2) if cos.zero? PI(prec2).div(x > 0 ? 2 : -2, prec) @@ -274,7 +273,7 @@ def acos(x, prec) raise Math::DomainError, "Out of domain argument for acos" if x < -1 || x > 1 return BigDecimal::Internal.nan_computation_result if x.nan? - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC return (PI(prec2) / 2).sub(asin(x, prec2), prec) if x < 0 return PI(prec2).div(2, prec) if x.zero? @@ -297,7 +296,7 @@ def atan(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :atan) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atan) return BigDecimal::Internal.nan_computation_result if x.nan? - n = prec + BigDecimal.double_fig + n = prec + BigDecimal::Internal::EXTRA_PREC return PI(n).div(2 * x.infinite?, prec) if x.infinite? x = -x if neg = x < 0 @@ -341,7 +340,7 @@ def atan2(y, x, prec) y = -y if neg = y < 0 xlarge = y.abs < x.abs - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC if x > 0 v = xlarge ? atan(y.div(x, prec2), prec) : PI(prec2) / 2 - atan(x.div(y, prec2), prec2) else @@ -367,7 +366,7 @@ def sinh(x, prec) return BigDecimal::Internal.nan_computation_result if x.nan? return BigDecimal::Internal.infinity_computation_result * x.infinite? if x.infinite? - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC prec2 -= x.exponent if x.exponent < 0 e = exp(x, prec2) (e - BigDecimal(1).div(e, prec2)).div(2, prec) @@ -390,7 +389,7 @@ def cosh(x, prec) return BigDecimal::Internal.nan_computation_result if x.nan? return BigDecimal::Internal.infinity_computation_result if x.infinite? - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC e = exp(x, prec2) (e + BigDecimal(1).div(e, prec2)).div(2, prec) end @@ -412,7 +411,7 @@ def tanh(x, prec) return BigDecimal::Internal.nan_computation_result if x.nan? return BigDecimal(x.infinite?) if x.infinite? - prec2 = prec + BigDecimal.double_fig + [-x.exponent, 0].max + prec2 = prec + BigDecimal::Internal::EXTRA_PREC + [-x.exponent, 0].max e = exp(x, prec2) einv = BigDecimal(1).div(e, prec2) (e - einv).div(e + einv, prec) @@ -436,7 +435,7 @@ def asinh(x, prec) return BigDecimal::Internal.infinity_computation_result * x.infinite? if x.infinite? return -asinh(-x, prec) if x < 0 - sqrt_prec = prec + [-x.exponent, 0].max + BigDecimal.double_fig + sqrt_prec = prec + [-x.exponent, 0].max + BigDecimal::Internal::EXTRA_PREC log(x + sqrt(x**2 + 1, sqrt_prec), prec) end @@ -458,7 +457,7 @@ def acosh(x, prec) return BigDecimal::Internal.infinity_computation_result if x.infinite? return BigDecimal::Internal.nan_computation_result if x.nan? - log(x + sqrt(x**2 - 1, prec + BigDecimal.double_fig), prec) + log(x + sqrt(x**2 - 1, prec + BigDecimal::Internal::EXTRA_PREC), prec) end # call-seq: @@ -480,7 +479,7 @@ def atanh(x, prec) return BigDecimal::Internal.infinity_computation_result if x == 1 return -BigDecimal::Internal.infinity_computation_result if x == -1 - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC (log(x + 1, prec2) - log(1 - x, prec2)).div(2, prec) end @@ -505,10 +504,10 @@ def log2(x, prec) return BigDecimal::Internal.nan_computation_result if x.nan? return BigDecimal::Internal.infinity_computation_result if x.infinite? == 1 - prec2 = prec + BigDecimal.double_fig * 3 / 2 + prec2 = prec + BigDecimal::Internal::EXTRA_PREC * 3 / 2 v = log(x, prec2).div(log(BigDecimal(2), prec2), prec2) # Perform half-up rounding to calculate log2(2**n)==n correctly in every rounding mode - v = v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) + v = v.round(prec + BigDecimal::Internal::EXTRA_PREC - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) v.mult(1, prec) end @@ -533,10 +532,10 @@ def log10(x, prec) return BigDecimal::Internal.nan_computation_result if x.nan? return BigDecimal::Internal.infinity_computation_result if x.infinite? == 1 - prec2 = prec + BigDecimal.double_fig * 3 / 2 + prec2 = prec + BigDecimal::Internal::EXTRA_PREC * 3 / 2 v = log(x, prec2).div(log(BigDecimal(10), prec2), prec2) # Perform half-up rounding to calculate log10(10**n)==n correctly in every rounding mode - v = v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) + v = v.round(prec + BigDecimal::Internal::EXTRA_PREC - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) v.mult(1, prec) end @@ -573,9 +572,9 @@ def expm1(x, prec) if x < -1 # log10(exp(x)) = x * log10(e) lg_e = 0.4342944819032518 - exp_prec = prec + (lg_e * x).ceil + BigDecimal.double_fig + exp_prec = prec + (lg_e * x).ceil + BigDecimal::Internal::EXTRA_PREC elsif x < 1 - exp_prec = prec - x.exponent + BigDecimal.double_fig + exp_prec = prec - x.exponent + BigDecimal::Internal::EXTRA_PREC else exp_prec = prec end @@ -613,7 +612,7 @@ def erf(x, prec) return BigDecimal(1).sub(erfc, prec) if erfc end - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC x_smallprec = x.mult(1, Integer.sqrt(prec2) / 2) # Taylor series of x with small precision is fast erf1 = _erf_taylor(x_smallprec, BigDecimal(0), BigDecimal(0), prec2) @@ -637,7 +636,7 @@ def erfc(x, prec) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erfc) return BigDecimal::Internal.nan_computation_result if x.nan? return BigDecimal(1 - x.infinite?) if x.infinite? - return BigDecimal(1).sub(erf(x, prec + BigDecimal.double_fig), prec) if x < 0 + return BigDecimal(1).sub(erf(x, prec + BigDecimal::Internal::EXTRA_PREC), prec) if x < 0 return BigDecimal(0) if x > 5000000000 # erfc(5000000000) < 1e-10000000000000000000 (underflow) if x >= 8 @@ -648,7 +647,7 @@ def erfc(x, prec) # erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi) # Precision of erf(x) needs about log10(exp(-x**2)) extra digits log10 = 2.302585092994046 - high_prec = prec + BigDecimal.double_fig + (x.ceil**2 / log10).ceil + high_prec = prec + BigDecimal::Internal::EXTRA_PREC + (x.ceil**2 / log10).ceil BigDecimal(1).sub(erf(x, high_prec), prec) end @@ -697,7 +696,7 @@ def erfc(x, prec) # Using Stirling's approximation, we can simplify this condition to: # sqrt(2)/2 + k*log(k) - k - 2*k*log(x) < -prec*log(10) # and the left side is minimized when k = x**2. - prec += BigDecimal.double_fig + prec += BigDecimal::Internal::EXTRA_PREC xf = x.to_f kmax = (1..(xf ** 2).floor).bsearch do |k| Math.log(2) / 2 + k * Math.log(k) - k - 2 * k * Math.log(xf) < -prec * Math.log(10) @@ -726,7 +725,7 @@ def erfc(x, prec) def gamma(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :gamma) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :gamma) - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC if x < 0.5 raise Math::DomainError, 'Numerical argument is out of domain - gamma' if x.frac.zero? @@ -754,7 +753,7 @@ def gamma(x, prec) def lgamma(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :lgamma) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :lgamma) - prec2 = prec + BigDecimal.double_fig + prec2 = prec + BigDecimal::Internal::EXTRA_PREC if x < 0.5 return [BigDecimal::INFINITY, 1] if x.frac.zero? @@ -763,7 +762,7 @@ def lgamma(x, prec) pi = PI(prec2) sin = _sinpix(x, pi, prec2) log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec2).first + BigMath.log(sin.abs, prec2), prec) - return [log_gamma, sin > 0 ? 1 : -1] if prec2 + log_gamma.exponent > prec + BigDecimal.double_fig + return [log_gamma, sin > 0 ? 1 : -1] if prec2 + log_gamma.exponent > prec + BigDecimal::Internal::EXTRA_PREC # Retry with higher precision if loss of significance is too large prec2 = prec2 * 3 / 2 @@ -896,7 +895,7 @@ def ldexp(x, exponent) def PI(prec) # Gauss–Legendre algorithm prec = BigDecimal::Internal.coerce_validate_prec(prec, :PI) - n = prec + BigDecimal.double_fig + n = prec + BigDecimal::Internal::EXTRA_PREC a = BigDecimal(1) b = BigDecimal(0.5, 0).sqrt(n) s = BigDecimal(0.25, 0) From 38c3e82fa3231a07874be1a8b2b9024ee1328770 Mon Sep 17 00:00:00 2001 From: Yuki Kurihara Date: Mon, 16 Feb 2026 00:24:54 +0900 Subject: [PATCH 517/546] Add RBS signature and testing (#488) * Add RBS signature and testing * Split util --- .github/workflows/benchmark.yml | 3 + .github/workflows/ci.yml | 1 + .github/workflows/jruby_test.yml | 4 + .github/workflows/push_gem.yml | 3 + .github/workflows/sig.yml | 26 + Gemfile | 5 + Rakefile | 19 + bigdecimal.gemspec | 2 + sig/big_decimal.rbs | 1502 +++++++++++++++++++++++++++++ sig/big_decimal_util.rbs | 158 +++ sig/big_math.rbs | 423 ++++++++ test_sig/test_big_decimal.rb | 620 ++++++++++++ test_sig/test_big_decimal_util.rb | 87 ++ test_sig/test_big_math.rb | 439 +++++++++ test_sig/test_helper.rb | 16 + 15 files changed, 3308 insertions(+) create mode 100644 .github/workflows/sig.yml create mode 100644 sig/big_decimal.rbs create mode 100644 sig/big_decimal_util.rbs create mode 100644 sig/big_math.rbs create mode 100644 test_sig/test_big_decimal.rb create mode 100644 test_sig/test_big_decimal_util.rb create mode 100644 test_sig/test_big_math.rb create mode 100644 test_sig/test_helper.rb diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 5034c322..41a8fda5 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -42,6 +42,9 @@ jobs: - { os: windows-latest , ruby: "3.1" } - { os: windows-latest , ruby: "3.2" } + env: + BUNDLE_WITHOUT: sig + steps: - uses: actions/checkout@v6.0.2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 411d67d0..19da16bf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,7 @@ jobs: env: BIGDECIMAL_USE_DECDIG_UINT16_T: ${{ matrix.decdig_bits == 16 }} BIGDECIMAL_USE_VP_TEST_METHODS: true + BUNDLE_WITHOUT: sig steps: - uses: actions/checkout@v6.0.2 diff --git a/.github/workflows/jruby_test.yml b/.github/workflows/jruby_test.yml index ffabbe6f..a75883b2 100644 --- a/.github/workflows/jruby_test.yml +++ b/.github/workflows/jruby_test.yml @@ -14,6 +14,10 @@ jobs: host: name: ${{ matrix.ruby }} runs-on: ubuntu-latest + + env: + BUNDLE_WITHOUT: sig + strategy: fail-fast: false matrix: diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index d108387a..959f92ea 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -17,6 +17,9 @@ jobs: name: rubygems.org url: https://rubygems.org/gems/bigdecimal + env: + BUNDLE_WITHOUT: sig + permissions: contents: write id-token: write diff --git a/.github/workflows/sig.yml b/.github/workflows/sig.yml new file mode 100644 index 00000000..7897bf01 --- /dev/null +++ b/.github/workflows/sig.yml @@ -0,0 +1,26 @@ +name: sig + +on: + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + - reopened + +jobs: + sig: + runs-on: "ubuntu-latest" + env: + BUNDLE_WITH: sig + steps: + - uses: actions/checkout@v6.0.2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + ruby-version: ruby + - name: Run RBS test + run: bundle exec rake rbs:test diff --git a/Gemfile b/Gemfile index b0c551f7..b0ef9eac 100644 --- a/Gemfile +++ b/Gemfile @@ -10,3 +10,8 @@ gem "minitest", "< 5.0.0" gem "irb" gem "test-unit" gem "test-unit-ruby-core" + +group :sig do + gem "rbs" + gem "rdoc" +end diff --git a/Rakefile b/Rakefile index 314f83aa..6590a91d 100644 --- a/Rakefile +++ b/Rakefile @@ -77,3 +77,22 @@ namespace :dev do end end end + +namespace :rbs do + Rake::TestTask.new(test: :compile) do |t| + t.libs << 'test_sig' + t.ruby_opts << '-rtest_helper' + t.test_files = FileList['test_sig/test_big_*.rb'] + t.warning = true + end + + desc 'Update RBS comments' + task :annotate do + require "tmpdir" + + Dir.mktmpdir do |tmpdir| + sh("rdoc --ri --output #{tmpdir}/doc --root=. lib") + sh("rbs annotate --no-system --no-gems --no-site --no-home -d #{tmpdir}/doc sig") + end + end +end diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 774fd223..8f279141 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -34,6 +34,8 @@ Gem::Specification.new do |s| sample/linear.rb sample/nlsolve.rb sample/pi.rb + sig/big_decimal.rbs + sig/big_math.rbs ] if Gem::Platform === s.platform and s.platform =~ 'java' or RUBY_ENGINE == 'jruby' s.platform = 'java' diff --git a/sig/big_decimal.rbs b/sig/big_decimal.rbs new file mode 100644 index 00000000..a0d0e80c --- /dev/null +++ b/sig/big_decimal.rbs @@ -0,0 +1,1502 @@ +class BigDecimal < Numeric + # BigDecimal::ROUND_MODE + type round_mode = 256 + type round_mode_integer = 1 | 2 | 3 | 4 | 5 | 6 | 7 + type round_mode_symbol = :up | :down | :half_up | :half_down | :half_even | :ceiling | :floor | :truncate | :banker | :default + + # + # Internal method used to provide marshalling support. See the Marshal module. + # + def self._load: (String) -> BigDecimal + + # + # Returns the number of digits a Float object is allowed to have; the result is + # system-dependent: + # + # BigDecimal.double_fig # => 16 + # + def self.double_fig: () -> Integer + + # + # + def self.interpret_loosely: (string) -> BigDecimal + + # + # Limit the number of significant digits in newly created BigDecimal numbers to + # the specified value. Rounding is performed as necessary, as specified by + # BigDecimal.mode. + # + # A limit of 0, the default, means no upper limit. + # + # The limit specified by this method takes less priority over any limit + # specified to instance methods such as ceil, floor, truncate, or round. + # + def self.limit: (?Integer? digits) -> Integer + + # + # Returns an integer representing the mode settings for exception handling and + # rounding. + # + # These modes control exception handling: + # + # * BigDecimal::EXCEPTION_NaN. + # * BigDecimal::EXCEPTION_INFINITY. + # * BigDecimal::EXCEPTION_UNDERFLOW. + # * BigDecimal::EXCEPTION_OVERFLOW. + # * BigDecimal::EXCEPTION_ZERODIVIDE. + # * BigDecimal::EXCEPTION_ALL. + # + # Values for `setting` for exception handling: + # + # * `true`: sets the given `mode` to `true`. + # * `false`: sets the given `mode` to `false`. + # * `nil`: does not modify the mode settings. + # + # You can use method BigDecimal.save_exception_mode to temporarily change, and + # then automatically restore, exception modes. + # + # For clarity, some examples below begin by setting all exception modes to + # `false`. + # + # This mode controls the way rounding is to be performed: + # + # * BigDecimal::ROUND_MODE + # + # You can use method BigDecimal.save_rounding_mode to temporarily change, and + # then automatically restore, the rounding mode. + # + # **NaNs** + # + # Mode BigDecimal::EXCEPTION_NaN controls behavior when a BigDecimal NaN is + # created. + # + # Settings: + # + # * `false` (default): Returns `BigDecimal('NaN')`. + # * `true`: Raises FloatDomainError. + # + # Examples: + # + # BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + # BigDecimal('NaN') # => NaN + # BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) # => 2 + # BigDecimal('NaN') # Raises FloatDomainError + # + # **Infinities** + # + # Mode BigDecimal::EXCEPTION_INFINITY controls behavior when a BigDecimal + # Infinity or -Infinity is created. Settings: + # + # * `false` (default): Returns `BigDecimal('Infinity')` or + # `BigDecimal('-Infinity')`. + # * `true`: Raises FloatDomainError. + # + # Examples: + # + # BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + # BigDecimal('Infinity') # => Infinity + # BigDecimal('-Infinity') # => -Infinity + # BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) # => 1 + # BigDecimal('Infinity') # Raises FloatDomainError + # BigDecimal('-Infinity') # Raises FloatDomainError + # + # **Underflow** + # + # Mode BigDecimal::EXCEPTION_UNDERFLOW controls behavior when a BigDecimal + # underflow occurs. Settings: + # + # * `false` (default): Returns `BigDecimal('0')` or `BigDecimal('-Infinity')`. + # * `true`: Raises FloatDomainError. + # + # Examples: + # + # BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + # def flow_under + # x = BigDecimal('0.1') + # 100.times { x *= x } + # end + # flow_under # => 100 + # BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, true) # => 4 + # flow_under # Raises FloatDomainError + # + # **Overflow** + # + # Mode BigDecimal::EXCEPTION_OVERFLOW controls behavior when a BigDecimal + # overflow occurs. Settings: + # + # * `false` (default): Returns `BigDecimal('Infinity')` or + # `BigDecimal('-Infinity')`. + # * `true`: Raises FloatDomainError. + # + # Examples: + # + # BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + # def flow_over + # x = BigDecimal('10') + # 100.times { x *= x } + # end + # flow_over # => 100 + # BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true) # => 1 + # flow_over # Raises FloatDomainError + # + # **Zero Division** + # + # Mode BigDecimal::EXCEPTION_ZERODIVIDE controls behavior when a zero-division + # occurs. Settings: + # + # * `false` (default): Returns `BigDecimal('Infinity')` or + # `BigDecimal('-Infinity')`. + # * `true`: Raises FloatDomainError. + # + # Examples: + # + # BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + # one = BigDecimal('1') + # zero = BigDecimal('0') + # one / zero # => Infinity + # BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, true) # => 16 + # one / zero # Raises FloatDomainError + # + # **All Exceptions** + # + # Mode BigDecimal::EXCEPTION_ALL controls all of the above: + # + # BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + # BigDecimal.mode(BigDecimal::EXCEPTION_ALL, true) # => 23 + # + # **Rounding** + # + # Mode BigDecimal::ROUND_MODE controls the way rounding is to be performed; its + # `setting` values are: + # + # * `ROUND_UP`: Round away from zero. Aliased as `:up`. + # * `ROUND_DOWN`: Round toward zero. Aliased as `:down` and `:truncate`. + # * `ROUND_HALF_UP`: Round toward the nearest neighbor; if the neighbors are + # equidistant, round away from zero. Aliased as `:half_up` and `:default`. + # * `ROUND_HALF_DOWN`: Round toward the nearest neighbor; if the neighbors are + # equidistant, round toward zero. Aliased as `:half_down`. + # * `ROUND_HALF_EVEN` (Banker's rounding): Round toward the nearest neighbor; + # if the neighbors are equidistant, round toward the even neighbor. Aliased + # as `:half_even` and `:banker`. + # * `ROUND_CEILING`: Round toward positive infinity. Aliased as `:ceiling` and + # `:ceil`. + # * `ROUND_FLOOR`: Round toward negative infinity. Aliased as `:floor:`. + # + def self.mode: (round_mode, ?(round_mode_integer | round_mode_symbol)) -> Integer + | (Integer exception_mode, ?bool? setting) -> Integer + + # + # Execute the provided block, but preserve the exception mode + # + # BigDecimal.save_exception_mode do + # BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + # BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + # + # BigDecimal(BigDecimal('Infinity')) + # BigDecimal(BigDecimal('-Infinity')) + # BigDecimal(BigDecimal('NaN')) + # end + # + # For use with the BigDecimal::EXCEPTION_* + # + # See BigDecimal.mode + # + def self.save_exception_mode: () { (?nil) -> void } -> void + + # + # Execute the provided block, but preserve the precision limit + # + # BigDecimal.limit(100) + # puts BigDecimal.limit + # BigDecimal.save_limit do + # BigDecimal.limit(200) + # puts BigDecimal.limit + # end + # puts BigDecimal.limit + # + def self.save_limit: () { (?nil) -> void } -> void + + # + # Execute the provided block, but preserve the rounding mode + # + # BigDecimal.save_rounding_mode do + # BigDecimal.mode(BigDecimal::ROUND_MODE, :up) + # puts BigDecimal.mode(BigDecimal::ROUND_MODE) + # end + # + # For use with the BigDecimal::ROUND_* + # + # See BigDecimal.mode + # + def self.save_rounding_mode: () { (?nil) -> void } -> void + + # + # Returns the modulus from dividing by b. + # + # See BigDecimal#divmod. + # + def %: (real | BigDecimal) -> BigDecimal + + # + # + def *: (real | BigDecimal) -> BigDecimal + | (Complex) -> Complex + + # + # Returns the BigDecimal value of `self` raised to power `other`: + # + # b = BigDecimal('3.14') + # b ** 2 # => 0.98596e1 + # b ** 2.0 # => 0.98596e1 + # b ** Rational(2, 1) # => 0.98596e1 + # + # Related: BigDecimal#power. + # + def **: (real | BigDecimal) -> BigDecimal + | (Complex) -> Complex + + # + # Returns the BigDecimal sum of `self` and `value`: + # + # b = BigDecimal('111111.111') # => 0.111111111e6 + # b + 2 # => 0.111113111e6 + # b + 2.0 # => 0.111113111e6 + # b + Rational(2, 1) # => 0.111113111e6 + # b + Complex(2, 0) # => (0.111113111e6+0i) + # + # See the [Note About + # Precision](BigDecimal.html#class-BigDecimal-label-A+Note+About+Precision). + # + def +: (real | BigDecimal) -> BigDecimal + | (Complex) -> Complex + + # + # Returns `self`: + # + # +BigDecimal(5) # => 0.5e1 + # +BigDecimal(-5) # => -0.5e1 + # + def +@: () -> BigDecimal + + # + # Returns the BigDecimal difference of `self` and `value`: + # + # b = BigDecimal('333333.333') # => 0.333333333e6 + # b - 2 # => 0.333331333e6 + # b - 2.0 # => 0.333331333e6 + # b - Rational(2, 1) # => 0.333331333e6 + # b - Complex(2, 0) # => (0.333331333e6+0i) + # + # See the [Note About + # Precision](BigDecimal.html#class-BigDecimal-label-A+Note+About+Precision). + # + def -: (real | BigDecimal) -> BigDecimal + | (Complex) -> Complex + + # + # Returns the BigDecimal negation of self: + # + # b0 = BigDecimal('1.5') + # b1 = -b0 # => -0.15e1 + # b2 = -b1 # => 0.15e1 + # + def -@: () -> BigDecimal + + # + # Divide by the specified value. + # + # The result precision will be the precision of the larger operand, but its + # minimum is 2*Float::DIG. + # + # See BigDecimal#div. See BigDecimal#quo. + # + def /: (real | BigDecimal) -> BigDecimal + | (Complex) -> Complex + + # + # Returns `true` if `self` is less than `other`, `false` otherwise: + # + # b = BigDecimal('1.5') # => 0.15e1 + # b < 2 # => true + # b < 2.0 # => true + # b < Rational(2, 1) # => true + # b < 1.5 # => false + # + # Raises an exception if the comparison cannot be made. + # + def <: (real | BigDecimal) -> bool + + # + # Returns `true` if `self` is less or equal to than `other`, `false` otherwise: + # + # b = BigDecimal('1.5') # => 0.15e1 + # b <= 2 # => true + # b <= 2.0 # => true + # b <= Rational(2, 1) # => true + # b <= 1.5 # => true + # b < 1 # => false + # + # Raises an exception if the comparison cannot be made. + # + def <=: (real | BigDecimal) -> bool + + # + # The comparison operator. a <=> b is 0 if a == b, 1 if a > b, -1 if a < b. + # + def <=>: (untyped) -> Integer? + + # + # Tests for value equality; returns true if the values are equal. + # + # The == and === operators and the eql? method have the same implementation for + # BigDecimal. + # + # Values may be coerced to perform the comparison: + # + # BigDecimal('1.0') == 1.0 #=> true + # + def ==: (untyped) -> bool + + # + # Tests for value equality; returns true if the values are equal. + # + # The == and === operators and the eql? method have the same implementation for + # BigDecimal. + # + # Values may be coerced to perform the comparison: + # + # BigDecimal('1.0') == 1.0 #=> true + # + def ===: (untyped) -> bool + + # + # Returns `true` if `self` is greater than `other`, `false` otherwise: + # + # b = BigDecimal('1.5') + # b > 1 # => true + # b > 1.0 # => true + # b > Rational(1, 1) # => true + # b > 2 # => false + # + # Raises an exception if the comparison cannot be made. + # + def >: (real | BigDecimal) -> bool + + # + # Returns `true` if `self` is greater than or equal to `other`, `false` + # otherwise: + # + # b = BigDecimal('1.5') + # b >= 1 # => true + # b >= 1.0 # => true + # b >= Rational(1, 1) # => true + # b >= 1.5 # => true + # b > 2 # => false + # + # Raises an exception if the comparison cannot be made. + # + def >=: (real | BigDecimal) -> bool + + # + # Returns a string representing the marshalling of `self`. See module Marshal. + # + # inf = BigDecimal('Infinity') # => Infinity + # dumped = inf._dump # => "9:Infinity" + # BigDecimal._load(dumped) # => Infinity + # + def _dump: (?untyped) -> String + + # + # Returns the BigDecimal absolute value of `self`: + # + # BigDecimal('5').abs # => 0.5e1 + # BigDecimal('-3').abs # => 0.3e1 + # + def abs: () -> BigDecimal + + # + # Returns the BigDecimal sum of `self` and `value` with a precision of `ndigits` + # decimal digits. + # + # When `ndigits` is less than the number of significant digits in the sum, the + # sum is rounded to that number of digits, according to the current rounding + # mode; see BigDecimal.mode. + # + # Examples: + # + # # Set the rounding mode. + # BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) + # b = BigDecimal('111111.111') + # b.add(1, 0) # => 0.111112111e6 + # b.add(1, 3) # => 0.111e6 + # b.add(1, 6) # => 0.111112e6 + # b.add(1, 15) # => 0.111112111e6 + # b.add(1.0, 15) # => 0.111112111e6 + # b.add(Rational(1, 1), 15) # => 0.111112111e6 + # + def add: (real | BigDecimal value, Integer digits) -> BigDecimal + + # + # Return the smallest integer greater than or equal to the value, as a + # BigDecimal. + # + # BigDecimal('3.14159').ceil #=> 4 + # BigDecimal('-9.1').ceil #=> -9 + # + # If n is specified and positive, the fractional part of the result has no more + # than that many digits. + # + # If n is specified and negative, at least that many digits to the left of the + # decimal point will be 0 in the result. + # + # BigDecimal('3.14159').ceil(3) #=> 3.142 + # BigDecimal('13345.234').ceil(-2) #=> 13400.0 + # + def ceil: () -> Integer + | (int n) -> BigDecimal + + # + # + def clone: () -> self + + # + # The coerce method provides support for Ruby type coercion. It is not enabled + # by default. + # + # This means that binary operations like + * / or - can often be performed on a + # BigDecimal and an object of another type, if the other object can be coerced + # into a BigDecimal value. + # + # e.g. + # a = BigDecimal("1.0") + # b = a / 2.0 #=> 0.5 + # + # Note that coercing a String to a BigDecimal is not supported by default; it + # requires a special compile-time option when building Ruby. + # + def coerce: (Numeric) -> [ BigDecimal, BigDecimal ] + + # + # Divide by the specified value. + # + # digits + # : If specified and less than the number of significant digits of the result, + # the result is rounded to that number of digits, according to + # BigDecimal.mode. + # + # If digits is 0, the result is the same as for the / operator or #quo. + # + # If digits is not specified, the result is an integer, by analogy with + # Float#div; see also BigDecimal#divmod. + # + # + # See BigDecimal#/. See BigDecimal#quo. + # + # Examples: + # + # a = BigDecimal("4") + # b = BigDecimal("3") + # + # a.div(b, 3) # => 0.133e1 + # + # a.div(b, 0) # => 0.1333333333333333333e1 + # a / b # => 0.1333333333333333333e1 + # a.quo(b) # => 0.1333333333333333333e1 + # + # a.div(b) # => 1 + # + def div: (real | BigDecimal value) -> Integer + | (real | BigDecimal value, int digits) -> BigDecimal + + # + # Divides by the specified value, and returns the quotient and modulus as + # BigDecimal numbers. The quotient is rounded towards negative infinity. + # + # For example: + # + # require 'bigdecimal' + # + # a = BigDecimal("42") + # b = BigDecimal("9") + # + # q, m = a.divmod(b) + # + # c = q * b + m + # + # a == c #=> true + # + # The quotient q is (a/b).floor, and the modulus is the amount that must be + # added to q * b to get a. + # + def divmod: (real | BigDecimal) -> [ Integer, BigDecimal ] + + # + # + def dup: () -> self + + # + # Tests for value equality; returns true if the values are equal. + # + # The == and === operators and the eql? method have the same implementation for + # BigDecimal. + # + # Values may be coerced to perform the comparison: + # + # BigDecimal('1.0') == 1.0 #=> true + # + def eql?: (untyped) -> bool + + # + # Returns the exponent of the BigDecimal number, as an Integer. + # + # If the number can be represented as 0.xxxxxx*10**n where xxxxxx is a string of + # digits with no leading zeros, then n is the exponent. + # + def exponent: () -> Integer + + # + # Returns True if the value is finite (not NaN or infinite). + # + def finite?: () -> bool + + # + # Return the integer part of the number, as a BigDecimal. + # + def fix: () -> BigDecimal + + # + # Return the largest integer less than or equal to the value, as a BigDecimal. + # + # BigDecimal('3.14159').floor #=> 3 + # BigDecimal('-9.1').floor #=> -10 + # + # If n is specified and positive, the fractional part of the result has no more + # than that many digits. + # + # If n is specified and negative, at least that many digits to the left of the + # decimal point will be 0 in the result. + # + # BigDecimal('3.14159').floor(3) #=> 3.141 + # BigDecimal('13345.234').floor(-2) #=> 13300.0 + # + def floor: () -> Integer + | (int n) -> BigDecimal + + # + # Return the fractional part of the number, as a BigDecimal. + # + def frac: () -> BigDecimal + + # + # Returns the integer hash value for `self`. + # + # Two instances of BigDecimal have the same hash value if and only if they have + # equal: + # + # * Sign. + # * Fractional part. + # * Exponent. + # + def hash: () -> Integer + + # + # Returns nil, -1, or +1 depending on whether the value is finite, -Infinity, or + # +Infinity. + # + def infinite?: () -> Integer? + + # + # Returns a string representation of self. + # + # BigDecimal("1234.5678").inspect + # #=> "0.12345678e4" + # + def inspect: () -> String + + # + # Returns the modulus from dividing by b. + # + # See BigDecimal#divmod. + # + def modulo: (real | BigDecimal b) -> BigDecimal + + # + # Returns the BigDecimal product of `self` and `value` with a precision of + # `ndigits` decimal digits. + # + # When `ndigits` is less than the number of significant digits in the sum, the + # sum is rounded to that number of digits, according to the current rounding + # mode; see BigDecimal.mode. + # + # Examples: + # + # # Set the rounding mode. + # BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) + # b = BigDecimal('555555.555') + # b.mult(3, 0) # => 0.1666666665e7 + # b.mult(3, 3) # => 0.167e7 + # b.mult(3, 6) # => 0.166667e7 + # b.mult(3, 15) # => 0.1666666665e7 + # b.mult(3.0, 0) # => 0.1666666665e7 + # b.mult(Rational(3, 1), 0) # => 0.1666666665e7 + # b.mult(Complex(3, 0), 0) # => (0.1666666665e7+0.0i) + # + def mult: (real | BigDecimal value, int digits) -> BigDecimal + + # + # Returns True if the value is Not a Number. + # + def nan?: () -> bool + + # + # Returns self if the value is non-zero, nil otherwise. + # + def nonzero?: () -> self? + + # + # Returns the value raised to the power of n. + # + # Also available as the operator **. + # + def power: (real | BigDecimal n, ?int prec) -> BigDecimal + + # + # Divide by the specified value. + # + # digits + # : If specified and less than the number of significant digits of the result, + # the result is rounded to the given number of digits, according to the + # rounding mode indicated by BigDecimal.mode. + # + # If digits is 0 or omitted, the result is the same as for the / operator. + # + # + # See BigDecimal#/. See BigDecimal#div. + # + def quo: (real | BigDecimal) -> BigDecimal + | (Complex) -> Complex + + # + # Returns the remainder from dividing by the value. + # + # x.remainder(y) means x-y*(x/y).truncate + # + def remainder: (real | BigDecimal) -> BigDecimal + + # + # Round to the nearest integer (by default), returning the result as a + # BigDecimal if n is specified, or as an Integer if it isn't. + # + # BigDecimal('3.14159').round #=> 3 + # BigDecimal('8.7').round #=> 9 + # BigDecimal('-9.9').round #=> -10 + # + # BigDecimal('3.14159').round(2).class.name #=> "BigDecimal" + # BigDecimal('3.14159').round.class.name #=> "Integer" + # + # If n is specified and positive, the fractional part of the result has no more + # than that many digits. + # + # If n is specified and negative, at least that many digits to the left of the + # decimal point will be 0 in the result, and return value will be an Integer. + # + # BigDecimal('3.14159').round(3) #=> 3.142 + # BigDecimal('13345.234').round(-2) #=> 13300 + # + # The value of the optional mode argument can be used to determine how rounding + # is performed; see BigDecimal.mode. + # + def round: () -> Integer + | (int n) -> (Integer | BigDecimal) + | (int n, round_mode_integer | round_mode_symbol) -> BigDecimal + | (?int n, half: :up | :down | :even) -> BigDecimal + + # + # Returns the sign of the value. + # + # Returns a positive value if > 0, a negative value if < 0. It behaves the same + # with zeros - it returns a positive value for a positive zero (BigDecimal('0')) + # and a negative value for a negative zero (BigDecimal('-0')). + # + # The specific value returned indicates the type and sign of the BigDecimal, as + # follows: + # + # BigDecimal::SIGN_NaN + # : value is Not a Number + # + # BigDecimal::SIGN_POSITIVE_ZERO + # : value is +0 + # + # BigDecimal::SIGN_NEGATIVE_ZERO + # : value is -0 + # + # BigDecimal::SIGN_POSITIVE_INFINITE + # : value is +Infinity + # + # BigDecimal::SIGN_NEGATIVE_INFINITE + # : value is -Infinity + # + # BigDecimal::SIGN_POSITIVE_FINITE + # : value is positive + # + # BigDecimal::SIGN_NEGATIVE_FINITE + # : value is negative + # + def sign: () -> Integer + + # + # Splits a BigDecimal number into four parts, returned as an array of values. + # + # The first value represents the sign of the BigDecimal, and is -1 or 1, or 0 if + # the BigDecimal is Not a Number. + # + # The second value is a string representing the significant digits of the + # BigDecimal, with no leading zeros. + # + # The third value is the base used for arithmetic (currently always 10) as an + # Integer. + # + # The fourth value is an Integer exponent. + # + # If the BigDecimal can be represented as 0.xxxxxx*10**n, then xxxxxx is the + # string of significant digits with no leading zeros, and n is the exponent. + # + # From these values, you can translate a BigDecimal to a float as follows: + # + # sign, significant_digits, base, exponent = a.split + # f = sign * "0.#{significant_digits}".to_f * (base ** exponent) + # + # (Note that the to_f method is provided as a more convenient way to translate a + # BigDecimal to a Float.) + # + def split: () -> [ Integer, String, Integer, Integer ] + + # + # Returns the square root of the value. + # + # Result has at least prec significant digits. + # + def sqrt: (int n) -> BigDecimal + + # + # Subtract the specified value. + # + # e.g. + # c = a.sub(b,n) + # + # digits + # : If specified and less than the number of significant digits of the result, + # the result is rounded to that number of digits, according to + # BigDecimal.mode. + # + def sub: (real | BigDecimal value, int digits) -> BigDecimal + + # + # Returns a new Float object having approximately the same value as the + # BigDecimal number. Normal accuracy limits and built-in errors of binary Float + # arithmetic apply. + # + def to_f: () -> Float + + # + # Returns the value as an Integer. + # + # If the BigDecimal is infinity or NaN, raises FloatDomainError. + # + def to_i: () -> Integer + + # + # Returns the value as an Integer. + # + # If the BigDecimal is infinity or NaN, raises FloatDomainError. + # + def to_int: () -> Integer + + # + # Converts a BigDecimal to a Rational. + # + def to_r: () -> Rational + + # + # Converts the value to a string. + # + # The default format looks like 0.xxxxEnn. + # + # The optional parameter s consists of either an integer; or an optional '+' or + # ' ', followed by an optional number, followed by an optional 'E' or 'F'. + # + # If there is a '+' at the start of s, positive values are returned with a + # leading '+'. + # + # A space at the start of s returns positive values with a leading space. + # + # If s contains a number, a space is inserted after each group of that many + # digits, starting from '.' and counting outwards. + # + # If s ends with an 'E', engineering notation (0.xxxxEnn) is used. + # + # If s ends with an 'F', conventional floating point notation is used. + # + # Examples: + # + # BigDecimal('-1234567890123.45678901234567890').to_s('5F') + # #=> '-123 45678 90123.45678 90123 45678 9' + # + # BigDecimal('1234567890123.45678901234567890').to_s('+8F') + # #=> '+12345 67890123.45678901 23456789' + # + # BigDecimal('1234567890123.45678901234567890').to_s(' F') + # #=> ' 1234567890123.4567890123456789' + # + def to_s: (?String | int s) -> String + + # + # Truncate to the nearest integer (by default), returning the result as a + # BigDecimal. + # + # BigDecimal('3.14159').truncate #=> 3 + # BigDecimal('8.7').truncate #=> 8 + # BigDecimal('-9.9').truncate #=> -9 + # + # If n is specified and positive, the fractional part of the result has no more + # than that many digits. + # + # If n is specified and negative, at least that many digits to the left of the + # decimal point will be 0 in the result. + # + # BigDecimal('3.14159').truncate(3) #=> 3.141 + # BigDecimal('13345.234').truncate(-2) #=> 13300.0 + # + def truncate: () -> Integer + | (int n) -> BigDecimal + + # + # Returns True if the value is zero. + # + def zero?: () -> bool + + private + + def initialize_copy: (self) -> self + + # + # Base value used in internal calculations. On a 32 bit system, BASE is 10000, + # indicating that calculation is done in groups of 4 digits. (If it were larger, + # BASE**2 wouldn't fit in 32 bits, so you couldn't guarantee that two groups + # could always be multiplied together without overflow.) + # + BASE: Integer + + # + # Determines whether overflow, underflow or zero divide result in an exception + # being thrown. See BigDecimal.mode. + # + EXCEPTION_ALL: Integer + + # + # Determines what happens when the result of a computation is infinity. See + # BigDecimal.mode. + # + EXCEPTION_INFINITY: Integer + + # + # Determines what happens when the result of a computation is not a number + # (NaN). See BigDecimal.mode. + # + EXCEPTION_NaN: Integer + + # + # Determines what happens when the result of a computation is an overflow (a + # result too large to be represented). See BigDecimal.mode. + # + EXCEPTION_OVERFLOW: Integer + + # + # Determines what happens when the result of a computation is an underflow (a + # result too small to be represented). See BigDecimal.mode. + # + EXCEPTION_UNDERFLOW: Integer + + # + # Determines what happens when a division by zero is performed. See + # BigDecimal.mode. + # + EXCEPTION_ZERODIVIDE: Integer + + # + # Special value constants + # + INFINITY: BigDecimal + + NAN: BigDecimal + + # + # Round towards +Infinity. See BigDecimal.mode. + # + ROUND_CEILING: Integer + + # + # Indicates that values should be rounded towards zero. See BigDecimal.mode. + # + ROUND_DOWN: Integer + + # + # Round towards -Infinity. See BigDecimal.mode. + # + ROUND_FLOOR: Integer + + # + # Indicates that digits >= 6 should be rounded up, others rounded down. See + # BigDecimal.mode. + # + ROUND_HALF_DOWN: Integer + + # + # Round towards the even neighbor. See BigDecimal.mode. + # + ROUND_HALF_EVEN: Integer + + # + # Indicates that digits >= 5 should be rounded up, others rounded down. See + # BigDecimal.mode. + # + ROUND_HALF_UP: Integer + + # + # Determines what happens when a result must be rounded in order to fit in the + # appropriate number of significant digits. See BigDecimal.mode. + # + ROUND_MODE: round_mode + + # + # Indicates that values should be rounded away from zero. See BigDecimal.mode. + # + ROUND_UP: Integer + + # + # Indicates that a value is negative and finite. See BigDecimal.sign. + # + SIGN_NEGATIVE_FINITE: Integer + + # + # Indicates that a value is negative and infinite. See BigDecimal.sign. + # + SIGN_NEGATIVE_INFINITE: Integer + + # + # Indicates that a value is -0. See BigDecimal.sign. + # + SIGN_NEGATIVE_ZERO: Integer + + # + # Indicates that a value is not a number. See BigDecimal.sign. + # + SIGN_NaN: Integer + + # + # Indicates that a value is positive and finite. See BigDecimal.sign. + # + SIGN_POSITIVE_FINITE: Integer + + # + # Indicates that a value is positive and infinite. See BigDecimal.sign. + # + SIGN_POSITIVE_INFINITE: Integer + + # + # Indicates that a value is +0. See BigDecimal.sign. + # + SIGN_POSITIVE_ZERO: Integer + + # + # The version of bigdecimal library + # + VERSION: String +end + +%a{annotate:rdoc:skip} +module Kernel + private + + # + # Returns the BigDecimal converted from `value` with a precision of `ndigits` + # decimal digits. + # + # When `ndigits` is less than the number of significant digits in the value, the + # result is rounded to that number of digits, according to the current rounding + # mode; see BigDecimal.mode. + # + # When `ndigits` is 0, the number of digits to correctly represent a float + # number is determined automatically. + # + # Returns `value` converted to a BigDecimal, depending on the type of `value`: + # + # * Integer, Float, Rational, Complex, or BigDecimal: converted directly: + # + # # Integer, Complex, or BigDecimal value does not require ndigits; ignored if given. + # BigDecimal(2) # => 0.2e1 + # BigDecimal(Complex(2, 0)) # => 0.2e1 + # BigDecimal(BigDecimal(2)) # => 0.2e1 + # # Float or Rational value requires ndigits. + # BigDecimal(2.0, 0) # => 0.2e1 + # BigDecimal(Rational(2, 1), 0) # => 0.2e1 + # + # * String: converted by parsing if it contains an integer or floating-point + # literal; leading and trailing whitespace is ignored: + # + # # String does not require ndigits; ignored if given. + # BigDecimal('2') # => 0.2e1 + # BigDecimal('2.0') # => 0.2e1 + # BigDecimal('0.2e1') # => 0.2e1 + # BigDecimal(' 2.0 ') # => 0.2e1 + # + # * Other type that responds to method `:to_str`: first converted to a string, + # then converted to a BigDecimal, as above. + # + # * Other type: + # + # * Raises an exception if keyword argument `exception` is `true`. + # * Returns `nil` if keyword argument `exception` is `false`. + # + # Raises an exception if `value` evaluates to a Float and `digits` is larger + # than Float::DIG + 1. + # + def self?.BigDecimal: (real | string | BigDecimal initial, ?int digits, ?exception: bool) -> BigDecimal +end + +%a{annotate:rdoc:skip} +class Integer + # + # Performs division; for integer `numeric`, truncates the result to an integer: + # + # 4 / 3 # => 1 + # 4 / -3 # => -2 + # -4 / 3 # => -2 + # -4 / -3 # => 1 + # + # For other +numeric+, returns non-integer result: + # + # 4 / 3.0 # => 1.3333333333333333 + # 4 / Rational(3, 1) # => (4/3) + # 4 / Complex(3, 0) # => ((4/3)+0i) + # + def /: (BigDecimal) -> BigDecimal + | ... + + # + # Performs multiplication: + # + # 4 * 2 # => 8 + # 4 * -2 # => -8 + # -4 * 2 # => -8 + # 4 * 2.0 # => 8.0 + # 4 * Rational(1, 3) # => (4/3) + # 4 * Complex(2, 0) # => (8+0i) + # + def *: (BigDecimal) -> BigDecimal + | ... + + # + # Performs addition: + # + # 2 + 2 # => 4 + # -2 + 2 # => 0 + # -2 + -2 # => -4 + # 2 + 2.0 # => 4.0 + # 2 + Rational(2, 1) # => (4/1) + # 2 + Complex(2, 0) # => (4+0i) + # + def +: (BigDecimal) -> BigDecimal + | ... + + # + # Performs subtraction: + # + # 4 - 2 # => 2 + # -4 - 2 # => -6 + # -4 - -2 # => -2 + # 4 - 2.0 # => 2.0 + # 4 - Rational(2, 1) # => (2/1) + # 4 - Complex(2, 0) # => (2+0i) + # + def -: (BigDecimal) -> BigDecimal + | ... +end + +%a{annotate:rdoc:skip} +class Float + # + # Returns a new Float which is the result of dividing `self` by `other`: + # + # f = 3.14 + # f / 2 # => 1.57 + # f / 2.0 # => 1.57 + # f / Rational(2, 1) # => 1.57 + # f / Complex(2, 0) # => (1.57+0.0i) + # + def /: (BigDecimal) -> BigDecimal + | ... + + # + # Returns a new Float which is the product of `self` and `other`: + # + # f = 3.14 + # f * 2 # => 6.28 + # f * 2.0 # => 6.28 + # f * Rational(1, 2) # => 1.57 + # f * Complex(2, 0) # => (6.28+0.0i) + # + def *: (BigDecimal) -> BigDecimal + | ... + + # + # Returns a new Float which is the sum of `self` and `other`: + # + # f = 3.14 + # f + 1 # => 4.140000000000001 + # f + 1.0 # => 4.140000000000001 + # f + Rational(1, 1) # => 4.140000000000001 + # f + Complex(1, 0) # => (4.140000000000001+0i) + # + def +: (BigDecimal) -> BigDecimal + | ... + + # + # Returns a new Float which is the difference of `self` and `other`: + # + # f = 3.14 + # f - 1 # => 2.14 + # f - 1.0 # => 2.14 + # f - Rational(1, 1) # => 2.14 + # f - Complex(1, 0) # => (2.14+0i) + # + def -: (BigDecimal) -> BigDecimal + | ... +end + +%a{annotate:rdoc:skip} +class Rational + # + # Performs division. + # + # Rational(2, 3) / Rational(2, 3) #=> (1/1) + # Rational(900) / Rational(1) #=> (900/1) + # Rational(-2, 9) / Rational(-9, 2) #=> (4/81) + # Rational(9, 8) / 4 #=> (9/32) + # Rational(20, 9) / 9.8 #=> 0.22675736961451246 + # + def /: (BigDecimal) -> BigDecimal + | ... + + # + # Performs multiplication. + # + # Rational(2, 3) * Rational(2, 3) #=> (4/9) + # Rational(900) * Rational(1) #=> (900/1) + # Rational(-2, 9) * Rational(-9, 2) #=> (1/1) + # Rational(9, 8) * 4 #=> (9/2) + # Rational(20, 9) * 9.8 #=> 21.77777777777778 + # + def *: (BigDecimal) -> BigDecimal + | ... + + # + # Performs addition. + # + # Rational(2, 3) + Rational(2, 3) #=> (4/3) + # Rational(900) + Rational(1) #=> (901/1) + # Rational(-2, 9) + Rational(-9, 2) #=> (-85/18) + # Rational(9, 8) + 4 #=> (41/8) + # Rational(20, 9) + 9.8 #=> 12.022222222222222 + # + def +: (BigDecimal) -> BigDecimal + | ... + + # + # Performs subtraction. + # + # Rational(2, 3) - Rational(2, 3) #=> (0/1) + # Rational(900) - Rational(1) #=> (899/1) + # Rational(-2, 9) - Rational(-9, 2) #=> (77/18) + # Rational(9, 8) - 4 #=> (-23/8) + # Rational(20, 9) - 9.8 #=> -7.577777777777778 + # + def -: (BigDecimal) -> BigDecimal + | ... +end + +%a{annotate:rdoc:skip} +class Complex + # + # Returns the quotient of `self` and `numeric`: + # + # Complex.rect(2, 3) / Complex.rect(2, 3) # => (1+0i) + # Complex.rect(900) / Complex.rect(1) # => (900+0i) + # Complex.rect(-2, 9) / Complex.rect(-9, 2) # => ((36/85)-(77/85)*i) + # Complex.rect(9, 8) / 4 # => ((9/4)+2i) + # Complex.rect(20, 9) / 9.8 # => (2.0408163265306123+0.9183673469387754i) + # + def /: (BigDecimal) -> Complex + | ... + + # + # Returns the product of `self` and `numeric`: + # + # Complex.rect(2, 3) * Complex.rect(2, 3) # => (-5+12i) + # Complex.rect(900) * Complex.rect(1) # => (900+0i) + # Complex.rect(-2, 9) * Complex.rect(-9, 2) # => (0-85i) + # Complex.rect(9, 8) * 4 # => (36+32i) + # Complex.rect(20, 9) * 9.8 # => (196.0+88.2i) + # + def *: (BigDecimal) -> Complex + | ... + + # + # Returns the sum of `self` and `numeric`: + # + # Complex.rect(2, 3) + Complex.rect(2, 3) # => (4+6i) + # Complex.rect(900) + Complex.rect(1) # => (901+0i) + # Complex.rect(-2, 9) + Complex.rect(-9, 2) # => (-11+11i) + # Complex.rect(9, 8) + 4 # => (13+8i) + # Complex.rect(20, 9) + 9.8 # => (29.8+9i) + # + def +: (BigDecimal) -> Complex + | ... + + # + # Returns the difference of `self` and `numeric`: + # + # Complex.rect(2, 3) - Complex.rect(2, 3) # => (0+0i) + # Complex.rect(900) - Complex.rect(1) # => (899+0i) + # Complex.rect(-2, 9) - Complex.rect(-9, 2) # => (7+7i) + # Complex.rect(9, 8) - 4 # => (5+8i) + # Complex.rect(20, 9) - 9.8 # => (10.2+9i) + # + def -: (BigDecimal) -> Complex + | ... +end diff --git a/sig/big_decimal_util.rbs b/sig/big_decimal_util.rbs new file mode 100644 index 00000000..4c5bfce2 --- /dev/null +++ b/sig/big_decimal_util.rbs @@ -0,0 +1,158 @@ +%a{annotate:rdoc:skip} +class Integer + # + # Returns the value of `int` as a BigDecimal. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # 42.to_d # => 0.42e2 + # + # See also Kernel.BigDecimal. + # + def to_d: () -> BigDecimal +end + +%a{annotate:rdoc:skip} +class Float + # + # Returns the value of `float` as a BigDecimal. The `precision` parameter is + # used to determine the number of significant digits for the result. When + # `precision` is set to `0`, the number of digits to represent the float being + # converted is determined automatically. The default `precision` is `0`. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # 0.5.to_d # => 0.5e0 + # 1.234.to_d # => 0.1234e1 + # 1.234.to_d(2) # => 0.12e1 + # + # See also Kernel.BigDecimal. + # + def to_d: (?Integer precision) -> BigDecimal +end + +%a{annotate:rdoc:skip} +class String + # + # Returns the result of interpreting leading characters in `str` as a + # BigDecimal. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # "0.5".to_d # => 0.5e0 + # "123.45e1".to_d # => 0.12345e4 + # "45.67 degrees".to_d # => 0.4567e2 + # + # See also Kernel.BigDecimal. + # + def to_d: () -> BigDecimal +end + +%a{annotate:rdoc:skip} +class BigDecimal + # + # Converts a BigDecimal to a String of the form "nnnnnn.mmm". This method is + # deprecated; use BigDecimal#to_s("F") instead. + # + # require 'bigdecimal/util' + # + # d = BigDecimal("3.14") + # d.to_digits # => "3.14" + # + def to_digits: () -> String + + # + # Returns self. + # + # require 'bigdecimal/util' + # + # d = BigDecimal("3.14") + # d.to_d # => 0.314e1 + # + def to_d: () -> BigDecimal +end + +%a{annotate:rdoc:skip} +class Rational + # + # Returns the value as a BigDecimal. + # + # The `precision` parameter is used to determine the number of significant + # digits for the result. When `precision` is set to `0`, the number of digits to + # represent the float being converted is determined automatically. The default + # `precision` is `0`. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # Rational(22, 7).to_d(3) # => 0.314e1 + # + # See also Kernel.BigDecimal. + # + def to_d: (Integer precision) -> BigDecimal +end + +%a{annotate:rdoc:skip} +class Complex + # + # Returns the value as a BigDecimal. If the imaginary part is not `0`, an error + # is raised + # + # The `precision` parameter is used to determine the number of significant + # digits for the result. When `precision` is set to `0`, the number of digits to + # represent the float being converted is determined automatically. The default + # `precision` is `0`. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # Complex(0.1234567, 0).to_d(4) # => 0.1235e0 + # Complex(Rational(22, 7), 0).to_d(3) # => 0.314e1 + # Complex(1, 1).to_d # raises ArgumentError + # + # See also Kernel.BigDecimal. + # + def to_d: (*untyped args) -> BigDecimal +end + +%a{annotate:rdoc:skip} +class NilClass + # + # Returns nil represented as a BigDecimal. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # nil.to_d # => 0.0 + # + def to_d: () -> BigDecimal +end diff --git a/sig/big_math.rbs b/sig/big_math.rbs new file mode 100644 index 00000000..6d9fc48c --- /dev/null +++ b/sig/big_math.rbs @@ -0,0 +1,423 @@ +# +# Core BigMath methods for BigDecimal (log, exp) are defined here. Other methods +# (sin, cos, atan) are defined in 'bigdecimal/math.rb'. +# +# +# Provides mathematical functions. +# +# Example: +# +# require "bigdecimal/math" +# +# include BigMath +# +# a = BigDecimal((PI(49)/2).to_s) +# puts sin(a,100) # => 0.9999999999...9999999986e0 +# +module BigMath + # + # Computes e (the base of natural logarithms) to the specified number of digits + # of precision, `numeric`. + # + # BigMath.E(32).to_s + # #=> "0.27182818284590452353602874713527e1" + # + def self?.E: (int prec) -> BigDecimal + + # + # Computes the value of pi to the specified number of digits of precision, + # `numeric`. + # + # BigMath.PI(32).to_s + # #=> "0.31415926535897932384626433832795e1" + # + def self?.PI: (int prec) -> BigDecimal + + # + # Computes the arccosine of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.acos(BigDecimal('0.5'), 32).to_s + # #=> "0.10471975511965977461542144610932e1" + # + def self?.acos: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the inverse hyperbolic cosine of `decimal` to the specified number of + # digits of precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.acosh(BigDecimal('2'), 32).to_s + # #=> "0.1316957896924816708625046347308e1" + # + def self?.acosh: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the arcsine of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.asin(BigDecimal('0.5'), 32).to_s + # #=> "0.52359877559829887307710723054658e0" + # + def self?.asin: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the inverse hyperbolic sine of `decimal` to the specified number of + # digits of precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.asinh(BigDecimal('1'), 32).to_s + # #=> "0.88137358701954302523260932497979e0" + # + def self?.asinh: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the arctangent of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.atan(BigDecimal('-1'), 32).to_s + # #=> "-0.78539816339744830961566084581988e0" + # + def self?.atan: (real | BigDecimal x, int prec) -> BigDecimal + + # + # Computes the arctangent of y and x to the specified number of digits of + # precision, `numeric`. + # + # BigMath.atan2(BigDecimal('-1'), BigDecimal('1'), 32).to_s + # #=> "-0.78539816339744830961566084581988e0" + # + def self?.atan2: (real | BigDecimal, real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the inverse hyperbolic tangent of `decimal` to the specified number + # of digits of precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.atanh(BigDecimal('0.5'), 32).to_s + # #=> "0.54930614433405484569762261846126e0" + # + def self?.atanh: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the cube root of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # BigMath.cbrt(BigDecimal('2'), 32).to_s + # #=> "0.12599210498948731647672106072782e1" + # + def self?.cbrt: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the cosine of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # If `decimal` is Infinity or NaN, returns NaN. + # + # BigMath.cos(BigMath.PI(16), 32).to_s + # #=> "-0.99999999999999999999999999999997e0" + # + def self?.cos: (real | BigDecimal x, int prec) -> BigDecimal + + # + # Computes the hyperbolic cosine of `decimal` to the specified number of digits + # of precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.cosh(BigDecimal('1'), 32).to_s + # #=> "0.15430806348152437784779056207571e1" + # + def self?.cosh: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the error function of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.erf(BigDecimal('1'), 32).to_s + # #=> "0.84270079294971486934122063508261e0" + # + def self?.erf: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the complementary error function of `decimal` to the specified number + # of digits of precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.erfc(BigDecimal('10'), 32).to_s + # #=> "0.20884875837625447570007862949578e-44" + # + def self?.erfc: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the value of e (the base of natural logarithms) raised to the power + # of `decimal`, to the specified number of digits of precision. + # + # If `decimal` is infinity, returns Infinity. + # + # If `decimal` is NaN, returns NaN. + # + def self?.exp: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes exp(decimal) - 1 to the specified number of digits of precision, + # `numeric`. + # + # BigMath.expm1(BigDecimal('0.1'), 32).to_s + # #=> "0.10517091807564762481170782649025e0" + # + def self?.expm1: (real | BigDecimal, int prec) -> BigDecimal + + # + # Decomposes `x` into a normalized fraction and an integral power of ten. + # + # BigMath.frexp(BigDecimal(123.456)) + # #=> [0.123456e0, 3] + # + def self?.frexp: (real | BigDecimal x) -> [ BigDecimal, Integer ] + + # + # Computes the gamma function of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # BigMath.gamma(BigDecimal('0.5'), 32).to_s + # #=> "0.17724538509055160272981674833411e1" + # + def self?.gamma: (real | BigDecimal, int prec) -> BigDecimal + + # + # Returns sqrt(x**2 + y**2) to the specified number of digits of precision, + # `numeric`. + # + # BigMath.hypot(BigDecimal('1'), BigDecimal('2'), 32).to_s + # #=> "0.22360679774997896964091736687313e1" + # + def self?.hypot: (real | BigDecimal, real | BigDecimal, int prec) -> BigDecimal + + # + # Inverse of `frexp`. Returns the value of fraction * 10**exponent. + # + # BigMath.ldexp(BigDecimal("0.123456e0"), 3) + # #=> 0.123456e3 + # + def self?.ldexp: (real | BigDecimal fraction, Integer exponent) -> BigDecimal + + # + # Computes the natural logarithm of the absolute value of the gamma function of + # `decimal` to the specified number of digits of precision, `numeric` and its + # sign. + # + # BigMath.lgamma(BigDecimal('0.5'), 32) + # #=> [0.57236494292470008707171367567653e0, 1] + # + def self?.lgamma: (real | BigDecimal, int prec) -> [ BigDecimal, Integer ] + + # + # Computes the natural logarithm of `decimal` to the specified number of digits + # of precision, `numeric`. + # + # If `decimal` is zero or negative, raises Math::DomainError. + # + # If `decimal` is positive infinity, returns Infinity. + # + # If `decimal` is NaN, returns NaN. + # + def self?.log: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the base 10 logarithm of `decimal` to the specified number of digits + # of precision, `numeric`. + # + # If `decimal` is zero or negative, raises Math::DomainError. + # + # If `decimal` is positive infinity, returns Infinity. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.log10(BigDecimal('3'), 32).to_s + # #=> "0.47712125471966243729502790325512e0" + # + def self?.log10: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes log(1 + decimal) to the specified number of digits of precision, + # `numeric`. + # + # BigMath.log1p(BigDecimal('0.1'), 32).to_s + # #=> "0.95310179804324860043952123280765e-1" + # + def self?.log1p: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the base 2 logarithm of `decimal` to the specified number of digits + # of precision, `numeric`. + # + # If `decimal` is zero or negative, raises Math::DomainError. + # + # If `decimal` is positive infinity, returns Infinity. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.log2(BigDecimal('3'), 32).to_s + # #=> "0.15849625007211561814537389439478e1" + # + def self?.log2: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the sine of `decimal` to the specified number of digits of precision, + # `numeric`. + # + # If `decimal` is Infinity or NaN, returns NaN. + # + # BigMath.sin(BigMath.PI(5)/4, 32).to_s + # #=> "0.70710807985947359435812921837984e0" + # + def self?.sin: (real | BigDecimal x, int prec) -> BigDecimal + + # + # Computes the hyperbolic sine of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.sinh(BigDecimal('1'), 32).to_s + # #=> "0.11752011936438014568823818505956e1" + # + def self?.sinh: (real | BigDecimal, int prec) -> BigDecimal + + # + # Computes the square root of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # BigMath.sqrt(BigDecimal('2'), 32).to_s + # #=> "0.14142135623730950488016887242097e1" + # + def self?.sqrt: (real | BigDecimal x, int prec) -> BigDecimal + + # + # Computes the tangent of `decimal` to the specified number of digits of + # precision, `numeric`. + # + # If `decimal` is Infinity or NaN, returns NaN. + # + # BigMath.tan(BigDecimal("0.0"), 4).to_s + # #=> "0.0" + # + # BigMath.tan(BigMath.PI(24) / 4, 32).to_s + # #=> "0.99999999999999999999999830836025e0" + # + def self?.tan: (real | BigDecimal x, int prec) -> BigDecimal + + # + # Computes the hyperbolic tangent of `decimal` to the specified number of digits + # of precision, `numeric`. + # + # If `decimal` is NaN, returns NaN. + # + # BigMath.tanh(BigDecimal('1'), 32).to_s + # #=> "0.76159415595576488811945828260479e0" + # + def self?.tanh: (real | BigDecimal, int prec) -> BigDecimal +end diff --git a/test_sig/test_big_decimal.rb b/test_sig/test_big_decimal.rb new file mode 100644 index 00000000..fba7d5a1 --- /dev/null +++ b/test_sig/test_big_decimal.rb @@ -0,0 +1,620 @@ +require "bigdecimal" +require 'test/unit' +require 'rbs/unit_test' +require_relative './test_helper' + +class BigDecimalSingletonTest < Test::Unit::TestCase + include TestHelper + library "bigdecimal" + testing "singleton(::BigDecimal)" + + def test__load + assert_send_type "(::String) -> ::BigDecimal", + BigDecimal, :_load, "18:0.123e1" + end + + def test_double_fig + assert_send_type "() -> ::Integer", + BigDecimal, :double_fig + end + + def test_interpret_loosely + assert_send_type "(String) -> BigDecimal", + BigDecimal, :interpret_loosely, "1.23" + assert_send_type "(_ToStr) -> BigDecimal", + BigDecimal, :interpret_loosely, ToStr.new("1.23") + end + + def test_limit + BigDecimal.save_limit do + assert_send_type "() -> ::Integer", + BigDecimal, :limit + assert_send_type "(nil) -> ::Integer", + BigDecimal, :limit, nil + assert_send_type "(::Integer digits) -> ::Integer", + BigDecimal, :limit, 5 + end + end + + def test_mode + BigDecimal.save_exception_mode do + assert_send_type "(::Integer mode) -> ::Integer", + BigDecimal, :mode, BigDecimal::EXCEPTION_ALL + assert_send_type "(::Integer mode, true setting) -> ::Integer", + BigDecimal, :mode, BigDecimal::EXCEPTION_ALL, true + assert_send_type "(::Integer mode, false setting) -> ::Integer", + BigDecimal, :mode, BigDecimal::EXCEPTION_ALL, false + assert_send_type "(::Integer mode, nil setting) -> ::Integer", + BigDecimal, :mode, BigDecimal::EXCEPTION_ALL, nil + end + + BigDecimal.save_rounding_mode do + assert_send_type "(::Integer mode) -> ::Integer", + BigDecimal, :mode, BigDecimal::ROUND_MODE + assert_send_type "(::Integer mode, ::Integer setting) -> ::Integer", + BigDecimal, :mode, BigDecimal::ROUND_MODE, BigDecimal::ROUND_DOWN + assert_send_type "(::Integer mode, ::Symbol setting) -> ::Integer", + BigDecimal, :mode, BigDecimal::ROUND_MODE, :half_up + end + end + + # FIXME: 実装がブロックにnilを渡すため、型定義の() -> voidと一致しない + def test_save_exception_mode + assert_send_type "() { (nil) -> void } -> void", + BigDecimal, :save_exception_mode do end + end + + def test_save_limit + assert_send_type "() { (nil) -> void } -> void", + BigDecimal, :save_limit do end + end + + def test_save_rounding_mode + assert_send_type "() { (nil) -> void } -> void", + BigDecimal, :save_rounding_mode do end + end + + def test_kernel + assert_send_type "(::String) -> ::BigDecimal", + Kernel, :BigDecimal, "1.23" + assert_send_type "(::_ToStr) -> ::BigDecimal", + Kernel, :BigDecimal, ToStr.new("1.23") + assert_send_type "(::Integer) -> ::BigDecimal", + Kernel, :BigDecimal, 123 + assert_send_type "(::BigDecimal) -> ::BigDecimal", + Kernel, :BigDecimal, BigDecimal("1.23") + assert_send_type "(::Float, ::Integer) -> ::BigDecimal", + Kernel, :BigDecimal, 1.23, 1 + assert_send_type "(::Float, ::_ToInt) -> ::BigDecimal", + Kernel, :BigDecimal, 1.23, ToInt.new(1) + assert_send_type "(::Rational, ::Integer) -> ::BigDecimal", + Kernel, :BigDecimal, Rational(1.23), 1 + assert_send_type "(::String, exception: bool) -> ::BigDecimal", + Kernel, :BigDecimal, "1.23", exception: false + assert_send_type "(::Float, ::Integer, exception: bool) -> ::BigDecimal", + Kernel, :BigDecimal, 1.23, 1, exception: true + end + + def test_constants + assert_const_type 'Integer', 'BigDecimal::BASE' + assert_const_type 'Integer', 'BigDecimal::EXCEPTION_ALL' + assert_const_type 'Integer', 'BigDecimal::EXCEPTION_INFINITY' + assert_const_type 'Integer', 'BigDecimal::EXCEPTION_NaN' + assert_const_type 'Integer', 'BigDecimal::EXCEPTION_OVERFLOW' + assert_const_type 'Integer', 'BigDecimal::EXCEPTION_UNDERFLOW' + assert_const_type 'Integer', 'BigDecimal::EXCEPTION_ZERODIVIDE' + assert_const_type 'BigDecimal', 'BigDecimal::INFINITY' + assert_const_type 'BigDecimal', 'BigDecimal::NAN' + assert_const_type 'Integer', 'BigDecimal::ROUND_CEILING' + assert_const_type 'Integer', 'BigDecimal::ROUND_DOWN' + assert_const_type 'Integer', 'BigDecimal::ROUND_FLOOR' + assert_const_type 'Integer', 'BigDecimal::ROUND_HALF_DOWN' + assert_const_type 'Integer', 'BigDecimal::ROUND_HALF_EVEN' + assert_const_type 'Integer', 'BigDecimal::ROUND_HALF_UP' + assert_const_type 'Integer', 'BigDecimal::ROUND_MODE' + assert_const_type 'Integer', 'BigDecimal::ROUND_UP' + assert_const_type 'Integer', 'BigDecimal::SIGN_NEGATIVE_FINITE' + assert_const_type 'Integer', 'BigDecimal::SIGN_NEGATIVE_INFINITE' + assert_const_type 'Integer', 'BigDecimal::SIGN_NEGATIVE_ZERO' + assert_const_type 'Integer', 'BigDecimal::SIGN_NaN' + assert_const_type 'Integer', 'BigDecimal::SIGN_POSITIVE_FINITE' + assert_const_type 'Integer', 'BigDecimal::SIGN_POSITIVE_INFINITE' + assert_const_type 'Integer', 'BigDecimal::SIGN_POSITIVE_ZERO' + assert_const_type 'String', 'BigDecimal::VERSION' + end +end + +class BigDecimalTest < Test::Unit::TestCase + include TestHelper + library "bigdecimal" + testing "::BigDecimal" + + def test_double_equal + assert_send_type "(untyped) -> bool", + BigDecimal("1.23"), :==, BigDecimal("1.234") + end + + def test_spaceship + assert_send_type "(::Numeric) -> ::Integer?", + BigDecimal("1.23"), :<=>, BigDecimal("1.234") + end + + def test_triple_equal + assert_send_type "(untyped) -> bool", + BigDecimal("1.23"), :===, BigDecimal("1.234") + end + + def test_clone + assert_send_type "() -> BigDecimal", + BigDecimal("1.23"), :clone + end + + def test_dup + assert_send_type "() -> BigDecimal", + BigDecimal("1.23"), :dup + end + + def test_eql? + assert_send_type "(untyped) -> bool", + BigDecimal("1.23"), :eql?, BigDecimal("1.234") + end + + def test_hash + assert_send_type "() -> ::Integer", + BigDecimal("1.23"), :hash + end + + def test_inspect + assert_send_type "() -> ::String", + BigDecimal("1.23"), :inspect + end + + def test_to_s + assert_send_type "() -> ::String", + BigDecimal("1.23"), :to_s + assert_send_type "(::String s) -> ::String", + BigDecimal("1.23"), :to_s, "2F" + assert_send_type "(::int s) -> ::String", + BigDecimal("1.23"), :to_s, 2 + end + + def test_less_than + assert_send_type "(::BigDecimal) -> bool", + BigDecimal("1.23"), :<, BigDecimal("1.234") + with_real(1.234) do |real| + assert_send_type "(::real) -> bool", + BigDecimal("1.23"), :<, real + end + end + + def test_less_than_equal_to + assert_send_type "(::BigDecimal) -> bool", + BigDecimal("1.23"), :<=, BigDecimal("1.234") + with_real(1.234) do |real| + assert_send_type "(::real) -> bool", + BigDecimal("1.23"), :<=, real + end + end + + def test_greater_than + assert_send_type "(::BigDecimal) -> bool", + BigDecimal("1.23"), :>, BigDecimal("1.234") + with_real(1.234) do |real| + assert_send_type "(::real) -> bool", + BigDecimal("1.23"), :>, real + end + end + + def test_greater_than_equal_to + assert_send_type "(::BigDecimal) -> bool", + BigDecimal("1.23"), :>=, BigDecimal("1.234") + with_real(1.234) do |real| + assert_send_type "(::real) -> bool", + BigDecimal("1.23"), :>=, real + end + end + + def test_modulus + assert_send_type "(::BigDecimal) -> ::BigDecimal", + BigDecimal("1.23"), :%, BigDecimal("1.234") + with_real(1.234) do |real| + assert_send_type "(::real) -> ::BigDecimal", + BigDecimal("1.23"), :%, real + end + end + + def test_multiply + assert_send_type "(::BigDecimal) -> ::BigDecimal", + BigDecimal("2"), :*, BigDecimal("3") + with_real(3) do |real| + assert_send_type "(::real) -> ::BigDecimal", + BigDecimal("2"), :*, real + end + assert_send_type "(::Complex) -> ::Complex", + BigDecimal("2"), :*, Complex(1, 2) + end + + def test_starstar + assert_send_type "(::BigDecimal) -> ::BigDecimal", + BigDecimal("2"), :**, BigDecimal("3") + with_real(3) do |real| + assert_send_type "(::real) -> ::BigDecimal", + BigDecimal("2"), :**, real + end + assert_send_type "(::Complex) -> ::Complex", + BigDecimal("2"), :**, Complex(1, 0) + end + + def test_power + with_real(2) do |real| + assert_send_type "(::real) -> ::BigDecimal", + BigDecimal("1.23"), :power, real + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigDecimal("1.23"), :power, real, 0 + end + assert_send_type "(::BigDecimal) -> ::BigDecimal", + BigDecimal("1.23"), :power, BigDecimal("2") + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigDecimal("1.23"), :power, BigDecimal("1.23"), 0 + end + + def test_plus + assert_send_type "(::BigDecimal) -> ::BigDecimal", + BigDecimal("1"), :+, BigDecimal("2") + with_real(2) do |real| + assert_send_type "(::real) -> ::BigDecimal", + BigDecimal("1"), :+, real + end + assert_send_type "(::Complex) -> ::Complex", + BigDecimal("1"), :+, Complex(2, 0) + end + + def test_minus + assert_send_type "(::BigDecimal) -> ::BigDecimal", + BigDecimal("3"), :-, BigDecimal("1") + with_real(1) do |real| + assert_send_type "(::real) -> ::BigDecimal", + BigDecimal("3"), :-, real + end + assert_send_type "(::Complex) -> ::Complex", + BigDecimal("3"), :-, Complex(1, 0) + end + + def test_divide + assert_send_type "(::BigDecimal) -> ::BigDecimal", + BigDecimal("6"), :/, BigDecimal("2") + with_real(2) do |real| + assert_send_type "(::real) -> ::BigDecimal", + BigDecimal("6"), :/, real + end + assert_send_type "(::Complex) -> ::Complex", + BigDecimal("6"), :/, Complex(2, 0) + end + + def test_unary_plus + assert_send_type "() -> ::BigDecimal", + BigDecimal("1.23"), :+@ + end + + def test_unary_minus + assert_send_type "() -> ::BigDecimal", + BigDecimal("1.23"), :-@ + end + + def test_abs + assert_send_type "() -> ::BigDecimal", + BigDecimal("1.23"), :abs + end + + def test_ceil + assert_send_type "() -> ::Integer", + BigDecimal("1.23"), :ceil + assert_send_type "(::Integer n) -> ::BigDecimal", + BigDecimal("1.23"), :ceil, 2 + end + + def test_coerce + assert_send_type "(::BigDecimal) -> [ ::BigDecimal, ::BigDecimal ]", + BigDecimal("1.23"), :coerce, BigDecimal("1.234") + end + + def test_div + assert_send_type "(::BigDecimal value) -> ::Integer", + BigDecimal("5"), :div, BigDecimal("2") + with_real(2) do |real| + assert_send_type "(::real value) -> ::Integer", + BigDecimal("5"), :div, real + end + assert_send_type "(::BigDecimal value, ::int digits) -> ::BigDecimal", + BigDecimal("1.23"), :div, BigDecimal("2"), 3 + with_real(2) do |real| + assert_send_type "(::real value, ::int digits) -> ::BigDecimal", + BigDecimal("1.23"), :div, real, 3 + end + end + + def test_divmod + assert_send_type "(::BigDecimal) -> [ ::Integer, ::BigDecimal ]", + BigDecimal("5"), :divmod, BigDecimal("2") + with_real(2) do |real| + assert_send_type "(::real) -> [ ::Integer, ::BigDecimal ]", + BigDecimal("5"), :divmod, real + end + end + + def test_finite? + assert_send_type "() -> bool", + BigDecimal("1.23"), :finite? + end + + def test_floor + assert_send_type "() -> ::Integer", + BigDecimal("1.23"), :floor + assert_send_type "(::int n) -> ::BigDecimal", + BigDecimal("1.23"), :floor, 2 + end + + def test_infinite? + assert_send_type "() -> ::Integer?", + BigDecimal("1.23"), :infinite? + end + + def test_modulo + assert_send_type "(::BigDecimal b) -> ::BigDecimal", + BigDecimal("5"), :modulo, BigDecimal("3") + with_real(3) do |real| + assert_send_type "(::real b) -> ::BigDecimal", + BigDecimal("5"), :modulo, real + end + end + + def test_nonzero? + assert_send_type "() -> BigDecimal", + BigDecimal("1.23"), :nonzero? + end + + def test_quo + assert_send_type "(::BigDecimal) -> ::BigDecimal", + BigDecimal("1.23"), :quo, BigDecimal("2") + with_real(2) do |real| + assert_send_type "(::real) -> ::BigDecimal", + BigDecimal("1.23"), :quo, real + end + assert_send_type "(::Complex) -> ::Complex", + BigDecimal("2"), :quo, Complex(1, 2) + end + + def test_remainder + assert_send_type "(::BigDecimal) -> ::BigDecimal", + BigDecimal("5"), :remainder, BigDecimal("3") + with_real(3) do |real| + assert_send_type "(::real) -> ::BigDecimal", + BigDecimal("5"), :remainder, real + end + end + + def test_round + assert_send_type "() -> ::Integer", + BigDecimal("3.14"), :round + assert_send_type "(::Integer) -> ::Integer", + BigDecimal("3.14"), :round, 0 + assert_send_type "(::Integer) -> ::BigDecimal", + BigDecimal("3.14"), :round, 1 + assert_send_type "(::Integer, ::Integer) -> ::BigDecimal", + BigDecimal("3.14"), :round, 0, BigDecimal::ROUND_UP + assert_send_type "(::Integer, :half_up) -> ::BigDecimal", + BigDecimal("3.14"), :round, 0, :half_up + assert_send_type "(half: :up) -> ::BigDecimal", + BigDecimal("3.14"), :round, half: :up + assert_send_type "(::Integer, half: :even) -> ::BigDecimal", + BigDecimal("3.14"), :round, 0, half: :even + end + + def test_to_int + assert_send_type "() -> ::Integer", + BigDecimal("1.23"), :to_int + end + + def test_truncate + assert_send_type "() -> ::Integer", + BigDecimal("1.23"), :truncate + assert_send_type "(::int n) -> ::BigDecimal", + BigDecimal("1.23"), :truncate, 2 + end + + def test_zero? + assert_send_type "() -> bool", + BigDecimal("1.23"), :zero? + end + + + def test__dump + assert_send_type "(?untyped) -> String", + BigDecimal("1.23"), :_dump + end + + def test_add + assert_send_type "(::BigDecimal value, ::Integer digits) -> ::BigDecimal", + BigDecimal("1.23"), :add, BigDecimal("1.23"), 2 + with_real(1.23) do |real| + assert_send_type "(::real value, ::Integer digits) -> ::BigDecimal", + BigDecimal("1.23"), :add, real, 2 + end + end + + def test_exponent + assert_send_type "() -> ::Integer", + BigDecimal("1.23"), :exponent + end + + def test_fix + assert_send_type "() -> ::BigDecimal", + BigDecimal("1.23"), :fix + end + + def test_frac + assert_send_type "() -> ::BigDecimal", + BigDecimal("1.23"), :frac + end + + def test_mult + assert_send_type "(::BigDecimal value, ::int digits) -> ::BigDecimal", + BigDecimal("1.23"), :mult, BigDecimal("1.23"), 2 + with_real(1.23) do |real| + assert_send_type "(::real value, ::int digits) -> ::BigDecimal", + BigDecimal("1.23"), :mult, real, 2 + end + end + + def test_nan? + assert_send_type "() -> bool", + BigDecimal("1.23"), :nan? + end + + def test_sign + assert_send_type "() -> ::Integer", + BigDecimal("1.23"), :sign + end + + def test_split + assert_send_type "() -> [ ::Integer, ::String, ::Integer, ::Integer ]", + BigDecimal("1.23"), :split + end + + def test_sqrt + assert_send_type "(::int n) -> ::BigDecimal", + BigDecimal("1.23"), :sqrt, 2 + end + + def test_sub + assert_send_type "(::BigDecimal value, ::int digits) -> ::BigDecimal", + BigDecimal("5.123"), :sub, BigDecimal("1.5"), 3 + with_real(1.5) do |real| + assert_send_type "(::real value, ::int digits) -> ::BigDecimal", + BigDecimal("5.123"), :sub, real, 3 + end + end + + def test_to_f + assert_send_type "() -> ::Float", + BigDecimal("1.23"), :to_f + end + + def test_to_i + assert_send_type "() -> ::Integer", + BigDecimal("1.23"), :to_i + end + + def test_to_r + assert_send_type "() -> ::Rational", + BigDecimal("1.23"), :to_r + end +end + +class IntegerToBigDecimalTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::Integer" + + def test_plus_with_integer + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 123, :+, BigDecimal("1.23") + end + + def test_minus_with_integer + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 123, :-, BigDecimal("1.23") + end + + def test_divide_with_integer + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 123, :/, BigDecimal("1.23") + end + + def test_multiply_with_integer + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 123, :*, BigDecimal("1.23") + end +end + +class FloatToBigDecimalTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::Float" + + def test_plus_with_float + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 1.23, :+, BigDecimal("1.23") + end + + def test_minus_with_float + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 1.23, :-, BigDecimal("1.23") + end + + def test_divide_with_float + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 1.23, :/, BigDecimal("1.23") + end + + def test_multiply_with_float + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 1.23, :*, BigDecimal("1.23") + end +end + +class RationalToBigDecimalTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::Rational" + + def test_plus_with_rational + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 123r, :+, BigDecimal("1.23") + end + + def test_minus_with_rational + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 123r, :-, BigDecimal("1.23") + end + + def test_divide_with_rational + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 123r, :/, BigDecimal("1.23") + end + + def test_multiply_with_rational + assert_send_type "(::BigDecimal) -> ::BigDecimal", + 123r, :*, BigDecimal("1.23") + end +end + +class ComplexToBigDecimalTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::Complex" + + def test_plus_with_complex + assert_send_type "(::BigDecimal) -> ::Complex", + Complex(0.1234567, 0), :+, BigDecimal("1.23") + end + + def test_minus_with_complex + assert_send_type "(::BigDecimal) -> ::Complex", + Complex(0.1234567, 0), :-, BigDecimal("1.23") + end + + def test_divide_with_complex + assert_send_type "(::BigDecimal) -> ::Complex", + Complex(0.1234567, 0), :/, BigDecimal("1.23") + end + + def test_multiply_with_complex + assert_send_type "(::BigDecimal) -> ::Complex", + Complex(0.1234567, 0), :*, BigDecimal("1.23") + end +end diff --git a/test_sig/test_big_decimal_util.rb b/test_sig/test_big_decimal_util.rb new file mode 100644 index 00000000..66e7f4ae --- /dev/null +++ b/test_sig/test_big_decimal_util.rb @@ -0,0 +1,87 @@ +require "bigdecimal" +require "bigdecimal/util" +require 'test/unit' +require 'rbs/unit_test' +require_relative './test_helper' + +class BigDecimalUtilTest < Test::Unit::TestCase + include TestHelper + library "bigdecimal" + testing "::BigDecimal" + + def test_to_digits + assert_send_type "() -> ::String", + BigDecimal("1.23"), :to_digits + end + + def test_to_d + assert_send_type "() -> ::BigDecimal", + BigDecimal("1.23"), :to_d + end +end + +class BigDecimalUtilIntegerTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::Integer" + + def test_to_d_with_integer + assert_send_type "() -> ::BigDecimal", 123, :to_d + end +end + +class BigDecimalUtilFloatTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::Float" + + def test_to_d_with_float + assert_send_type "() -> ::BigDecimal", 12.3, :to_d + end +end + +class BigDecimalUtilRationalTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::Rational" + + def test_to_d_with_rational + assert_send_type "(Integer) -> ::BigDecimal", Rational(22, 7), :to_d, 3 + end +end + +class BigDecimalUtilComplexTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::Complex" + + def test_to_d_with_complex + assert_send_type "() -> ::BigDecimal", Complex(0.1234567, 0), :to_d + end +end + +class BigDecimalUtilStringTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::String" + + def test_to_d_with_string + assert_send_type "() -> ::BigDecimal", "123", :to_d + end +end + +class BigDecimalUtilNilClassTest < Test::Unit::TestCase + include TestHelper + + library "bigdecimal" + testing "::NilClass" + + def test_to_d_with_nil + assert_send_type "() -> ::BigDecimal", nil, :to_d + end +end diff --git a/test_sig/test_big_math.rb b/test_sig/test_big_math.rb new file mode 100644 index 00000000..6bb776e2 --- /dev/null +++ b/test_sig/test_big_math.rb @@ -0,0 +1,439 @@ +require "bigdecimal" +require "bigdecimal/math" +require 'test/unit' +require 'rbs/unit_test' +require_relative './test_helper' + +class BigMathSingletonTest < Test::Unit::TestCase + include TestHelper + library "bigdecimal" + testing "singleton(::BigMath)" + + def test_E + assert_send_type "(::Integer prec) -> ::BigDecimal", + BigMath, :E, 10 + end + + def test_PI + assert_send_type "(::Integer prec) -> ::BigDecimal", + BigMath, :PI, 10 + end + + def test_acos + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :acos, BigDecimal('0.5'), 32 + with_real(0.5) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :acos, real, 32 + end + end + + def test_acosh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :acosh, BigDecimal('2'), 32 + with_real(2) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :acosh, real, 32 + end + end + + def test_asin + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :asin, BigDecimal('0.5'), 32 + with_real(0.5) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :asin, real, 32 + end + end + + def test_asinh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :asinh, BigDecimal('1'), 32 + with_real(1) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :asinh, real, 32 + end + end + + def test_atan + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + BigMath, :atan, BigDecimal('-1'), 32 + with_real(-1) do |real| + assert_send_type "(::real x, ::Integer prec) -> ::BigDecimal", + BigMath, :atan, real, 32 + end + end + + def test_atan2 + assert_send_type "(::BigDecimal, ::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :atan2, BigDecimal('-1'), BigDecimal('1'), 32 + with_real(-1) do |real_y| + with_real(1) do |real_x| + assert_send_type "(::real, ::real, ::Integer) -> ::BigDecimal", + BigMath, :atan2, real_y, real_x, 32 + end + end + end + + def test_atanh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :atanh, BigDecimal('0.5'), 32 + with_real(0.5) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :atanh, real, 32 + end + end + + def test_cbrt + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :cbrt, BigDecimal('2'), 32 + with_real(2) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :cbrt, real, 32 + end + end + + def test_cos + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + BigMath, :cos, BigMath.PI(16), 32 + with_real(1) do |real| + assert_send_type "(::real x, ::Integer prec) -> ::BigDecimal", + BigMath, :cos, real, 10 + end + end + + def test_cosh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :cosh, BigDecimal('1'), 32 + with_real(1) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :cosh, real, 32 + end + end + + def test_erf + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :erf, BigDecimal('1'), 32 + with_real(1) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :erf, real, 32 + end + end + + def test_erfc + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :erfc, BigDecimal('10'), 32 + with_real(10) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :erfc, real, 32 + end + end + + def test_exp + assert_send_type "(::BigDecimal, ::Integer prec) -> ::BigDecimal", + BigMath, :exp, BigDecimal('1'), 10 + with_real(1) do |real| + assert_send_type "(::real, ::Integer prec) -> ::BigDecimal", + BigMath, :exp, real, 10 + end + end + + def test_expm1 + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :expm1, BigDecimal('0.1'), 32 + with_real(0.1) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :expm1, real, 32 + end + end + + def test_frexp + assert_send_type "(::BigDecimal) -> [::BigDecimal, ::Integer]", + BigMath, :frexp, BigDecimal(123.456) + with_real(123.456) do |real| + assert_send_type "(::real) -> [::BigDecimal, ::Integer]", + BigMath, :frexp, real + end + end + + def test_gamma + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :gamma, BigDecimal('0.5'), 32 + with_real(2) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :gamma, real, 32 + end + end + + def test_hypot + assert_send_type "(::BigDecimal, ::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :hypot, BigDecimal('1'), BigDecimal('2'), 32 + with_real(1) do |real_x| + with_real(2) do |real_y| + assert_send_type "(::real, ::real, ::Integer) -> ::BigDecimal", + BigMath, :hypot, real_x, real_y, 32 + end + end + end + + def test_ldexp + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :ldexp, BigDecimal("0.123456e0"), 3 + with_real(0.123456) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :ldexp, real, 3 + end + end + + def test_lgamma + assert_send_type "(::BigDecimal, ::Integer) -> [::BigDecimal, ::Integer]", + BigMath, :lgamma, BigDecimal('0.5'), 32 + with_real(2) do |real| + assert_send_type "(::real, ::Integer) -> [::BigDecimal, ::Integer]", + BigMath, :lgamma, real, 32 + end + end + + def test_log + assert_send_type "(::BigDecimal, ::Integer prec) -> ::BigDecimal", + BigMath, :log, BigDecimal('1'), 10 + with_real(1) do |real| + assert_send_type "(::real, ::Integer prec) -> ::BigDecimal", + BigMath, :log, real, 10 + end + end + + def test_log10 + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :log10, BigDecimal('3'), 32 + with_real(3) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :log10, real, 32 + end + end + + def test_log1p + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :log1p, BigDecimal('0.1'), 32 + with_real(0.1) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :log1p, real, 32 + end + end + + def test_log2 + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :log2, BigDecimal('3'), 32 + with_real(3) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :log2, real, 32 + end + end + + def test_sin + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + BigMath, :sin, BigMath.PI(5) / 4, 32 + with_real(1) do |real| + assert_send_type "(::real x, ::Integer prec) -> ::BigDecimal", + BigMath, :sin, real, 10 + end + end + + def test_sinh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :sinh, BigDecimal('1'), 32 + with_real(1) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :sinh, real, 32 + end + end + + def test_sqrt + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :sqrt, BigDecimal('2'), 32 + with_real(2) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :sqrt, real, 32 + end + end + + def test_tan + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + BigMath, :tan, BigDecimal("0.0"), 4 + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + BigMath, :tan, BigMath.PI(24) / 4, 32 + with_real(1) do |real| + assert_send_type "(::real x, ::Integer prec) -> ::BigDecimal", + BigMath, :tan, real, 10 + end + end + + def test_tanh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + BigMath, :tanh, BigDecimal('1'), 32 + with_real(1) do |real| + assert_send_type "(::real, ::Integer) -> ::BigDecimal", + BigMath, :tanh, real, 32 + end + end +end + +class BigMathTest < Test::Unit::TestCase + include TestHelper + library "bigdecimal" + testing "::BigMath" + + class TestClass + include BigMath + end + + def test_E + assert_send_type "(::Integer prec) -> ::BigDecimal", + TestClass.new, :E, 10 + end + + def test_PI + assert_send_type "(::Integer prec) -> ::BigDecimal", + TestClass.new, :PI, 10 + end + + def test_acos + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :acos, BigDecimal('0.5'), 32 + end + + def test_acosh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :acosh, BigDecimal('2'), 32 + end + + def test_asin + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :asin, BigDecimal('0.5'), 32 + end + + def test_asinh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :asinh, BigDecimal('1'), 32 + end + + def test_atan + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + TestClass.new, :atan, BigDecimal('1.23'), 10 + end + + def test_atan2 + assert_send_type "(::BigDecimal, ::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :atan2, BigDecimal('-1'), BigDecimal('1'), 32 + end + + def test_atanh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :atanh, BigDecimal('0.5'), 32 + end + + def test_cbrt + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :cbrt, BigDecimal('2'), 32 + end + + def test_cos + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + TestClass.new, :cos, BigDecimal('1.23'), 10 + end + + def test_cosh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :cosh, BigDecimal('1'), 32 + end + + def test_erf + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :erf, BigDecimal('1'), 32 + end + + def test_erfc + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :erfc, BigDecimal('10'), 32 + end + + def test_exp + assert_send_type "(::BigDecimal, ::Integer prec) -> ::BigDecimal", + TestClass.new, :exp, BigDecimal('1.23'), 10 + end + + def test_expm1 + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :expm1, BigDecimal('0.1'), 32 + end + + def test_frexp + assert_send_type "(::BigDecimal) -> [::BigDecimal, ::Integer]", + TestClass.new, :frexp, BigDecimal(123.456) + end + + def test_gamma + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :gamma, BigDecimal('0.5'), 32 + end + + def test_hypot + assert_send_type "(::BigDecimal, ::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :hypot, BigDecimal('1'), BigDecimal('2'), 32 + end + + def test_ldexp + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :ldexp, BigDecimal("0.123456e0"), 3 + end + + def test_lgamma + assert_send_type "(::BigDecimal, ::Integer) -> [::BigDecimal, ::Integer]", + TestClass.new, :lgamma, BigDecimal('0.5'), 32 + end + + def test_log + assert_send_type "(::BigDecimal, ::Integer prec) -> ::BigDecimal", + TestClass.new, :log, BigDecimal('1.23'), 10 + end + + def test_log10 + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :log10, BigDecimal('3'), 32 + end + + def test_log1p + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :log1p, BigDecimal('0.1'), 32 + end + + def test_log2 + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :log2, BigDecimal('3'), 32 + end + + def test_sin + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + TestClass.new, :sin, BigDecimal('1.23'), 10 + end + + def test_sinh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :sinh, BigDecimal('1'), 32 + end + + def test_sqrt + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + TestClass.new, :sqrt, BigDecimal('1.23'), 10 + end + + def test_tan + assert_send_type "(::BigDecimal x, ::Integer prec) -> ::BigDecimal", + TestClass.new, :tan, BigDecimal("0.0"), 4 + end + + def test_tanh + assert_send_type "(::BigDecimal, ::Integer) -> ::BigDecimal", + TestClass.new, :tanh, BigDecimal('1'), 32 + end +end diff --git a/test_sig/test_helper.rb b/test_sig/test_helper.rb new file mode 100644 index 00000000..a9ce1a5c --- /dev/null +++ b/test_sig/test_helper.rb @@ -0,0 +1,16 @@ +require 'rbs/unit_test' + +module TestHelper + include RBS::UnitTest::TypeAssertions + include RBS::UnitTest::Convertibles + + def self.included(base) + base.extend RBS::UnitTest::TypeAssertions::ClassMethods + end + + def with_real(n) + yield n.to_i + yield n.to_f + yield n.to_r + end +end From 99db373ec43b0c1aa5bf7a37227f977c62c676d2 Mon Sep 17 00:00:00 2001 From: Yuki Kurihara Date: Thu, 5 Mar 2026 00:49:18 +0900 Subject: [PATCH 518/546] Add missing sig file (#492) --- bigdecimal.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 8f279141..114b6b3a 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -34,6 +34,7 @@ Gem::Specification.new do |s| sample/linear.rb sample/nlsolve.rb sample/pi.rb + sig/big_decimal_util.rbs sig/big_decimal.rbs sig/big_math.rbs ] From 0c8adcf3a50ac0b655d5fe4348daac86b5f0b783 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 12:50:19 +0000 Subject: [PATCH 519/546] Bump step-security/harden-runner from 2.14.1 to 2.15.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.14.1 to 2.15.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/e3f713f2d8f53843e71c69a996d56f51aa9adfb9...58077d3c7e43986b6b15fba718e8ea69e387dfcc) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.15.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 959f92ea..bb49aa28 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit From acb0f7e8fe3fed0ea0de0022ea1be4b2eeae3103 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 12:22:30 +0000 Subject: [PATCH 520/546] Bump rubygems/release-gem from 1.1.2 to 1.1.4 Bumps [rubygems/release-gem](https://github.com/rubygems/release-gem) from 1.1.2 to 1.1.4. - [Release notes](https://github.com/rubygems/release-gem/releases) - [Commits](https://github.com/rubygems/release-gem/compare/1c162a739e8b4cb21a676e97b087e8268d8fc40b...e9a6361a0b14562539327c2a02373edc56dd3169) --- updated-dependencies: - dependency-name: rubygems/release-gem dependency-version: 1.1.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index bb49aa28..ca5e9162 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -43,7 +43,7 @@ jobs: ruby-version: ${{ matrix.ruby }} - name: Publish to RubyGems - uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2 + uses: rubygems/release-gem@e9a6361a0b14562539327c2a02373edc56dd3169 # v1.1.4 - name: Create GitHub release run: | From 2306d71c3c75af50b8e7a261158799c080eeef67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 12:22:35 +0000 Subject: [PATCH 521/546] Bump step-security/harden-runner from 2.15.1 to 2.16.0 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.15.1 to 2.16.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/58077d3c7e43986b6b15fba718e8ea69e387dfcc...fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.16.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index bb49aa28..88babdc9 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit From af72ebda2b6828022011f0f4bd6ed061e575951b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 19 Mar 2026 03:39:07 +0900 Subject: [PATCH 522/546] Simplify butterfly operation of Number Theoretic Transform (#496) --- ext/bigdecimal/ntt.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/ntt.h b/ext/bigdecimal/ntt.h index 941f23f7..86fed3cb 100644 --- a/ext/bigdecimal/ntt.h +++ b/ext/bigdecimal/ntt.h @@ -40,13 +40,15 @@ ntt_recursive(int size_bits, uint32_t *input, uint32_t *output, uint32_t *tmp, i uint32_t stride = (uint32_t)1 << (size_bits - depth - 1); uint32_t n = size_half / stride; uint32_t rn = 1, rm = prime - 1; - uint32_t idx = 0; for (uint32_t i = 0; i < n; i++) { - uint32_t j = i * 2 * stride; - for (uint32_t k = 0; k < stride; k++, j++, idx++) { - uint32_t a = tmp[j], b = tmp[j + stride]; - output[idx] = (a + (uint64_t)rn * b) % prime; - output[idx + size_half] = (a + (uint64_t)rm * b) % prime; + uint32_t *aptr = tmp + i * 2 * stride; + uint32_t *bptr = aptr + stride; + uint32_t *out1 = output + stride * i; + uint32_t *out2 = out1 + size_half; + for (uint32_t k = 0; k < stride; k++) { + uint32_t a = aptr[k], b = bptr[k]; + out1[k] = (a + (uint64_t)rn * b) % prime; + out2[k] = (a + (uint64_t)rm * b) % prime; } rn = ((uint64_t)rn * r) % prime; rm = ((uint64_t)rm * r) % prime; From fa02252eda85a9e8a5070ce9bcc9db1fac8cd96f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 20 Mar 2026 01:42:13 +0900 Subject: [PATCH 523/546] Remove DECDIG=uint16_t branch. BigDecimal already requires uint64_t from v3.1.0 (#497) --- .github/workflows/ci.yml | 7 +--- ext/bigdecimal/bigdecimal.c | 14 +------- ext/bigdecimal/bigdecimal.h | 47 ++++++------------------- ext/bigdecimal/extconf.rb | 1 - test/bigdecimal/helper.rb | 9 +---- test/bigdecimal/test_bigdecimal.rb | 9 ++--- test/bigdecimal/test_bigdecimal_util.rb | 9 ++--- test/bigdecimal/test_vp_operation.rb | 26 ++++---------- 8 files changed, 24 insertions(+), 98 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19da16bf..67470eea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: host: needs: ruby-versions - name: ${{ matrix.os }} ${{ matrix.ruby }} decdig-${{ matrix.decdig_bits }}bit + name: ${{ matrix.os }} ${{ matrix.ruby }} runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -31,19 +31,14 @@ jobs: - macos-14 - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} - decdig_bits: [32] include: - - { os: ubuntu-latest, ruby: "3.4", decdig_bits: 16 } - { os: windows-latest , ruby: mingw } - { os: windows-latest , ruby: mswin } exclude: - - { os: macos-latest , ruby: "2.5" } - - { os: macos-14 , ruby: "2.5" } - { os: windows-latest , ruby: debug } - { os: windows-latest , ruby: truffleruby } - { os: windows-latest , ruby: truffleruby-head } env: - BIGDECIMAL_USE_DECDIG_UINT16_T: ${{ matrix.decdig_bits == 16 }} BIGDECIMAL_USE_VP_TEST_METHODS: true BUNDLE_WITHOUT: sig diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index baf34fdf..3b1ebc17 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -29,18 +29,14 @@ #endif #include "bits.h" +#include "ntt.h" #include "div.h" #include "static_assert.h" #define BIGDECIMAL_VERSION "4.0.1" -#if SIZEOF_DECDIG == 4 -#define USE_NTT_MULTIPLICATION 1 -#include "ntt.h" #define NTT_MULTIPLICATION_THRESHOLD 100 #define NEWTON_RAPHSON_DIVISION_THRESHOLD 200 -#endif - #define SIGNED_VALUE_MAX INTPTR_MAX #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) @@ -3279,7 +3275,6 @@ BigDecimal_vpmult(VALUE self, VALUE v) { return c.bigdecimal; } -#if SIZEOF_DECDIG == 4 VALUE BigDecimal_nttmult(VALUE self, VALUE v) { BDVALUE a,b,c; @@ -3295,7 +3290,6 @@ BigDecimal_nttmult(VALUE self, VALUE v) { RB_GC_GUARD(b.bigdecimal); return c.bigdecimal; } -#endif #endif /* BIGDECIMAL_USE_VP_TEST_METHODS */ @@ -3670,9 +3664,7 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "vpdivd_newton", BigDecimal_vpdivd_newton, 2); rb_define_method(rb_cBigDecimal, "newton_raphson_inverse", BigDecimal_newton_raphson_inverse, 1); rb_define_method(rb_cBigDecimal, "vpmult", BigDecimal_vpmult, 1); -#ifdef USE_NTT_MULTIPLICATION rb_define_method(rb_cBigDecimal, "nttmult", BigDecimal_nttmult, 1); -#endif #endif /* BIGDECIMAL_USE_VP_TEST_METHODS */ #define ROUNDING_MODE(i, name, value) \ @@ -4956,13 +4948,11 @@ VpMult(Real *c, Real *a, Real *b) VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ if (!AddExponent(c, b->exponent)) return 0; -#ifdef USE_NTT_MULTIPLICATION if (b->Prec >= NTT_MULTIPLICATION_THRESHOLD) { ntt_multiply((uint32_t)a->Prec, (uint32_t)b->Prec, a->frac, b->frac, c->frac); c->Prec = a->Prec + b->Prec; goto Cleanup; } -#endif carry = 0; nc = ind_c = MxIndAB; @@ -5059,13 +5049,11 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) if (word_a > word_r || word_b + word_c - 2 >= word_r) goto space_error; -#ifdef USE_NTT_MULTIPLICATION // Newton-Raphson division requires multiplication to be faster than O(n^2) if (word_c >= NEWTON_RAPHSON_DIVISION_THRESHOLD && word_b >= NEWTON_RAPHSON_DIVISION_THRESHOLD) { VpDivdNewton(c, r, a, b); goto Exit; } -#endif for (i = 0; i < word_a; ++i) r->frac[i] = a->frac[i]; for (i = word_a; i < word_r; ++i) r->frac[i] = 0; diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 71ddb21f..57ae4bb0 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -17,24 +17,15 @@ # include #endif -#if defined(HAVE_INT64_T) && !defined(BIGDECIMAL_USE_DECDIG_UINT16_T) -# define DECDIG uint32_t -# define DECDIG_DBL uint64_t -# define DECDIG_DBL_SIGNED int64_t -# define SIZEOF_DECDIG 4 -# define PRI_DECDIG_PREFIX "" -# ifdef PRI_LL_PREFIX -# define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX -# else -# define PRI_DECDIG_DBL_PREFIX "l" -# endif +#define DECDIG uint32_t +#define DECDIG_DBL uint64_t +#define DECDIG_DBL_SIGNED int64_t +#define SIZEOF_DECDIG 4 +#define PRI_DECDIG_PREFIX "" +#ifdef PRI_LL_PREFIX +# define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX #else -# define DECDIG uint16_t -# define DECDIG_DBL uint32_t -# define DECDIG_DBL_SIGNED int32_t -# define SIZEOF_DECDIG 2 -# define PRI_DECDIG_PREFIX "h" -# define PRI_DECDIG_DBL_PREFIX "" +# define PRI_DECDIG_DBL_PREFIX "l" #endif #define PRIdDECDIG PRI_DECDIG_PREFIX"d" @@ -51,31 +42,15 @@ #define PRIxDECDIG_DBL PRI_DECDIG_DBL_PREFIX"x" #define PRIXDECDIG_DBL PRI_DECDIG_DBL_PREFIX"X" -#if SIZEOF_DECDIG == 4 -# define BIGDECIMAL_BASE ((DECDIG)1000000000U) -# define BIGDECIMAL_COMPONENT_FIGURES 9 +#define BIGDECIMAL_BASE ((DECDIG)1000000000U) +#define BIGDECIMAL_COMPONENT_FIGURES 9 /* * The number of components required for a 64-bit integer. * * INT64_MAX: 9_223372036_854775807 * UINT64_MAX: 18_446744073_709551615 */ -# define BIGDECIMAL_INT64_MAX_LENGTH 3 - -#elif SIZEOF_DECDIG == 2 -# define BIGDECIMAL_BASE ((DECDIG)10000U) -# define BIGDECIMAL_COMPONENT_FIGURES 4 -/* - * The number of components required for a 64-bit integer. - * - * INT64_MAX: 922_3372_0368_5477_5807 - * UINT64_MAX: 1844_6744_0737_0955_1615 - */ -# define BIGDECIMAL_INT64_MAX_LENGTH 5 - -#else -# error Unknown size of DECDIG -#endif +#define BIGDECIMAL_INT64_MAX_LENGTH 3 #define BIGDECIMAL_DOUBLE_FIGURES (1+DBL_DIG) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 47f26381..e1897e6c 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -52,7 +52,6 @@ def have_builtin_func(name, check_expr, opt = "", &b) bigdecimal_rb = "$(srcdir)/../../lib/bigdecimal.rb" end -$defs.push '-DBIGDECIMAL_USE_DECDIG_UINT16_T' if ENV['BIGDECIMAL_USE_DECDIG_UINT16_T'] == 'true' $defs.push '-DBIGDECIMAL_USE_VP_TEST_METHODS' if ENV['BIGDECIMAL_USE_VP_TEST_METHODS'] == 'true' create_makefile('bigdecimal') {|mf| diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index f624edf8..07e68cf2 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -5,14 +5,7 @@ module TestBigDecimalBase BASE = BigDecimal::BASE - case BASE - when 1000000000 - SIZEOF_DECDIG = RbConfig::SIZEOF["int32_t"] - BASE_FIG = 9 - when 10000 - SIZEOF_DECDIG = RbConfig::SIZEOF["int16_t"] - BASE_FIG = 4 - end + BASE_FIG = 9 def setup @mode = BigDecimal.mode(BigDecimal::EXCEPTION_ALL) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 03f7f2ae..0d7b310d 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -108,13 +108,8 @@ def test_BigDecimal_bug7522 def test_BigDecimal_issue_192 # https://github.com/ruby/bigdecimal/issues/192 # https://github.com/rails/rails/pull/42125 - if BASE_FIG == 9 - int = 1_000_000_000_12345_0000 - big = BigDecimal("0.100000000012345e19") - else # BASE_FIG == 4 - int = 1_0000_12_00 - big = BigDecimal("0.1000012e9") - end + int = 1_000_000_000_12345_0000 + big = BigDecimal("0.100000000012345e19") assert_equal(BigDecimal(int), big, "[ruby/bigdecimal#192]") end diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 34c46bc9..e2ead321 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -65,13 +65,8 @@ def test_Float_to_d_bug13331 def test_Float_to_d_issue_192 # https://github.com/ruby/bigdecimal/issues/192 # https://github.com/rails/rails/pull/42125 - if BASE_FIG == 9 - flo = 1_000_000_000.12345 - big = BigDecimal("0.100000000012345e10") - else # BASE_FIG == 4 - flo = 1_0000.12 - big = BigDecimal("0.1000012e5") - end + flo = 1_000_000_000.12345 + big = BigDecimal("0.100000000012345e10") assert_equal(flo.to_d, big, "[ruby/bigdecimal#192]") end diff --git a/test/bigdecimal/test_vp_operation.rb b/test/bigdecimal/test_vp_operation.rb index 3d527edb..5d0d048a 100644 --- a/test/bigdecimal/test_vp_operation.rb +++ b/test/bigdecimal/test_vp_operation.rb @@ -13,10 +13,6 @@ def setup end end - def ntt_mult_available? - BASE_FIG == 9 - end - def test_vpmult assert_equal(BigDecimal('121932631112635269'), BigDecimal('123456789').vpmult(BigDecimal('987654321'))) assert_equal(BigDecimal('12193263.1112635269'), BigDecimal('123.456789').vpmult(BigDecimal('98765.4321'))) @@ -26,7 +22,6 @@ def test_vpmult end def test_nttmult - omit 'NTT multiplication is only available for 32-bit DECDIG' unless ntt_mult_available? [*1..32].repeated_permutation(2) do |a, b| x = BigDecimal(10 ** (BASE_FIG * a) / 7) y = BigDecimal(10 ** (BASE_FIG * b) / 13) @@ -68,10 +63,8 @@ def test_not_affected_by_limit BigDecimal.limit 3 assert_equal(xy, x.vpmult(y)) assert_equal(3, BigDecimal.limit) - if ntt_mult_available? - assert_equal(xy, x.nttmult(y)) - assert_equal(3, BigDecimal.limit) - end + assert_equal(xy, x.nttmult(y)) + assert_equal(3, BigDecimal.limit) prec = (z.exponent - 1) / BASE_FIG - (y.exponent - 1) / BASE_FIG + 1 assert_equal([x, mod], z.vpdivd(y, prec)) @@ -176,16 +169,9 @@ def test_vpdivd_large_prec_divisor end def test_vpdivd_intermediate_zero - if BASE_FIG == 9 - x = BigDecimal('123456789.246913578000000000123456789') - y = BigDecimal('123456789') - assert_vpdivd_equal([BigDecimal('1.000000002000000000000000001'), BigDecimal(0)], [x, y, 4]) - assert_vpdivd_equal([BigDecimal('1.000000000049999999'), BigDecimal('1e-18')], [BigDecimal("2.000000000099999999"), 2, 3]) - else - x = BigDecimal('1234.246800001234') - y = BigDecimal('1234') - assert_vpdivd_equal([BigDecimal('1.000200000001'), BigDecimal(0)], [x, y, 4]) - assert_vpdivd_equal([BigDecimal('1.00000499'), BigDecimal('1e-8')], [BigDecimal("2.00000999"), 2, 3]) - end + x = BigDecimal('123456789.246913578000000000123456789') + y = BigDecimal('123456789') + assert_vpdivd_equal([BigDecimal('1.000000002000000000000000001'), BigDecimal(0)], [x, y, 4]) + assert_vpdivd_equal([BigDecimal('1.000000000049999999'), BigDecimal('1e-18')], [BigDecimal("2.000000000099999999"), 2, 3]) end end From 0a47ee4f14f38ffb533335ec290bc68de23c638c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 20 Mar 2026 03:19:48 +0900 Subject: [PATCH 524/546] Use bit_length to calculate NTT bit size (#498) * Use bit_length to calculate NTT bit size This will fix NTT size calculation bug (infinite loop) when multiplicand size is larger than 1<<31 * Remove outdated comment --- ext/bigdecimal/bigdecimal.c | 1 - ext/bigdecimal/ntt.h | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 3b1ebc17..2a7b2264 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -5049,7 +5049,6 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) if (word_a > word_r || word_b + word_c - 2 >= word_r) goto space_error; - // Newton-Raphson division requires multiplication to be faster than O(n^2) if (word_c >= NEWTON_RAPHSON_DIVISION_THRESHOLD && word_b >= NEWTON_RAPHSON_DIVISION_THRESHOLD) { VpDivdNewton(c, r, a, b); goto Exit; diff --git a/ext/bigdecimal/ntt.h b/ext/bigdecimal/ntt.h index 86fed3cb..4519459d 100644 --- a/ext/bigdecimal/ntt.h +++ b/ext/bigdecimal/ntt.h @@ -123,9 +123,7 @@ ntt_multiply(size_t a_size, size_t b_size, uint32_t *a, uint32_t *b, uint32_t *c return; } - int b_bits = 0; - while (((uint32_t)1 << b_bits) < (uint32_t)b_size) b_bits++; - int ntt_size_bits = b_bits + 1; + int ntt_size_bits = bit_length(b_size - 1) + 1; if (ntt_size_bits > MAX_NTT32_BITS) { rb_raise(rb_eArgError, "Multiply size too large"); } From 34e4715b671a6e1d6098a873a505763d64664820 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 20 Mar 2026 03:54:36 +0900 Subject: [PATCH 525/546] Update depend files, etc (#499) * Add missing depend files * Remove needless size_t to uint32_t cast --- ext/bigdecimal/bigdecimal.c | 2 +- ext/bigdecimal/depend | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 2a7b2264..43667d48 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4949,7 +4949,7 @@ VpMult(Real *c, Real *a, Real *b) if (!AddExponent(c, b->exponent)) return 0; if (b->Prec >= NTT_MULTIPLICATION_THRESHOLD) { - ntt_multiply((uint32_t)a->Prec, (uint32_t)b->Prec, a->frac, b->frac, c->frac); + ntt_multiply(a->Prec, b->Prec, a->frac, b->frac, c->frac); c->Prec = a->Prec + b->Prec; goto Cleanup; } diff --git a/ext/bigdecimal/depend b/ext/bigdecimal/depend index 33cf28d1..3afc9e49 100644 --- a/ext/bigdecimal/depend +++ b/ext/bigdecimal/depend @@ -12,4 +12,10 @@ bigdecimal.o: $(hdrdir)/ruby/subst.h bigdecimal.o: $(hdrdir)/ruby/util.h bigdecimal.o: bigdecimal.c bigdecimal.o: bigdecimal.h +bigdecimal.o: bits.h +bigdecimal.o: div.h +bigdecimal.o: feature.h +bigdecimal.o: missing.h +bigdecimal.o: ntt.h +bigdecimal.o: static_assert.h # AUTOGENERATED DEPENDENCIES END From 4a7268e37aaab950343f1d4553b7ca8432080c0d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 23 Mar 2026 04:55:41 +0900 Subject: [PATCH 526/546] Fix erfc(x,prec) precision when x is huge (#502) --- lib/bigdecimal/math.rb | 10 ++++++---- test/bigdecimal/test_bigmath.rb | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index bbf8fcfd..5d4a635b 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -636,7 +636,7 @@ def erfc(x, prec) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erfc) return BigDecimal::Internal.nan_computation_result if x.nan? return BigDecimal(1 - x.infinite?) if x.infinite? - return BigDecimal(1).sub(erf(x, prec + BigDecimal::Internal::EXTRA_PREC), prec) if x < 0 + return BigDecimal(1).sub(erf(x, prec + BigDecimal::Internal::EXTRA_PREC), prec) if x < 0.5 return BigDecimal(0) if x > 5000000000 # erfc(5000000000) < 1e-10000000000000000000 (underflow) if x >= 8 @@ -645,9 +645,10 @@ def erfc(x, prec) end # erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi) - # Precision of erf(x) needs about log10(exp(-x**2)) extra digits + # Precision of erf(x) needs about log10(exp(-x**2)/x/sqrt(pi)) extra digits log10 = 2.302585092994046 - high_prec = prec + BigDecimal::Internal::EXTRA_PREC + (x.ceil**2 / log10).ceil + xf = x.to_f + high_prec = prec + BigDecimal::Internal::EXTRA_PREC + ((xf**2 + Math.log(xf) + Math.log(Math::PI)/2) / log10).ceil BigDecimal(1).sub(erf(x, high_prec), prec) end @@ -704,7 +705,8 @@ def erfc(x, prec) return unless kmax sum = BigDecimal(1) - x2 = x.mult(x, prec) + # To calculate `exp(x2, prec)`, x2 needs extra log10(x**2) digits of precision + x2 = x.mult(x, prec + (2 * Math.log10(xf)).ceil) d = BigDecimal(1) (1..kmax).each do |k| d = d.div(x2, prec).mult(1 - 2 * k, prec).div(2, prec) diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 37f24e35..917ba942 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -542,6 +542,7 @@ def test_erfc assert_converge_in_precision {|n| BigMath.erfc(30 * SQRT2, n) } assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(50), n) } assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(60000), n) } + assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(1000000000 + SQRT2), n) } # Near crossover point between taylor series and asymptotic expansion around prec=150 assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(19.5), n) } assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(20.5), n) } From 39853fac380fec1826386419b45cc16bc13cc930 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 26 Mar 2026 02:33:18 +0900 Subject: [PATCH 527/546] Increase BigMath converge test precisions (#503) Conversion assertion was using precisions=[50, 100, 150]. This will be increased to [200, 400, 800, 1200]. Some slow methods (erf, erfc, gamma, lgamma) uses smaller precisions. --- test/bigdecimal/helper.rb | 9 +++-- test/bigdecimal/test_bigmath.rb | 70 ++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 07e68cf2..faffce59 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -39,18 +39,19 @@ def assert_in_exact_precision(expected, actual, precision) assert_in_delta(expected.mult(1, precision), actual, delta) end + CONVERSION_CHECK_PRECISIONS = [200, 400, 800, 1200] + # Asserts that the calculation of the given block converges to some value # with exactly the given +precision+. - def assert_converge_in_precision(&block) - expected = yield(200) - [50, 100, 150].each do |n| + def assert_converge_in_precision(precisions = CONVERSION_CHECK_PRECISIONS, &block) + expected = yield(precisions.max + 16) + precisions.each do |n| value = yield(n) assert(value != expected, "Unable to estimate precision for exact value") assert_equal(expected.mult(1, n), value) end end - def assert_nan(x) assert(x.nan?, "Expected #{x.inspect} to be NaN") end diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 917ba942..3cb5bf00 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -509,9 +509,10 @@ def test_erf BigDecimal("0.9953222650189527341620692563672529286108917970400600767383523262004372807199951773676290080196806805"), BigMath.erf(BigDecimal("2"), 100) ) - assert_converge_in_precision {|n| BigMath.erf(BigDecimal("1e-30"), n) } - assert_converge_in_precision {|n| BigMath.erf(BigDecimal("0.3"), n) } - assert_converge_in_precision {|n| BigMath.erf(SQRT2, n) } + precisions = [200, 300, 400] + assert_converge_in_precision(precisions) {|n| BigMath.erf(BigDecimal("1e-30"), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erf(BigDecimal("0.3"), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erf(SQRT2, n) } end def test_erfc @@ -524,28 +525,31 @@ def test_erfc assert_equal(2, BigMath.erfc(BigDecimal('-1e400'), 10)) assert_equal(1, BigMath.erfc(BigDecimal('1e-400'), N)) + precisions = [200, 300, 400] + # erfc with taylor series assert_equal( BigDecimal("2.088487583762544757000786294957788611560818119321163727012213713938174695833440290610766384285723554e-45"), BigMath.erfc(BigDecimal("10"), 100) ) - assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(0.3), n) } - assert_converge_in_precision {|n| BigMath.erfc(SQRT2, n) } - assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(8), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(BigDecimal(0.3), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(SQRT2, n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(BigDecimal(8), n) } # erfc with asymptotic expansion assert_equal( BigDecimal("1.896961059966276509268278259713415434936907563929186183462834752900411805205111886605256690776760041e-697"), BigMath.erfc(BigDecimal("40"), 100) ) - assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(30), n) } - assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(-2), n) } - assert_converge_in_precision {|n| BigMath.erfc(30 * SQRT2, n) } - assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(50), n) } - assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(60000), n) } - assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(1000000000 + SQRT2), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(BigDecimal(30), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(BigDecimal(-2), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(30 * SQRT2, n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(BigDecimal(50), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(BigDecimal(60000), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(BigDecimal(1000000000 + SQRT2), n) } + # Near crossover point between taylor series and asymptotic expansion around prec=150 - assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(19.5), n) } - assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(20.5), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(BigDecimal(19.5), n) } + assert_converge_in_precision(precisions) {|n| BigMath.erfc(BigDecimal(20.5), n) } end def test_gamma @@ -562,10 +566,11 @@ def test_gamma BigDecimal('0.28242294079603478742934215780245355184774949260912e456569'), BigMath.gamma(100000, 50) ) - assert_converge_in_precision {|n| gamma(BigDecimal("0.3"), n) } - assert_converge_in_precision {|n| gamma(BigDecimal("-1.9" + "9" * 30), n) } - assert_converge_in_precision {|n| gamma(BigDecimal("1234.56789"), n) } - assert_converge_in_precision {|n| gamma(BigDecimal("-987.654321"), n) } + precisions = [50, 100, 150] + assert_converge_in_precision(precisions) {|n| gamma(BigDecimal("0.3"), n) } + assert_converge_in_precision(precisions) {|n| gamma(BigDecimal("-1.9" + "9" * 30), n) } + assert_converge_in_precision(precisions) {|n| gamma(BigDecimal("1234.56789"), n) } + assert_converge_in_precision(precisions) {|n| gamma(BigDecimal("-987.654321"), n) } end def test_lgamma @@ -581,22 +586,23 @@ def test_lgamma assert_equal(sign, bigsign) end assert_equal([BigMath.log(PI(120).sqrt(120), 100), 1], lgamma(BigDecimal("0.5"), 100)) - assert_converge_in_precision {|n| lgamma(BigDecimal("0." + "9" * 80), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("1." + "0" * 80 + "1"), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("1." + "9" * 80), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("2." + "0" * 80 + "1"), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("-1." + "9" * 30), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("-3." + "0" * 30 + "1"), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("10"), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("0.3"), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("-1.9" + "9" * 30), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("987.65421"), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("-1234.56789"), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal("1e+400"), n).first } + precisions = [50, 100, 150] + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("0." + "9" * 80), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("1." + "0" * 80 + "1"), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("1." + "9" * 80), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("2." + "0" * 80 + "1"), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("-1." + "9" * 30), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("-3." + "0" * 30 + "1"), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("10"), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("0.3"), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("-1.9" + "9" * 30), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("987.65421"), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("-1234.56789"), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal("1e+400"), n).first } # gamma close 1 or -1 cases - assert_converge_in_precision {|n| lgamma(BigDecimal('-3.143580888349980058694358781820227899566'), n).first } - assert_converge_in_precision {|n| lgamma(BigDecimal('-4.991544640560047722345260122806465721667'), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal('-3.143580888349980058694358781820227899566'), n).first } + assert_converge_in_precision(precisions) {|n| lgamma(BigDecimal('-4.991544640560047722345260122806465721667'), n).first } end def test_frexp From 4782fc5d93ae7ca737100b27909e216663262403 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 27 Mar 2026 00:51:30 +0900 Subject: [PATCH 528/546] Fix error compiling with ruby.wasm (#504) Add a workaround for compile failure when rb_complex_real exists but HAVE_RB_COMPLEX_REAL is somehow undefined. --- ext/bigdecimal/missing.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index e8a8cabf..437d5bfb 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -58,7 +58,7 @@ char *BigDecimal_dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, c #ifndef HAVE_RB_COMPLEX_REAL static inline VALUE -rb_complex_real(VALUE cmp) +rb_complex_real_fallback(VALUE cmp) { #ifdef RCOMPLEX return RCOMPLEX(cmp)->real; @@ -66,11 +66,12 @@ rb_complex_real(VALUE cmp) return rb_funcall(cmp, rb_intern("real"), 0); #endif } +#define rb_complex_real rb_complex_real_fallback #endif #ifndef HAVE_RB_COMPLEX_IMAG static inline VALUE -rb_complex_imag(VALUE cmp) +rb_complex_imag_fallback(VALUE cmp) { # ifdef RCOMPLEX return RCOMPLEX(cmp)->imag; @@ -78,6 +79,7 @@ rb_complex_imag(VALUE cmp) return rb_funcall(cmp, rb_intern("imag"), 0); # endif } +#define rb_complex_imag rb_complex_imag_fallback #endif /* st */ From e64c502c14405da72130fc6587c742eb4e7836a3 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 28 Mar 2026 03:29:51 +0900 Subject: [PATCH 529/546] Bump version to 4.1.0 (#505) --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 43667d48..97c1ddce 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -33,7 +33,7 @@ #include "div.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "4.0.1" +#define BIGDECIMAL_VERSION "4.1.0" #define NTT_MULTIPLICATION_THRESHOLD 100 #define NEWTON_RAPHSON_DIVISION_THRESHOLD 200 From db5888a9e003d99bb867ae695a02a81b2204d1f6 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 30 Mar 2026 15:27:45 +0100 Subject: [PATCH 530/546] Define `test` as the default rake task (#509) --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 6590a91d..85ca5daf 100644 --- a/Rakefile +++ b/Rakefile @@ -19,8 +19,8 @@ Rake::TestTask.new do |t| t.warning = true end -task travis: :test task test: :compile +task default: :test benchmark_tasks = [] namespace :benchmark do From 64834a8e61d01a467a8185c0823c53ffd3e8b238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Mon, 30 Mar 2026 16:38:29 +0200 Subject: [PATCH 531/546] Add changelog for 4.1.0. (#508) --- CHANGES.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 2dc17cdd..506c9920 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,31 @@ # CHANGES +## 4.1.0 + +* Drop Ruby 2.5 support [GH-505] + + **@tompng** + +* Performance improvements: NTT multiplication, Newton-Raphson division, bit-burst algorithm for exp/sin, Gauss-Legendre for PI, improved log, and faster add/sub for large exponent differences [GH-407] [GH-433] [GH-434] [GH-478] [GH-484] + + **@tompng** + +* Remove ENABLE_NUMERIC_STRING flag [GH-479] + + **@tompng** + +* Add RBS signature and testing [GH-488] [GH-492] + + **@ksss** + +* Fix erfc(x,prec) precision when x is huge [GH-502] + + **@tompng** + +* Fix error compiling with ruby.wasm [GH-504] + + **@tompng** + ## 4.0.1 * Fix warning [GH-475] From bf04ad4066381795c7a5f9a761f140c15feaef54 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 30 Mar 2026 16:33:47 +0100 Subject: [PATCH 532/546] Make BigDecimal object embedded (#507) Fix: https://github.com/ruby/bigdecimal/issues/293 BigDecimal is a very good fit for embedded objects, as most of them are small, and they're all immutable, so they don't need any resizing. In most case it results in smaller objects, but also reduces pointer chasing as well as make these objects faster to free for the GC. master: ```ruby >> ObjectSpace.memsize_of(BigDecimal("422343434234234234234234234234423")) => 92 >> ObjectSpace.memsize_of(BigDecimal("4223434342342342342342342342344232342423423")) => 96 ``` This branch: ```ruby >> ObjectSpace.memsize_of(BigDecimal("422343434234234234234234234234423")) => 80 >> ObjectSpace.memsize_of(BigDecimal("4223434342342342342342342342344232342423423")) => 160 ``` --- ext/bigdecimal/bigdecimal.c | 279 ++++++++++++++---------------------- ext/bigdecimal/bigdecimal.h | 7 +- ext/bigdecimal/extconf.rb | 4 + 3 files changed, 116 insertions(+), 174 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 97c1ddce..3ef71bda 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -77,11 +77,6 @@ static struct { uint8_t mode; } rbd_rounding_modes[RBD_NUM_ROUNDING_MODES]; -typedef struct { - VALUE bigdecimal_or_nil; - Real *real_or_null; -} NULLABLE_BDVALUE; - static inline BDVALUE bdvalue_nonnullable(NULLABLE_BDVALUE v) { @@ -157,42 +152,6 @@ rbd_struct_size(size_t const internal_digits) return offsetof(Real, frac) + frac_len * sizeof(DECDIG); } -static inline Real * -rbd_allocate_struct(size_t const internal_digits) -{ - size_t const size = rbd_struct_size(internal_digits); - Real *real = ruby_xcalloc(1, size); - atomic_allocation_count_inc(); - real->MaxPrec = internal_digits; - return real; -} - -static inline Real * -rbd_allocate_struct_decimal_digits(size_t const decimal_digits) -{ - return rbd_allocate_struct(roomof(decimal_digits, BASE_FIG)); -} - -static void -rbd_free_struct(Real *real) -{ - if (real != NULL) { - check_allocation_count_nonzero(); - ruby_xfree(real); - atomic_allocation_count_dec_nounderflow(); - } -} - -MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero(int sign, size_t const digits)); -#define NewZero rbd_allocate_struct_zero -static inline Real * -rbd_allocate_struct_zero(int sign, size_t const digits) -{ - Real *real = rbd_allocate_struct_decimal_digits(digits); - VpSetZero(real, sign); - return real; -} - /* * ================== Ruby Interface part ========================== */ @@ -207,7 +166,6 @@ static void VpCheckException(Real *p, bool always); static VALUE CheckGetValue(BDVALUE v); static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); -static Real *VpCopy(Real *pv, Real const* const x); static int VPrint(FILE *fp,const char *cntl_chr,Real *a); /* @@ -222,49 +180,67 @@ static VALUE BigDecimal_negative_zero(void); static VALUE BigDecimal_addsub_with_coerce(VALUE self, VALUE r, size_t prec, int operation); static VALUE BigDecimal_mult_with_coerce(VALUE self, VALUE r, size_t prec); -static void -BigDecimal_delete(void *pv) -{ - rbd_free_struct(pv); -} +#ifndef HAVE_RB_EXT_RACTOR_SAFE +# undef RUBY_TYPED_FROZEN_SHAREABLE +# define RUBY_TYPED_FROZEN_SHAREABLE 0 +#endif + +#ifdef RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +#else +# ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE +# define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +# else +# define RUBY_TYPED_EMBEDDABLE 0 +# endif +#endif static size_t BigDecimal_memsize(const void *ptr) { +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + return 0; // Entirely embedded +#else const Real *pv = ptr; return (sizeof(*pv) + pv->MaxPrec * sizeof(DECDIG)); -} - -#ifndef HAVE_RB_EXT_RACTOR_SAFE -# undef RUBY_TYPED_FROZEN_SHAREABLE -# define RUBY_TYPED_FROZEN_SHAREABLE 0 #endif +} static const rb_data_type_t BigDecimal_data_type = { - "BigDecimal", - { 0, BigDecimal_delete, BigDecimal_memsize, }, -#ifdef RUBY_TYPED_FREE_IMMEDIATELY - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_WB_PROTECTED -#endif + .wrap_struct_name = "BigDecimal", + .function = { + .dmark = 0, + .dfree = RUBY_DEFAULT_FREE, + .dsize = BigDecimal_memsize, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, }; -// TypedData_Wrap_Struct may fail if there is no memory, or GC.add_stress_to_class(BigDecimal) is set. -// We need to first allocate empty struct, allocate Real struct, and then set the data pointer. -typedef struct { VALUE _obj; } NULL_WRAPPED_VALUE; -static NULL_WRAPPED_VALUE -BigDecimal_alloc_empty_struct(VALUE klass) +static VALUE +BigDecimal_allocate(size_t const internal_digits) { - return (NULL_WRAPPED_VALUE) { TypedData_Wrap_Struct(klass, &BigDecimal_data_type, NULL) }; + const size_t size = rbd_struct_size(internal_digits); + VALUE bd = rb_data_typed_object_zalloc(rb_cBigDecimal, size, &BigDecimal_data_type); + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + vp->MaxPrec = internal_digits; + RB_OBJ_FREEZE(bd); + return bd; } static VALUE -BigDecimal_wrap_struct(NULL_WRAPPED_VALUE v, Real *real) +BigDecimal_allocate_decimal_digits(size_t const decimal_digits) { - VALUE obj = v._obj; - assert(RTYPEDDATA_DATA(obj) == NULL); - RTYPEDDATA_DATA(obj) = real; - RB_OBJ_FREEZE(obj); - return obj; + return BigDecimal_allocate(roomof(decimal_digits, BASE_FIG)); +} + +static Real * +VpPtr(VALUE obj) +{ + Real *vp; + TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, vp); + return vp; } MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_wrap(int sign, size_t const digits)); @@ -272,9 +248,10 @@ MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_wrap(int sign, size_ static BDVALUE rbd_allocate_struct_zero_wrap(int sign, size_t const digits) { - NULL_WRAPPED_VALUE null_wrapped = BigDecimal_alloc_empty_struct(rb_cBigDecimal); - Real *real = rbd_allocate_struct_zero(sign, digits); - return (BDVALUE) { BigDecimal_wrap_struct(null_wrapped, real), real }; + VALUE obj = BigDecimal_allocate_decimal_digits(digits); + Real *real = VpPtr(obj); + VpSetZero(real, sign); + return (BDVALUE) { obj, real }; } static inline int @@ -336,8 +313,7 @@ GetBDValueWithPrecInternal(VALUE v, size_t prec, int must) goto SomeOneMayDoIt; } - Real *vp; - TypedData_Get_Struct(v, Real, &BigDecimal_data_type, vp); + Real *vp = VpPtr(v); return (NULLABLE_BDVALUE) { v, vp }; SomeOneMayDoIt: @@ -1010,26 +986,18 @@ check_int_precision(VALUE v) static NULLABLE_BDVALUE CreateFromString(const char *str, VALUE klass, bool strict_p, bool raise_exception) { - NULL_WRAPPED_VALUE null_wrapped = BigDecimal_alloc_empty_struct(klass); - Real *pv = VpAlloc(str, strict_p, raise_exception); - if (!pv) return (NULLABLE_BDVALUE) { Qnil, NULL }; - return (NULLABLE_BDVALUE) { BigDecimal_wrap_struct(null_wrapped, pv), pv }; + return VpAlloc(str, strict_p, raise_exception); } -static Real * -VpCopy(Real *pv, Real const* const x) +void +VpMemCopy(Real *pv, Real const* const x) { - assert(x != NULL); - - pv = (Real *)ruby_xrealloc(pv, rbd_struct_size(x->MaxPrec)); pv->MaxPrec = x->MaxPrec; pv->Prec = x->Prec; pv->exponent = x->exponent; pv->sign = x->sign; pv->flag = x->flag; MEMCPY(pv->frac, x->frac, DECDIG, pv->MaxPrec); - - return pv; } /* Returns True if the value is Not a Number. */ @@ -1219,7 +1187,7 @@ GetCoercePrec(Real *a, size_t prec) static VALUE BigDecimal_coerce(VALUE self, VALUE other) { - Real* pv = DATA_PTR(self); + Real* pv = VpPtr(self); BDVALUE b = GetBDValueWithPrecMust(other, GetCoercePrec(pv, 0)); return rb_assoc_new(CheckGetValue(b), self); } @@ -1687,7 +1655,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE if (VpIsNaN(a.real) || VpIsNaN(b.real) || (VpIsInf(a.real) && VpIsInf(b.real))) { VALUE nan = BigDecimal_nan(); - *div = *mod = (NULLABLE_BDVALUE) { nan, DATA_PTR(nan) }; + *div = *mod = (NULLABLE_BDVALUE) { nan, VpPtr(nan) }; goto Done; } if (VpIsZero(b.real)) { @@ -1696,19 +1664,19 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE if (VpIsInf(a.real)) { if (VpGetSign(a.real) == VpGetSign(b.real)) { VALUE inf = BigDecimal_positive_infinity(); - *div = (NULLABLE_BDVALUE) { inf, DATA_PTR(inf) }; + *div = (NULLABLE_BDVALUE) { inf, VpPtr(inf) }; } else { VALUE inf = BigDecimal_negative_infinity(); - *div = (NULLABLE_BDVALUE) { inf, DATA_PTR(inf) }; + *div = (NULLABLE_BDVALUE) { inf, VpPtr(inf) }; } VALUE nan = BigDecimal_nan(); - *mod = (NULLABLE_BDVALUE) { nan, DATA_PTR(nan) }; + *mod = (NULLABLE_BDVALUE) { nan, VpPtr(nan) }; goto Done; } if (VpIsZero(a.real)) { VALUE zero = BigDecimal_positive_zero(); - *div = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; + *div = (NULLABLE_BDVALUE) { zero, VpPtr(zero) }; *mod = bdvalue_nullable(a); goto Done; } @@ -1722,7 +1690,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod = bdvalue_nullable(b); } else { VALUE zero = BigDecimal_positive_zero(); - *div = (NULLABLE_BDVALUE) { zero, DATA_PTR(zero) }; + *div = (NULLABLE_BDVALUE) { zero, VpPtr(zero) }; *mod = bdvalue_nullable(a); } goto Done; @@ -2566,9 +2534,7 @@ check_exception(VALUE bd) { assert(is_kind_of_BigDecimal(bd)); - Real *vp; - TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); - VpCheckException(vp, false); + VpCheckException(VpPtr(bd), false); return bd; } @@ -2576,17 +2542,19 @@ check_exception(VALUE bd) static VALUE rb_uint64_convert_to_BigDecimal(uint64_t uval) { - NULL_WRAPPED_VALUE null_wrapped = BigDecimal_alloc_empty_struct(rb_cBigDecimal); + VALUE bd; Real *vp; if (uval == 0) { - vp = rbd_allocate_struct(1); + bd = BigDecimal_allocate(1); + vp = VpPtr(bd); vp->Prec = 1; vp->exponent = 1; VpSetZero(vp, 1); vp->frac[0] = 0; } else if (uval < BASE) { - vp = rbd_allocate_struct(1); + bd = BigDecimal_allocate(1); + vp = VpPtr(bd); vp->Prec = 1; vp->exponent = 1; VpSetSign(vp, 1); @@ -2611,14 +2579,15 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval) } const size_t exp = len + ntz; - vp = rbd_allocate_struct(len); + bd = BigDecimal_allocate(len); + vp = VpPtr(bd); vp->Prec = len; vp->exponent = exp; VpSetSign(vp, 1); MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - len, DECDIG, len); } - return BigDecimal_wrap_struct(null_wrapped, vp); + return bd; } static VALUE @@ -2627,8 +2596,7 @@ rb_int64_convert_to_BigDecimal(int64_t ival) const uint64_t uval = (ival < 0) ? (((uint64_t)-(ival+1))+1) : (uint64_t)ival; VALUE bd = rb_uint64_convert_to_BigDecimal(uval); if (ival < 0) { - Real *vp; - TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + Real *vp = VpPtr(bd); VpSetSign(vp, -1); } return bd; @@ -2835,8 +2803,7 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } VALUE bd = rb_inum_convert_to_BigDecimal(inum); - Real *vp; - TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + Real *vp = VpPtr(bd); assert(vp->Prec == prec); vp->exponent = exp; @@ -2902,13 +2869,15 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) if (digs == SIZE_MAX) return check_exception(val); - NULL_WRAPPED_VALUE null_wrapped = BigDecimal_alloc_empty_struct(rb_cBigDecimal); - Real *vp; - TypedData_Get_Struct(val, Real, &BigDecimal_data_type, vp); - vp = VpCopy(NULL, vp); + Real *vp = VpPtr(val); + + VALUE copy = BigDecimal_allocate(vp->MaxPrec); + Real *vp_copy = VpPtr(copy); + + VpMemCopy(vp_copy, vp); + RB_GC_GUARD(val); - VALUE copy = BigDecimal_wrap_struct(null_wrapped, vp); /* TODO: rounding */ return check_exception(copy); } @@ -3707,7 +3676,7 @@ Init_bigdecimal(void) static int gfDebug = 1; /* Debug switch */ #endif /* BIGDECIMAL_DEBUG */ -static Real *VpConstOne; /* constant 1.0 */ +static VALUE VpConstOne; /* constant 1.0 */ enum op_sw { OP_SW_ADD = 1, /* + */ @@ -4108,8 +4077,9 @@ VpInit(DECDIG BaseVal) VpGetDoubleNegZero(); /* Const 1.0 */ - VpConstOne = NewZero(1, 1); - VpSetOne(VpConstOne); + rb_global_variable(&VpConstOne); + VpConstOne = NewZeroWrap(1, 1).bigdecimal; + VpSetOne(VpPtr(VpConstOne)); #ifdef BIGDECIMAL_DEBUG gnAlloc = 0; @@ -4121,7 +4091,7 @@ VpInit(DECDIG BaseVal) VP_EXPORT Real * VpOne(void) { - return VpConstOne; + return VpPtr(VpConstOne); } /* If exponent overflows,then raise exception or returns 0 */ @@ -4152,7 +4122,7 @@ AddExponent(Real *a, SIGNED_VALUE n) return VpException(VP_EXCEPTION_OVERFLOW, "Exponent overflow", 0); } -Real * +NULLABLE_BDVALUE bigdecimal_parse_special_string(const char *str) { static const struct { @@ -4177,66 +4147,27 @@ bigdecimal_parse_special_string(const char *str) p = str + table[i].len; while (*p && ISSPACE(*p)) ++p; if (*p == '\0') { - Real *vp = rbd_allocate_struct(1); + VALUE obj = BigDecimal_allocate(1); + Real *vp = VpPtr(obj); switch (table[i].sign) { default: - UNREACHABLE; break; + UNREACHABLE; + return (NULLABLE_BDVALUE) { Qnil, NULL }; case VP_SIGN_POSITIVE_INFINITE: VpSetPosInf(vp); - return vp; + break; case VP_SIGN_NEGATIVE_INFINITE: VpSetNegInf(vp); - return vp; + break; case VP_SIGN_NaN: VpSetNaN(vp); - return vp; + break; } + return (NULLABLE_BDVALUE) { obj, vp }; } } - return NULL; -} - -struct VpCtoV_args { - Real *a; - const char *int_chr; - size_t ni; - const char *frac; - size_t nf; - const char *exp_chr; - size_t ne; -}; - -static VALUE -call_VpCtoV(VALUE arg) -{ - struct VpCtoV_args *x = (struct VpCtoV_args *)arg; - return (VALUE)VpCtoV(x->a, x->int_chr, x->ni, x->frac, x->nf, x->exp_chr, x->ne); -} - -static int -protected_VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne, int free_on_error) -{ - struct VpCtoV_args args; - int state = 0; - - args.a = a; - args.int_chr = int_chr; - args.ni = ni; - args.frac = frac; - args.nf = nf; - args.exp_chr = exp_chr; - args.ne = ne; - - VALUE result = rb_protect(call_VpCtoV, (VALUE)&args, &state); - if (state) { - if (free_on_error) { - rbd_free_struct(a); - } - rb_jump_tag(state); - } - - return (int)result; + return (NULLABLE_BDVALUE) { Qnil, NULL }; } /* @@ -4245,25 +4176,25 @@ protected_VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size * szVal ... The value assigned(char). * * [Returns] - * Pointer to the newly allocated variable, or - * NULL be returned if memory allocation is failed,or any error. + * NULLABLE_BDVALUE to the newly allocated variable. + * Null is returned if memory allocation failed, or any error occured. */ -VP_EXPORT Real * +VP_EXPORT NULLABLE_BDVALUE VpAlloc(const char *szVal, int strict_p, int exc) { const char *orig_szVal = szVal; size_t i, j, ni, ipf, nf, ipe, ne, exp_seen, nalloc; char v, *psz; int sign=1; - Real *vp = NULL; VALUE buf; /* Skipping leading spaces */ while (ISSPACE(*szVal)) szVal++; /* Check on Inf & NaN */ - if ((vp = bigdecimal_parse_special_string(szVal)) != NULL) { - return vp; + NULLABLE_BDVALUE special_bd = bigdecimal_parse_special_string(szVal); + if (special_bd.real_or_null != NULL) { + return special_bd; } /* Skip leading `#`. @@ -4417,10 +4348,11 @@ VpAlloc(const char *szVal, int strict_p, int exc) VALUE str; invalid_value: if (!strict_p) { - return NewZero(1, 1); + BDVALUE res = rbd_allocate_struct_zero_wrap(1, 1); + return (NULLABLE_BDVALUE) { res.bigdecimal, res.real }; } if (!exc) { - return NULL; + return (NULLABLE_BDVALUE) { Qnil, NULL }; } str = rb_str_new2(orig_szVal); rb_raise(rb_eArgError, "invalid value for BigDecimal(): \"%"PRIsVALUE"\"", str); @@ -4428,11 +4360,12 @@ VpAlloc(const char *szVal, int strict_p, int exc) nalloc = (ni + nf + BASE_FIG - 1) / BASE_FIG + 1; /* set effective allocation */ /* units for szVal[] */ - vp = rbd_allocate_struct(nalloc); + VALUE obj = BigDecimal_allocate(nalloc); + Real *vp = VpPtr(obj); VpSetZero(vp, sign); - protected_VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne, true); + VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne); rb_str_resize(buf, 0); - return vp; + return (NULLABLE_BDVALUE) { obj, vp }; } /* diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 57ae4bb0..faa66264 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -168,6 +168,11 @@ typedef struct { Real *real; } BDVALUE; +typedef struct { + VALUE bigdecimal_or_nil; + Real *real_or_null; +} NULLABLE_BDVALUE; + /* * ------------------ * EXPORTables. @@ -194,7 +199,7 @@ VP_EXPORT unsigned short VpSetRoundMode(unsigned short n); VP_EXPORT int VpException(unsigned short f,const char *str,int always); VP_EXPORT size_t VpNumOfChars(Real *vp,const char *pszFmt); VP_EXPORT size_t VpInit(DECDIG BaseVal); -VP_EXPORT Real *VpAlloc(const char *szVal, int strict_p, int exc); +VP_EXPORT NULLABLE_BDVALUE VpAlloc(const char *szVal, int strict_p, int exc); VP_EXPORT size_t VpAsgn(Real *c, Real *a, int isw); VP_EXPORT size_t VpAddSub(Real *c,Real *a,Real *b,int operation); VP_EXPORT size_t VpMult(Real *c,Real *a,Real *b); diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index e1897e6c..0b4baca2 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -46,6 +46,10 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_func("rb_category_warn", "ruby.h") have_const("RB_WARN_CATEGORY_DEPRECATED", "ruby.h") +if RUBY_ENGINE == "ruby" + have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3 +end + if File.file?(File.expand_path('../lib/bigdecimal.rb', __FILE__)) bigdecimal_rb = "$(srcdir)/lib/bigdecimal.rb" else From 1f2894fd94f2811f0ea5038cc0298f041daa049b Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 30 Mar 2026 16:34:35 +0100 Subject: [PATCH 533/546] Remove unused minitest from Gemfile (#510) --- Gemfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Gemfile b/Gemfile index b0ef9eac..06ea1a76 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,6 @@ gem "benchmark_driver" gem "fiddle", platform: :ruby gem "rake", ">= 12.3.3" gem "rake-compiler", ">= 0.9" -gem "minitest", "< 5.0.0" gem "irb" gem "test-unit" gem "test-unit-ruby-core" From 32fb1de0aca598ce417e5cf751ffa141633c4a8a Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 31 Mar 2026 03:38:40 +0900 Subject: [PATCH 534/546] Multiplication with 8-decdig batch (#501) Perform multiplication with 8-decdigs * 8-decdigs batch. This will reduce carry operation by 1/4, and may enable vectorization. --- ext/bigdecimal/bigdecimal.c | 91 +++++++++++----------------- test/bigdecimal/test_vp_operation.rb | 14 +++++ 2 files changed, 50 insertions(+), 55 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 3ef71bda..6561d9ed 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -35,8 +35,8 @@ #define BIGDECIMAL_VERSION "4.1.0" -#define NTT_MULTIPLICATION_THRESHOLD 100 -#define NEWTON_RAPHSON_DIVISION_THRESHOLD 200 +#define NTT_MULTIPLICATION_THRESHOLD 350 +#define NEWTON_RAPHSON_DIVISION_THRESHOLD 100 #define SIGNED_VALUE_MAX INTPTR_MAX #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) @@ -4837,17 +4837,12 @@ VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, * a0 a1 .... an * b0 * +_____________________________ * c0 c1 c2 ...... cl - * nc <---| - * MaxAB |--------------------| */ VP_EXPORT size_t VpMult(Real *c, Real *a, Real *b) { - size_t MxIndA, MxIndB, MxIndAB; - size_t ind_c, i, ii, nc; - size_t ind_as, ind_ae, ind_bs; - DECDIG carry; - DECDIG_DBL s; + ssize_t a_batch_max, b_batch_max; + DECDIG_DBL batch[15]; if (!VpIsDefOP(c, a, b, OP_SW_MULT)) return 0; /* No significant digit */ @@ -4871,9 +4866,6 @@ VpMult(Real *c, Real *a, Real *b) a = b; b = w; } - MxIndA = a->Prec - 1; - MxIndB = b->Prec - 1; - MxIndAB = a->Prec + b->Prec - 1; /* set LHSV c info */ @@ -4887,51 +4879,40 @@ VpMult(Real *c, Real *a, Real *b) goto Cleanup; } - carry = 0; - nc = ind_c = MxIndAB; - memset(c->frac, 0, (nc + 1) * sizeof(DECDIG)); /* Initialize c */ - c->Prec = nc + 1; /* set precision */ - for (nc = 0; nc < MxIndAB; ++nc, --ind_c) { - if (nc < MxIndB) { /* The left triangle of the Fig. */ - ind_as = MxIndA - nc; - ind_ae = MxIndA; - ind_bs = MxIndB; - } - else if (nc <= MxIndA) { /* The middle rectangular of the Fig. */ - ind_as = MxIndA - nc; - ind_ae = MxIndA - (nc - MxIndB); - ind_bs = MxIndB; - } - else /* if (nc > MxIndA) */ { /* The right triangle of the Fig. */ - ind_as = 0; - ind_ae = MxIndAB - nc - 1; - ind_bs = MxIndB - (nc - MxIndA); - } + c->Prec = a->Prec + b->Prec; /* set precision */ + memset(c->frac, 0, c->Prec * sizeof(DECDIG)); /* Initialize c */ - for (i = ind_as; i <= ind_ae; ++i) { - s = (DECDIG_DBL)a->frac[i] * b->frac[ind_bs--]; - carry = (DECDIG)(s / BASE); - s -= (DECDIG_DBL)carry * BASE; - c->frac[ind_c] += (DECDIG)s; - if (c->frac[ind_c] >= BASE) { - s = c->frac[ind_c] / BASE; - carry += (DECDIG)s; - c->frac[ind_c] -= (DECDIG)(s * BASE); + // Process 8 decdigits at a time to reduce the number of carry operations. + a_batch_max = (a->Prec - 1) / 8; + b_batch_max = (b->Prec - 1) / 8; + for (ssize_t ibatch = a_batch_max; ibatch >= 0; ibatch--) { + int isize = ibatch == a_batch_max ? (a->Prec - 1) % 8 + 1 : 8; + for (ssize_t jbatch = b_batch_max; jbatch >= 0; jbatch--) { + int jsize = jbatch == b_batch_max ? (b->Prec - 1) % 8 + 1 : 8; + memset(batch, 0, (isize + jsize - 1) * sizeof(DECDIG_DBL)); + + // Perform multiplication without carry calculation. + // 999999999 * 999999999 * 8 < 2**63 - 1, so DECDIG_DBL can hold the intermediate sum without overflow. + for (int i = 0; i < isize; i++) { + for (int j = 0; j < jsize; j++) { + batch[i + j] += (DECDIG_DBL)a->frac[ibatch * 8 + i] * b->frac[jbatch * 8 + j]; + } } - if (carry) { - ii = ind_c; - while (ii-- > 0) { - c->frac[ii] += carry; - if (c->frac[ii] >= BASE) { - carry = c->frac[ii] / BASE; - c->frac[ii] -= (carry * BASE); - } - else { - break; - } - } - } - } + + // Add the batch result to c with carry calculation. + DECDIG_DBL carry = 0; + for (int k = isize + jsize - 2; k >= 0; k--) { + size_t l = (ibatch + jbatch) * 8 + k + 1; + DECDIG_DBL s = c->frac[l] + batch[k] + carry; + c->frac[l] = (DECDIG)(s % BASE); + carry = (DECDIG_DBL)(s / BASE); + } + + // Adding carry may exceed BASE, but it won't cause overflow of DECDIG. + // Exceeded value will be resolved in the carry operation of next (ibatch + jbatch - 1) batch. + // WARNING: This safety strongly relies on the current nested loop execution order. + c->frac[(ibatch + jbatch) * 8] += (DECDIG)carry; + } } Cleanup: diff --git a/test/bigdecimal/test_vp_operation.rb b/test/bigdecimal/test_vp_operation.rb index 5d0d048a..5b5dab65 100644 --- a/test/bigdecimal/test_vp_operation.rb +++ b/test/bigdecimal/test_vp_operation.rb @@ -14,6 +14,13 @@ def setup end def test_vpmult + # Max carry case + [*32...40].repeated_permutation(2) do |n, m| + x = BigDecimal('9' * BASE_FIG * n) + y = BigDecimal('9' * BASE_FIG * m) + assert_equal(x.to_i * y.to_i, x.vpmult(y)) + end + assert_equal(BigDecimal('121932631112635269'), BigDecimal('123456789').vpmult(BigDecimal('987654321'))) assert_equal(BigDecimal('12193263.1112635269'), BigDecimal('123.456789').vpmult(BigDecimal('98765.4321'))) x = 123**456 @@ -22,6 +29,13 @@ def test_vpmult end def test_nttmult + # Max carry case + [*32...40].repeated_permutation(2) do |n, m| + x = BigDecimal('9' * BASE_FIG * n) + y = BigDecimal('9' * BASE_FIG * m) + assert_equal(x.to_i * y.to_i, x.nttmult(y)) + end + [*1..32].repeated_permutation(2) do |a, b| x = BigDecimal(10 ** (BASE_FIG * a) / 7) y = BigDecimal(10 ** (BASE_FIG * b) / 13) From f0985b36f5b4b7c13605d8eb15fce18b194a61b0 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 1 Apr 2026 23:49:17 +0900 Subject: [PATCH 535/546] Increase VpMult batch size (#511) Number of divmod operation which is the bottleneck depends on batch size. batch_size*BASE**2 doesn't need to be less than 2**63-1, it needs to be less than 2**64. We can increase batch size to 16. --- ext/bigdecimal/bigdecimal.c | 25 ++++++++++++++----------- test/bigdecimal/test_vp_operation.rb | 4 ++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6561d9ed..7756ec63 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -35,7 +35,9 @@ #define BIGDECIMAL_VERSION "4.1.0" -#define NTT_MULTIPLICATION_THRESHOLD 350 +/* Make sure VPMULT_BATCH_SIZE*BASE*BASE does not overflow DECDIG_DBL */ +#define VPMULT_BATCH_SIZE 16 +#define NTT_MULTIPLICATION_THRESHOLD 450 #define NEWTON_RAPHSON_DIVISION_THRESHOLD 100 #define SIGNED_VALUE_MAX INTPTR_MAX #define SIGNED_VALUE_MIN INTPTR_MIN @@ -4842,7 +4844,7 @@ VP_EXPORT size_t VpMult(Real *c, Real *a, Real *b) { ssize_t a_batch_max, b_batch_max; - DECDIG_DBL batch[15]; + DECDIG_DBL batch[VPMULT_BATCH_SIZE * 2 - 1]; if (!VpIsDefOP(c, a, b, OP_SW_MULT)) return 0; /* No significant digit */ @@ -4882,27 +4884,28 @@ VpMult(Real *c, Real *a, Real *b) c->Prec = a->Prec + b->Prec; /* set precision */ memset(c->frac, 0, c->Prec * sizeof(DECDIG)); /* Initialize c */ - // Process 8 decdigits at a time to reduce the number of carry operations. - a_batch_max = (a->Prec - 1) / 8; - b_batch_max = (b->Prec - 1) / 8; + // Process VPMULT_BATCH_SIZE decdigits at a time to reduce the number of carry operations. + a_batch_max = (a->Prec - 1) / VPMULT_BATCH_SIZE; + b_batch_max = (b->Prec - 1) / VPMULT_BATCH_SIZE; for (ssize_t ibatch = a_batch_max; ibatch >= 0; ibatch--) { - int isize = ibatch == a_batch_max ? (a->Prec - 1) % 8 + 1 : 8; + int isize = ibatch == a_batch_max ? (a->Prec - 1) % VPMULT_BATCH_SIZE + 1 : VPMULT_BATCH_SIZE; for (ssize_t jbatch = b_batch_max; jbatch >= 0; jbatch--) { - int jsize = jbatch == b_batch_max ? (b->Prec - 1) % 8 + 1 : 8; + int jsize = jbatch == b_batch_max ? (b->Prec - 1) % VPMULT_BATCH_SIZE + 1 : VPMULT_BATCH_SIZE; memset(batch, 0, (isize + jsize - 1) * sizeof(DECDIG_DBL)); // Perform multiplication without carry calculation. - // 999999999 * 999999999 * 8 < 2**63 - 1, so DECDIG_DBL can hold the intermediate sum without overflow. + // BASE * BASE * VPMULT_BATCH_SIZE < 2**64 should be satisfied so that + // DECDIG_DBL can hold the intermediate sum without overflow. for (int i = 0; i < isize; i++) { for (int j = 0; j < jsize; j++) { - batch[i + j] += (DECDIG_DBL)a->frac[ibatch * 8 + i] * b->frac[jbatch * 8 + j]; + batch[i + j] += (DECDIG_DBL)a->frac[ibatch * VPMULT_BATCH_SIZE + i] * b->frac[jbatch * VPMULT_BATCH_SIZE + j]; } } // Add the batch result to c with carry calculation. DECDIG_DBL carry = 0; for (int k = isize + jsize - 2; k >= 0; k--) { - size_t l = (ibatch + jbatch) * 8 + k + 1; + size_t l = (ibatch + jbatch) * VPMULT_BATCH_SIZE + k + 1; DECDIG_DBL s = c->frac[l] + batch[k] + carry; c->frac[l] = (DECDIG)(s % BASE); carry = (DECDIG_DBL)(s / BASE); @@ -4911,7 +4914,7 @@ VpMult(Real *c, Real *a, Real *b) // Adding carry may exceed BASE, but it won't cause overflow of DECDIG. // Exceeded value will be resolved in the carry operation of next (ibatch + jbatch - 1) batch. // WARNING: This safety strongly relies on the current nested loop execution order. - c->frac[(ibatch + jbatch) * 8] += (DECDIG)carry; + c->frac[(ibatch + jbatch) * VPMULT_BATCH_SIZE] += (DECDIG)carry; } } diff --git a/test/bigdecimal/test_vp_operation.rb b/test/bigdecimal/test_vp_operation.rb index 5b5dab65..ce690aee 100644 --- a/test/bigdecimal/test_vp_operation.rb +++ b/test/bigdecimal/test_vp_operation.rb @@ -15,7 +15,7 @@ def setup def test_vpmult # Max carry case - [*32...40].repeated_permutation(2) do |n, m| + [*32...48].repeated_permutation(2) do |n, m| x = BigDecimal('9' * BASE_FIG * n) y = BigDecimal('9' * BASE_FIG * m) assert_equal(x.to_i * y.to_i, x.vpmult(y)) @@ -30,7 +30,7 @@ def test_vpmult def test_nttmult # Max carry case - [*32...40].repeated_permutation(2) do |n, m| + [*32...48].repeated_permutation(2) do |n, m| x = BigDecimal('9' * BASE_FIG * n) y = BigDecimal('9' * BASE_FIG * m) assert_equal(x.to_i * y.to_i, x.nttmult(y)) From 70caa24f43032b8033e5b0678bb40b940b22a4c4 Mon Sep 17 00:00:00 2001 From: Brandon Zylstra <9854+brandonzylstra@users.noreply.github.com> Date: Fri, 3 Apr 2026 09:17:55 -0400 Subject: [PATCH 536/546] Update to cover change in Bundler (#512) Since `install` is no longer the default subcommand for everyone (as of Bundler 4), the explicit `bundle install` is better. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5a86817..1097a020 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ gem 'bigdecimal' And then execute: ```bash -bundle +bundle install ``` Or install it yourself as: From ae1d238b0d32cd7456a7cf9fc376b8e46a711f40 Mon Sep 17 00:00:00 2001 From: Brandon Zylstra <9854+brandonzylstra@users.noreply.github.com> Date: Fri, 3 Apr 2026 09:19:07 -0400 Subject: [PATCH 537/546] tiny grammar fix in README.md (#513) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1097a020..6b1e79eb 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ gem install bigdecimal ### For RubyInstaller users -If your Ruby comes from [RubyInstaller](https://rubyinstaller.org/), make sure [Devkit](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit) is available on your environment before installing bigdecimal. +If your Ruby comes from [RubyInstaller](https://rubyinstaller.org/), make sure [Devkit](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit) is available in your environment before installing bigdecimal. ### For Chocolatey From 3bf735fbe41fb07832ddf01ff507d92ea1810b05 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 3 Apr 2026 22:42:25 +0900 Subject: [PATCH 538/546] Add a workaround for slow BigDecimal#to_f when it has large N_significant_digits (#514) --- lib/bigdecimal.rb | 13 +++++++++++-- lib/bigdecimal/math.rb | 10 +++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 370e9c27..6003802b 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -76,9 +76,18 @@ def self.newton_loop(prec, initial_precision: BigDecimal.double_fig / 2, safe_ma end end + # Fast and rough conversion to float for mathematical calculations. + # Bigdecimal#to_f is slow when n_significant_digits is large. + # This is because to_f internally converts BigDecimal to String + # to get the exact nearest float representation. + # TODO: Remove this workaround when BigDecimal#to_f is optimized. + def self.fast_to_f(x) # :nodoc: + x.n_significant_digits < 40 ? x.to_f : x.mult(1, 20).to_f + end + # Calculates Math.log(x.to_f) considering large or small exponent def self.float_log(x) # :nodoc: - Math.log(x._decimal_shift(-x.exponent).to_f) + x.exponent * Math.log(10) + Math.log(fast_to_f(x._decimal_shift(-x.exponent))) + x.exponent * Math.log(10) end # Calculating Taylor series sum using binary splitting method @@ -268,7 +277,7 @@ def sqrt(prec) ex = exponent / 2 x = _decimal_shift(-2 * ex) - y = BigDecimal(Math.sqrt(x.to_f), 0) + y = BigDecimal(Math.sqrt(BigDecimal::Internal.fast_to_f(x)), 0) Internal.newton_loop(prec + BigDecimal::Internal::EXTRA_PREC) do |p| y = y.add(x.div(y, p), p).div(2, p) end diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 5d4a635b..03e88cea 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -144,7 +144,7 @@ def cbrt(x, prec) x = -x if neg = x < 0 ex = x.exponent / 3 x = x._decimal_shift(-3 * ex) - y = BigDecimal(Math.cbrt(x.to_f), 0) + y = BigDecimal(Math.cbrt(BigDecimal::Internal.fast_to_f(x)), 0) BigDecimal::Internal.newton_loop(prec + BigDecimal::Internal::EXTRA_PREC) do |p| y = (2 * y + x.div(y, p).div(y, p)).div(3, p) end @@ -304,7 +304,7 @@ def atan(x, prec) # Solve tan(y) - x = 0 with Newton's method # Repeat: y -= (tan(y) - x) * cos(y)**2 - y = BigDecimal(Math.atan(x.to_f), 0) + y = BigDecimal(Math.atan(BigDecimal::Internal.fast_to_f(x)), 0) BigDecimal::Internal.newton_loop(n) do |p| s = sin(y, p) c = (1 - s * s).sqrt(p) @@ -605,7 +605,7 @@ def erf(x, prec) return BigDecimal(1) if x > 5000000000 # erf(5000000000) > 1 - 1e-10000000000000000000 if x > 8 - xf = x.to_f + xf = BigDecimal::Internal.fast_to_f(x) log10_erfc = -xf ** 2 / Math.log(10) - Math.log10(xf * Math::PI ** 0.5) erfc_prec = [prec + log10_erfc.ceil, 1].max erfc = _erfc_asymptotic(x, erfc_prec) @@ -647,7 +647,7 @@ def erfc(x, prec) # erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi) # Precision of erf(x) needs about log10(exp(-x**2)/x/sqrt(pi)) extra digits log10 = 2.302585092994046 - xf = x.to_f + xf = BigDecimal::Internal.fast_to_f(x) high_prec = prec + BigDecimal::Internal::EXTRA_PREC + ((xf**2 + Math.log(xf) + Math.log(Math::PI)/2) / log10).ceil BigDecimal(1).sub(erf(x, high_prec), prec) end @@ -698,7 +698,7 @@ def erfc(x, prec) # sqrt(2)/2 + k*log(k) - k - 2*k*log(x) < -prec*log(10) # and the left side is minimized when k = x**2. prec += BigDecimal::Internal::EXTRA_PREC - xf = x.to_f + xf = BigDecimal::Internal.fast_to_f(x) kmax = (1..(xf ** 2).floor).bsearch do |k| Math.log(2) / 2 + k * Math.log(k) - k - 2 * k * Math.log(xf) < -prec * Math.log(10) end From 219cb2e641e3a1242f7fbe43025bf1ea3b2797af Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 4 Apr 2026 22:09:32 +0900 Subject: [PATCH 539/546] Bump version to v4.1.1 (#516) --- CHANGES.md | 10 ++++++++++ ext/bigdecimal/bigdecimal.c | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 506c9920..adb2db71 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # CHANGES +## 4.1.1 + +* Make BigDecimal object embedded [GH-507] + + **@byroot** + +* Multiplication with 16-decdig batch [GH-501] [GH-511] + + **@tompng** + ## 4.1.0 * Drop Ruby 2.5 support [GH-505] diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7756ec63..555abebc 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -33,7 +33,7 @@ #include "div.h" #include "static_assert.h" -#define BIGDECIMAL_VERSION "4.1.0" +#define BIGDECIMAL_VERSION "4.1.1" /* Make sure VPMULT_BATCH_SIZE*BASE*BASE does not overflow DECDIG_DBL */ #define VPMULT_BATCH_SIZE 16 From 14ac73c7295c0b8663a4386609f00f65cb96f792 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:22:19 +0000 Subject: [PATCH 540/546] Bump rubygems/release-gem from 1.1.4 to 1.2.0 Bumps [rubygems/release-gem](https://github.com/rubygems/release-gem) from 1.1.4 to 1.2.0. - [Release notes](https://github.com/rubygems/release-gem/releases) - [Commits](https://github.com/rubygems/release-gem/compare/e9a6361a0b14562539327c2a02373edc56dd3169...6317d8d1f7e28c24d28f6eff169ea854948bd9f7) --- updated-dependencies: - dependency-name: rubygems/release-gem dependency-version: 1.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 73df0419..7fcd3f8a 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -43,7 +43,7 @@ jobs: ruby-version: ${{ matrix.ruby }} - name: Publish to RubyGems - uses: rubygems/release-gem@e9a6361a0b14562539327c2a02373edc56dd3169 # v1.1.4 + uses: rubygems/release-gem@6317d8d1f7e28c24d28f6eff169ea854948bd9f7 # v1.2.0 - name: Create GitHub release run: | From 9d3e6df6a3b75f37a035af500b9a16aea49530df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:22:23 +0000 Subject: [PATCH 541/546] Bump step-security/harden-runner from 2.16.0 to 2.16.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.16.0 to 2.16.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594...fe104658747b27e96e4f7e80cd0a94068e53901d) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.16.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 73df0419..1d7f6730 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 + uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 with: egress-policy: audit From b66add10fd5ce7549f38e363373d2a0d1fa2cfe6 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 6 Apr 2026 15:30:19 +0200 Subject: [PATCH 542/546] Optimize BigDecimal#to_s (#519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most of the time was spent in two calls to `snprintf`, by a simpler integer to ASCII function, it can be made several time faster. The code is largely adapted from an earlier version of ruby/json. ruby/json now use a much more optimized algorithm, but there are licensing consideration so not sure it's worth optimizing that much. Before: ``` ruby 4.0.2 (2026-03-17 revision d3da9fec82) +YJIT +PRISM [arm64-darwin25] Warming up -------------------------------------- 22.99 408.215k i/100ms large 230.802k i/100ms Calculating ------------------------------------- 22.99 4.214M (± 2.2%) i/s (237.32 ns/i) - 21.227M in 5.040152s large 2.384M (± 2.6%) i/s (419.45 ns/i) - 12.002M in 5.037698s ``` After: ``` ruby 4.0.2 (2026-03-17 revision d3da9fec82) +YJIT +PRISM [arm64-darwin25] Warming up -------------------------------------- 22.99 1.026M i/100ms large 846.057k i/100ms Calculating ------------------------------------- 22.99 10.882M (± 0.8%) i/s (91.89 ns/i) - 55.426M in 5.093603s large 9.094M (± 1.0%) i/s (109.97 ns/i) - 45.687M in 5.024549s ``` ```ruby require "bundler/inline" gemfile do source 'https://rubygems.org' gem "benchmark-ips" gem "bigdecimal", path: "/Users/byroot/src/github.com/byroot/bigdecimal" end small = BigDecimal("29.99") large = BigDecimal("32423094234234.23423432") Benchmark.ips do |x| x.report("22.99") { small.to_s } x.report("large") { large.to_s } end ``` --- ext/bigdecimal/bigdecimal.c | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 555abebc..3234a894 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -5449,10 +5449,22 @@ VpToSpecialString(Real *a, char *buf, size_t buflen, int fPlus) return 0; } +#define ULLTOA_BUFFER_SIZE 20 +static size_t Vp_ulltoa(unsigned long long number, char *buf) +{ + static const char digits[] = "0123456789"; + char* tmp = buf; + + do *tmp-- = digits[number % 10]; while (number /= 10); + return buf - tmp; +} + VP_EXPORT void VpToString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { + char ulltoa_buf[ULLTOA_BUFFER_SIZE]; + char *ulltoa_buf_end = ulltoa_buf + ULLTOA_BUFFER_SIZE; size_t i, n, ZeroSup; DECDIG shift, m, e, nn; char *p = buf; @@ -5492,10 +5504,11 @@ VpToString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) while (m) { nn = e / m; if (!ZeroSup || nn) { - /* The reading zero(s) */ - size_t n = (size_t)snprintf(p, plen, "%lu", (unsigned long)nn); + size_t n = Vp_ulltoa(nn, ulltoa_buf_end - 1); if (n > plen) goto overflow; + MEMCPY(p, ulltoa_buf_end - n, char, n); ADVANCE(n); + /* as 0.00xx will be ignored. */ ZeroSup = 0; /* Set to print succeeding zeros */ } @@ -5514,7 +5527,22 @@ VpToString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) *(--p) = '\0'; ++plen; } - snprintf(p, plen, "e%"PRIdSIZE, ex); + *p = 'e'; + ADVANCE(1); + + if (ex < 0) { + *p = '-'; + ADVANCE(1); + ex = -ex; + } + + size_t ex_n = Vp_ulltoa(ex, ulltoa_buf_end - 1); + if (ex_n > plen) goto overflow; + MEMCPY(p, ulltoa_buf_end - ex_n, char, ex_n); + ADVANCE(ex_n); + *p = '\0'; + ADVANCE(1); + if (fFmt) VpFormatSt(buf, fFmt); overflow: From 48b2adc5e6569fc9c713380deb92b2c39f7465d7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 7 Apr 2026 00:46:39 +0900 Subject: [PATCH 543/546] Fix calloc-transposed-args warning (#520) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` ntt.h: In function ‘ntt_multiply’: ntt.h:136:41: warning: ‘ruby_xcalloc’ sizes specified with ‘sizeof’ in the earlier argument and not in the later argument [-Wcalloc-transposed-args] 136 | uint32_t *mem = ruby_xcalloc(sizeof(uint32_t), ntt_size * 9); | ^~~~~~~~ ntt.h:136:41: note: earlier argument should specify number of elements, later size of each element ``` --- ext/bigdecimal/ntt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/ntt.h b/ext/bigdecimal/ntt.h index 4519459d..42b9bd47 100644 --- a/ext/bigdecimal/ntt.h +++ b/ext/bigdecimal/ntt.h @@ -133,7 +133,7 @@ ntt_multiply(size_t a_size, size_t b_size, uint32_t *a, uint32_t *b, uint32_t *c uint32_t batch_size = ntt_size - (uint32_t)b_size; uint32_t batch_count = (uint32_t)((a_size + batch_size - 1) / batch_size); - uint32_t *mem = ruby_xcalloc(sizeof(uint32_t), ntt_size * 9); + uint32_t *mem = ruby_xcalloc(ntt_size * 9, sizeof(uint32_t)); uint32_t *ntt1 = mem; uint32_t *ntt2 = mem + ntt_size; uint32_t *ntt3 = mem + ntt_size * 2; From 72937b79d87a2a8dd70da691046cedf038744dbc Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 7 Apr 2026 21:03:34 +0900 Subject: [PATCH 544/546] Use '0'+n for converting single digit to char (#521) --- ext/bigdecimal/bigdecimal.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 3234a894..d0fdef4b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -5396,8 +5396,8 @@ VpSzMantissa(Real *a, char *buf, size_t buflen) while (m) { nn = e / m; if (!ZeroSup || nn) { - snprintf(buf, buflen, "%lu", (unsigned long)nn); /* The leading zero(s) */ - buf += strlen(buf); + *buf = (char)('0' + nn); + buf++; /* as 0.00xx will be ignored. */ ZeroSup = 0; /* Set to print succeeding zeros */ } @@ -5504,10 +5504,8 @@ VpToString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) while (m) { nn = e / m; if (!ZeroSup || nn) { - size_t n = Vp_ulltoa(nn, ulltoa_buf_end - 1); - if (n > plen) goto overflow; - MEMCPY(p, ulltoa_buf_end - n, char, n); - ADVANCE(n); + *p = (char)('0' + nn); + ADVANCE(1); /* as 0.00xx will be ignored. */ ZeroSup = 0; /* Set to print succeeding zeros */ From fc544875124fb4d0ab595aebb298d4649afb7ae7 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Wed, 8 Apr 2026 22:44:25 +0900 Subject: [PATCH 545/546] Revert "Add a workaround for slow BigDecimal#to_f when it has large N_significant_digits (#514)" (#522) This reverts commit 3bf735fbe41fb07832ddf01ff507d92ea1810b05. --- lib/bigdecimal.rb | 13 ++----------- lib/bigdecimal/math.rb | 10 +++++----- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 6003802b..370e9c27 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -76,18 +76,9 @@ def self.newton_loop(prec, initial_precision: BigDecimal.double_fig / 2, safe_ma end end - # Fast and rough conversion to float for mathematical calculations. - # Bigdecimal#to_f is slow when n_significant_digits is large. - # This is because to_f internally converts BigDecimal to String - # to get the exact nearest float representation. - # TODO: Remove this workaround when BigDecimal#to_f is optimized. - def self.fast_to_f(x) # :nodoc: - x.n_significant_digits < 40 ? x.to_f : x.mult(1, 20).to_f - end - # Calculates Math.log(x.to_f) considering large or small exponent def self.float_log(x) # :nodoc: - Math.log(fast_to_f(x._decimal_shift(-x.exponent))) + x.exponent * Math.log(10) + Math.log(x._decimal_shift(-x.exponent).to_f) + x.exponent * Math.log(10) end # Calculating Taylor series sum using binary splitting method @@ -277,7 +268,7 @@ def sqrt(prec) ex = exponent / 2 x = _decimal_shift(-2 * ex) - y = BigDecimal(Math.sqrt(BigDecimal::Internal.fast_to_f(x)), 0) + y = BigDecimal(Math.sqrt(x.to_f), 0) Internal.newton_loop(prec + BigDecimal::Internal::EXTRA_PREC) do |p| y = y.add(x.div(y, p), p).div(2, p) end diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 03e88cea..5d4a635b 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -144,7 +144,7 @@ def cbrt(x, prec) x = -x if neg = x < 0 ex = x.exponent / 3 x = x._decimal_shift(-3 * ex) - y = BigDecimal(Math.cbrt(BigDecimal::Internal.fast_to_f(x)), 0) + y = BigDecimal(Math.cbrt(x.to_f), 0) BigDecimal::Internal.newton_loop(prec + BigDecimal::Internal::EXTRA_PREC) do |p| y = (2 * y + x.div(y, p).div(y, p)).div(3, p) end @@ -304,7 +304,7 @@ def atan(x, prec) # Solve tan(y) - x = 0 with Newton's method # Repeat: y -= (tan(y) - x) * cos(y)**2 - y = BigDecimal(Math.atan(BigDecimal::Internal.fast_to_f(x)), 0) + y = BigDecimal(Math.atan(x.to_f), 0) BigDecimal::Internal.newton_loop(n) do |p| s = sin(y, p) c = (1 - s * s).sqrt(p) @@ -605,7 +605,7 @@ def erf(x, prec) return BigDecimal(1) if x > 5000000000 # erf(5000000000) > 1 - 1e-10000000000000000000 if x > 8 - xf = BigDecimal::Internal.fast_to_f(x) + xf = x.to_f log10_erfc = -xf ** 2 / Math.log(10) - Math.log10(xf * Math::PI ** 0.5) erfc_prec = [prec + log10_erfc.ceil, 1].max erfc = _erfc_asymptotic(x, erfc_prec) @@ -647,7 +647,7 @@ def erfc(x, prec) # erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi) # Precision of erf(x) needs about log10(exp(-x**2)/x/sqrt(pi)) extra digits log10 = 2.302585092994046 - xf = BigDecimal::Internal.fast_to_f(x) + xf = x.to_f high_prec = prec + BigDecimal::Internal::EXTRA_PREC + ((xf**2 + Math.log(xf) + Math.log(Math::PI)/2) / log10).ceil BigDecimal(1).sub(erf(x, high_prec), prec) end @@ -698,7 +698,7 @@ def erfc(x, prec) # sqrt(2)/2 + k*log(k) - k - 2*k*log(x) < -prec*log(10) # and the left side is minimized when k = x**2. prec += BigDecimal::Internal::EXTRA_PREC - xf = BigDecimal::Internal.fast_to_f(x) + xf = x.to_f kmax = (1..(xf ** 2).floor).bsearch do |k| Math.log(2) / 2 + k * Math.log(k) - k - 2 * k * Math.log(xf) < -prec * Math.log(10) end From 50b80b10545e1602fa9e3c64b80595ab4efc4e17 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 9 Apr 2026 01:50:59 +0900 Subject: [PATCH 546/546] BigMath.exp overflow/underflow check (#523) * BigMath.exp overflow/underflow check `BigMath.exp(BigDecimal('1e100000000'), 20)` was slow. Skip calculation when x.exponent >= 21 which always result in overflow or underflow. * Add underflow check to BigMath.erfc --- lib/bigdecimal.rb | 19 ++++++++++++++++++- lib/bigdecimal/math.rb | 2 +- test/bigdecimal/helper.rb | 15 ++++++++++++++- test/bigdecimal/test_bigdecimal.rb | 11 +++++++++++ test/bigdecimal/test_bigmath.rb | 3 ++- 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 370e9c27..5b492ec0 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -57,6 +57,13 @@ def self.infinity_computation_result # :nodoc: BigDecimal::INFINITY end + def self.underflow_computation_result # :nodoc: + if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_UNDERFLOW) + raise FloatDomainError, 'Exponent underflow' + end + BigDecimal(0) + end + def self.nan_computation_result # :nodoc: if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_NaN) raise FloatDomainError, "Computation results to 'NaN'" @@ -350,7 +357,17 @@ def exp(x, prec) prec = BigDecimal::Internal.coerce_validate_prec(prec, :exp) x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :exp) return BigDecimal::Internal.nan_computation_result if x.nan? - return x.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) if x.infinite? + if x.infinite? || x.exponent >= 21 # exp(10**20) and exp(-10**20) overflows/underflows 64-bit exponent + if x.positive? + return BigDecimal::Internal.infinity_computation_result + elsif x.infinite? + # exp(-Infinity) is +0 by definition, this is not an underflow. + return BigDecimal(0) + else + return BigDecimal::Internal.underflow_computation_result + end + end + return BigDecimal(1) if x.zero? # exp(x * 10**cnt) = exp(x)**(10**cnt) diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 5d4a635b..1b3357cb 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -637,7 +637,7 @@ def erfc(x, prec) return BigDecimal::Internal.nan_computation_result if x.nan? return BigDecimal(1 - x.infinite?) if x.infinite? return BigDecimal(1).sub(erf(x, prec + BigDecimal::Internal::EXTRA_PREC), prec) if x < 0.5 - return BigDecimal(0) if x > 5000000000 # erfc(5000000000) < 1e-10000000000000000000 (underflow) + return BigDecimal::Internal.underflow_computation_result if x > 5000000000 # erfc(5000000000) < 1e-10000000000000000000 (underflow) if x >= 8 y = _erfc_asymptotic(x, prec) diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index faffce59..4be51892 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -71,7 +71,7 @@ def assert_infinite_calculation(positive:) BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) positive ? assert_positive_infinite(yield) : assert_negative_infinite(yield) BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) - assert_raise_with_message(FloatDomainError, /Infinity/) { yield } + assert_raise_with_message(FloatDomainError, /infinity|overflow/i) { yield } end end @@ -83,6 +83,19 @@ def assert_negative_infinite_calculation(&block) assert_infinite_calculation(positive: false, &block) end + def assert_underflow_calculation(accept_overflow: false) + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + assert_equal(BigDecimal(0), yield) + BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, true) + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true) + # Accept internal overflow (e.g. overflow calculating denominator part) + pattern = accept_overflow ? /underflow|overflow/i : /underflow/i + assert_raise_with_message(FloatDomainError, pattern) { yield } + end + end + def assert_nan_calculation(&block) BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 0d7b310d..4487f4a4 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2296,6 +2296,7 @@ def test_exp_with_negative end def test_exp_with_negative_infinite + # exp(-infinity) is exactly zero. This is not an underflow. assert_equal(0, BigMath.exp(NEGATIVE_INFINITY, 20)) end @@ -2303,6 +2304,16 @@ def test_exp_with_positive_infinite assert_positive_infinite_calculation { BigMath.exp(BigDecimal::INFINITY, 20) } end + def test_exp_with_overflow_underflow + assert_underflow_calculation { BigMath.exp(-1e+100, 20) } + assert_underflow_calculation { BigMath.exp(-0.9e+20, 20) } + assert_positive_infinite_calculation { BigMath.exp(1e+100, 20) } + assert_positive_infinite_calculation { BigMath.exp(0.9e+20, 20) } + huge = BigDecimal("0.1e#{EXPONENT_MAX / 100}") + assert_positive_infinite_calculation { BigMath.exp(huge, 20) } + assert_underflow_calculation { BigMath.exp(-huge, 20) } + end + def test_exp_with_nan assert_nan_calculation { BigMath.exp(BigDecimal::NAN, 20) } end diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 3cb5bf00..ed1b8be1 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -521,7 +521,8 @@ def test_erfc end assert_equal(0, BigMath.erfc(PINF, N)) assert_equal(2, BigMath.erfc(MINF, N)) - assert_equal(0, BigMath.erfc(BigDecimal('1e400'), 10)) + assert_underflow_calculation(accept_overflow: true) { BigMath.erfc(4999999999, 10) } + assert_underflow_calculation { BigMath.erfc(BigDecimal('1e400'), 10) } assert_equal(2, BigMath.erfc(BigDecimal('-1e400'), 10)) assert_equal(1, BigMath.erfc(BigDecimal('1e-400'), N))