diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..5ce8fde8 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,50 @@ +name: Benchmarking + +on: + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + - reopened + +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 + + - run: rake compile + + - run: rake benchmark diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad0328be..87658190 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: @@ -14,20 +20,19 @@ jobs: os: - ubuntu-20.04 - ubuntu-18.04 - - macos-11.0 - macos-10.15 - windows-latest ruby: + - "3.0" # Quoted to avoid 3.0 becoming "3" as a String. - 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: 3.0 } - { os: windows-latest , ruby: debug } steps: @@ -42,8 +47,9 @@ jobs: - run: rake compile - - run: rake build - - run: rake test + - run: rake build + - run: gem install pkg/*.gem + if: ${{ ( matrix.ruby != 'debug' && matrix.os == 'linux-latest' ) || ( matrix.ruby != 'debug' && matrix.os == 'macos-latest' ) }} diff --git a/CHANGES.md b/CHANGES.md index 015e7d35..209572c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,71 @@ # CHANGES +## 3.1.0 + +* Improve documentation [GH-209] + + **Burdette Lamar** + +* Let BigDecimal#quo accept precision. [GH-214] [Bug #8826] + + 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] + + 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** + +* 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. [GH-180] + + **Kenta Murata** + +* 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`. diff --git a/Gemfile b/Gemfile index ed7382e0..06413ee9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,11 @@ 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" +gem "test-unit" 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 | diff --git a/Rakefile b/Rakefile index 671e6bb6..e8667eb2 100644 --- a/Rakefile +++ b/Rakefile @@ -10,9 +10,54 @@ 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 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 + +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/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/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) diff --git a/benchmark/from_large_integer.yml b/benchmark/from_large_integer.yml new file mode 100644 index 00000000..58437fe0 --- /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 + + int_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + +benchmark: + int_n10000: BigDecimal(int_n10000) diff --git a/benchmark/from_small_integer.yml b/benchmark/from_small_integer.yml new file mode 100644 index 00000000..26ce4107 --- /dev/null +++ b/benchmark/from_small_integer.yml @@ -0,0 +1,21 @@ +loop_count: 100000 + +contexts: +- gems: + bigdecimal: 3.0.0 +- name: "master" + prelude: |- + $LOAD_PATH.unshift(File.expand_path("lib")) + require "bigdecimal" + +prelude: |- + figs = (0..9).to_a + + 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/bigdecimal.gemspec b/bigdecimal.gemspec index 7e5388d8..2b1ff6a6 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,10 +1,8 @@ # coding: utf-8 -bigdecimal_version = '3.0.0' - Gem::Specification.new do |s| s.name = "bigdecimal" - s.version = bigdecimal_version + s.version = "3.1.0" s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"] @@ -19,6 +17,12 @@ 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/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 @@ -30,10 +34,5 @@ Gem::Specification.new do |s| sample/pi.rb ] - s.required_ruby_version = Gem::Requirement.new(">= 2.4.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" + s.required_ruby_version = Gem::Requirement.new(">= 2.5.0") 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 diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4e49de8e..5af6c7c5 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -30,14 +30,11 @@ #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) @@ -65,20 +62,22 @@ 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)) -#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 # define RRATIONAL_ZERO_P(x) (FIXNUM_P(rb_rational_num(x)) && \ @@ -103,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) @@ -164,7 +115,9 @@ rb_complex_imag(VALUE cmp) */ 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 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); @@ -176,6 +129,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) { @@ -186,7 +145,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 @@ -208,21 +167,6 @@ is_kind_of_BigDecimal(VALUE const v) return rb_typeddata_is_kind_of(v, &BigDecimal_data_type); } -static VALUE -ToValue(Real *p) -{ - if (VpIsNaN(p)) { - VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'(Not a Number)", 0); - } - else if (VpIsPosInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", 0); - } - else if (VpIsNegInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", 0); - } - return p->obj; -} - NORETURN(static void cannot_be_coerced_into_BigDecimal(VALUE, VALUE)); static void @@ -242,95 +186,67 @@ 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); +static VALUE rb_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; - char szD[128]; - VALUE orig = Qundef; - double d; + const size_t digs = prec < 0 ? SIZE_MAX : (size_t)prec; -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); - 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"); - } - return VpCreateRbObject(prec, "0"); + v = rb_float_convert_to_BigDecimal(v, digs, must); + break; 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(ToValue(pv), rb_rational_den(v), LONG2NUM(prec)); - goto again; - } - - v = orig; - goto SomeOneMayDoIt; + 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: - sprintf(szD, "%ld", FIX2LONG(v)); - return VpCreateRbObject(VpBaseFig() * 2 + 1, szD); + case T_FIXNUM: { + char szD[128]; + 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)); + 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: - bg = rb_big2str(v, 10); - PUSH(bg); - return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, - RSTRING_PTR(bg)); + 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; } + Real *vp; + TypedData_Get_Struct(v, Real, &BigDecimal_data_type, vp); + return vp; + SomeOneMayDoIt: if (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* @@ -340,11 +256,13 @@ 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: + * + * 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) @@ -353,7 +271,7 @@ BigDecimal_double_fig(VALUE self) } /* call-seq: - * big_decimal.precs -> array + * precs -> array * * Returns an Array of two Integer values that represent platform-dependent * internal storage properties. @@ -363,7 +281,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 @@ -383,73 +300,202 @@ 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) +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)) { + 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. - * Conversely, when the exponent is positive, the decimal point moves to rightward. + * 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[n-1] | + * |<----------| exponent == -2 | + * |---------------------------------->| precision + * |---------------------------------->| scale + * * - * | 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; - ssize_t precision; + + /* Count the number of decimal digits before frac[1]. */ + ssize_t n_digits_head = BASE_FIG; if (ex < 0) { - precision = (-ex + 1) * BASE_FIG; /* 1 is for p->frac[0] */ - ex = 0; + n_digits_head += (-ex) * BASE_FIG; /* The number of leading zeros before frac[0]. */ + ex = 0; } - else if (p->Prec > 0) { - BDIGIT x = p->frac[0]; - for (precision = 0; x > 0; x /= 10) { - ++precision; - } + else if (ex > 0) { + /* 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 */ } - if (ex > (ssize_t)p->Prec) { - 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; - precision += n * BASE_FIG; + if (out_scale) { + ssize_t scale = 0; - if (ex < (ssize_t)p->Prec) { - BDIGIT 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); +} + +/* + * 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 + * + * 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) { @@ -457,32 +503,38 @@ 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); - BDIGIT 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); } /* - * call-seq: hash + * call-seq: + * hash -> integer + * + * Returns the integer hash value for +self+. * - * Creates a hash for this BigDecimal. + * Two instances of \BigDecimal have the same hash value if and only if + * they have equal: + * + * - Sign. + * - Fractional part. + * - Exponent. * - * Two BigDecimals with equal sign, - * fractional part and exponent have the same hash. */ static VALUE BigDecimal_hash(VALUE self) @@ -495,23 +547,23 @@ 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); } /* - * call-seq: _dump + * call-seq: + * _dump -> string * - * 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) @@ -553,12 +605,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)); + GUARD_OBJ(pv, VpNewRbClass(m, (char *)pch, self, true, true)); m /= VpBaseFig(); if (m && pv->MaxPrec > m) { pv->MaxPrec = m+1; } - return ToValue(pv); + return VpCheckGetValue(pv); } static unsigned short @@ -645,42 +697,166 @@ 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 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:+. * */ static VALUE @@ -768,25 +944,54 @@ 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)) -#define VpReallocReal(ptr, prec) (Real *)VpMemRealloc((ptr), offsetof(Real, frac) + (prec) * sizeof(BDIGIT)) +#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) @@ -799,7 +1004,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; } @@ -838,15 +1043,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); @@ -869,7 +1066,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); @@ -1003,8 +1200,8 @@ BigDecimal_coerce(VALUE self, VALUE other) Real *b; if (RB_TYPE_P(other, T_FLOAT)) { - GUARD_OBJ(b, GetVpValueWithPrec(other, DBLE_FIG, 1)); - obj = rb_assoc_new(ToValue(b), self); + GUARD_OBJ(b, GetVpValueWithPrec(other, 0, 1)); + obj = rb_assoc_new(VpCheckGetValue(b), self); } else { if (RB_TYPE_P(other, T_RATIONAL)) { @@ -1021,12 +1218,14 @@ BigDecimal_coerce(VALUE self, VALUE other) } /* - * call-seq: - * +big_decimal -> big_decimal + * call-seq: + * +big_decimal -> self * - * Return self. + * Returns +self+: + * + * +BigDecimal(5) # => 0.5e1 + * +BigDecimal(-5) # => -0.5e1 * - * +BigDecimal('5') #=> 0.5e1 */ static VALUE @@ -1036,22 +1235,21 @@ BigDecimal_uplus(VALUE self) } /* - * Document-method: BigDecimal#add - * Document-method: BigDecimal#+ + * call-seq: + * self + value -> bigdecimal * - * call-seq: - * add(value, digits) + * Returns the \BigDecimal sum of +self+ and +value+: * - * Add the specified 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) * - * e.g. - * c = a.add(b,n) - * c = a + b + * See the {Note About Precision}[BigDecimal.html#class-BigDecimal-label-A+Note+About+Precision]. * - * 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) { @@ -1061,7 +1259,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); @@ -1078,11 +1276,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)); } @@ -1090,24 +1288,21 @@ BigDecimal_add(VALUE self, VALUE r) VpAddSub(c, a, b, 1); } } - return ToValue(c); + 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 @@ -1119,7 +1314,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); @@ -1136,11 +1331,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)); } @@ -1148,7 +1343,7 @@ BigDecimal_sub(VALUE self, VALUE r) VpAddSub(c, a, b, -1); } } - return ToValue(c); + return VpCheckGetValue(c); } static VALUE @@ -1169,7 +1364,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: @@ -1286,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: * - * Returns true if a is less than b. + * 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. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce). */ static VALUE BigDecimal_lt(VALUE self, VALUE r) @@ -1299,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: * - * Returns true if a is less than or equal to b. + * 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. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce). */ static VALUE BigDecimal_le(VALUE self, VALUE r) @@ -1312,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: + * + * b = BigDecimal('1.5') + * b > 1 # => true + * b > 1.0 # => true + * b > Rational(1, 1) # => true + * b > 2 # => false * - * Returns true if a is greater 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_gt(VALUE self, VALUE r) @@ -1325,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 a is greater than or equal to b. + * 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. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce) */ static VALUE BigDecimal_ge(VALUE self, VALUE r) @@ -1340,11 +1565,14 @@ BigDecimal_ge(VALUE self, VALUE r) /* * call-seq: - * -big_decimal -> big_decimal + * -self -> bigdecimal * - * Return the negation of self. + * Returns the \BigDecimal negation of self: + * + * b0 = BigDecimal('1.5') + * b1 = -b0 # => -0.15e1 + * b2 = -b1 # => 0.15e1 * - * -BigDecimal('5') #=> -0.5e1 */ static VALUE @@ -1353,26 +1581,11 @@ 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 ToValue(c); + 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) { @@ -1382,7 +1595,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); @@ -1395,71 +1608,122 @@ 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 ToValue(c); + return VpCheckGetValue(c); } 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); Real *a, *b; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); - if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + 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)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); - } - else { - b = GetVpValue(r, 0); + rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); + } + + 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; - 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")); - GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0")); + 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; } /* call-seq: - * a / b -> 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) +/* 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])); + } + 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_div(VALUE self, VALUE r) -/* For c = self/r: with round operation */ +BigDecimal_quo(int argc, VALUE *argv, VALUE self) { - ENTER(5); - Real *c=NULL, *res=NULL, *div = NULL; - r = BigDecimal_divide(&c, &res, &div, self, r); - 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], (BDIGIT)(VpBaseVal() * (BDIGIT_DBL)res->frac[0] / div->frac[0])); + 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 ToValue(c); + + return result; } /* @@ -1474,75 +1738,99 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) Real *a, *b; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); - if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + 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)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } - else { - b = GetVpValue(r, 0); + + if (!is_kind_of_BigDecimal(rr)) { + return Qfalse; } - if (!b) return Qfalse; + TypedData_Get_Struct(rr, Real, &BigDecimal_data_type, b); SAVE(b); 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")); - VpSetInf(d, (SIGNED_VALUE)(VpGetSign(a) == VpGetSign(b) ? 1 : -1)); - GUARD_OBJ(c, VpCreateRbObject(1, "NaN")); - *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")); - *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")); - GUARD_OBJ(d, VpCreateRbObject(1, "0")); - *div = d; - *mod = c; - 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")); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0")); + 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 > 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); - GUARD_OBJ(d, VpCreateRbObject(mx, "0")); + + 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); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0")); - VpAddSub(d, c, b, 1); - *div = res; - *mod = d; - } else { - *div = d; - *mod = c; + /* 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) * 2*BASE_FIG, "0", true)); + VpAddSub(d, c, b, 1); + *div = res; + *mod = d; + } + else { + *div = d; + *mod = c; } return Qtrue; -NaN: - GUARD_OBJ(c, VpCreateRbObject(1, "NaN")); - GUARD_OBJ(d, VpCreateRbObject(1, "NaN")); - *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; } @@ -1562,7 +1850,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, '%'); } @@ -1577,7 +1865,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); @@ -1590,17 +1878,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 */ @@ -1627,7 +1915,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,13 +1948,14 @@ 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")); } /* - * 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) @@ -1678,7 +1967,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")); } @@ -1692,18 +1981,25 @@ 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")); + GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0", true)); GUARD_OBJ(av, GetVpValue(self, 1)); - GUARD_OBJ(bv, GetVpValue(b, 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; + } + 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")); + GUARD_OBJ(res, VpCreateRbObject((mx * 2 + 2)*VpBaseFig(), "#0", true)); VpDivd(cv, res, av, bv); VpSetPrecLimit(pl); VpLeftRound(cv, VpGetRoundMode(), ix); - return ToValue(cv); + return VpCheckGetValue(cv); } } @@ -1711,6 +2007,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. @@ -1725,6 +2022,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") @@ -1748,6 +2048,31 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) return BigDecimal_div2(self, b, n); } + /* + * call-seq: + * add(value, ndigits) -> new_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 + * + */ + static VALUE BigDecimal_add2(VALUE self, VALUE b, VALUE n) { @@ -1761,7 +2086,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,10 +2116,35 @@ 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); } } + /* + * 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) @@ -1809,18 +2159,19 @@ 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); } } /* * 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 @@ -1832,10 +2183,10 @@ 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 ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1857,9 +2208,9 @@ 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 ToValue(c); + return VpCheckGetValue(c); } /* Return the integer part of the number, as a BigDecimal. @@ -1873,9 +2224,9 @@ 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 ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1946,13 +2297,13 @@ 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) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1992,13 +2343,13 @@ 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) { - 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. @@ -2012,9 +2363,9 @@ 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 ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -2052,16 +2403,16 @@ 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 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: @@ -2098,13 +2449,13 @@ 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) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -2400,13 +2751,13 @@ 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; if (VpIsZero(exp)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } log_x = BigMath_log(x->obj, SSIZET2NUM(n+1)); @@ -2444,10 +2795,10 @@ 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 ToValue(y); + return VpCheckGetValue(y); } retry: @@ -2470,9 +2821,9 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) goto retry; } if (NIL_P(prec)) { - n += DBLE_FIG; + n += BIGDECIMAL_DOUBLE_FIGURES; } - exp = GetVpValueWithPrec(vexp, DBLE_FIG, 1); + exp = GetVpValueWithPrec(vexp, 0, 1); break; case T_RATIONAL: @@ -2516,7 +2867,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)) { @@ -2538,18 +2889,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", true)); } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } if (is_zero(vexp)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } else if (is_one(vexp)) { return self; @@ -2561,24 +2912,24 @@ 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", true)); } else { /* (-Infinity) ** (-odd_integer) -> -0 */ - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } } else { /* (-Infinity) ** (-non_integer) -> -0 */ - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } } else { - return ToValue(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)) { @@ -2597,52 +2948,52 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { VpSetPosInf(y); } - return ToValue(y); + return VpCheckGetValue(y); } } 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); if (is_one(abs_value)) { - return ToValue(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)); } 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", true)); } else { - return ToValue(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)); } 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", true)); } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } } @@ -2654,24 +3005,30 @@ 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); + VpPowerByInt(y, x, int_exp); if (!NIL_P(prec) && VpIsDef(y)) { VpMidRound(y, VpGetRoundMode(), n); } - return ToValue(y); + 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) @@ -2725,182 +3082,483 @@ 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); + VpCheckGetValue(vp); /* VpCheckGetValue performs exception check */ + + return bd; +} - argc = rb_scan_args(argc, argv, "11:", &iniValue, &nFig, &opts); - exc = opts_exception_p(opts); +static VALUE +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); - if (argc == 1) { - mf = 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] = (DECDIG)uval; } else { - /* expand GetPrecisionInt for exception suppression */ - ssize_t n = NUM2INT(nFig); - if (n < 0) { - if (!exc) { - return NULL; + DECDIG buf[BIGDECIMAL_INT64_MAX_LENGTH] = {0,}; + 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; } - rb_raise(rb_eArgError, "negative precision"); } - 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; + for (; uval > 0; ++len) { + // Store digits + buf[BIGDECIMAL_INT64_MAX_LENGTH - len - 1] = r; + uval /= BASE; + r = uval % BASE; } + + 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 - len, DECDIG, len); } - retry: - switch (TYPE(iniValue)) { - case T_DATA: - if (is_kind_of_BigDecimal(iniValue)) { - return DATA_PTR(iniValue); - } - break; + return BigDecimal_wrap_struct(obj, vp); +} - case T_FIXNUM: - /* fall through */ - case T_BIGNUM: - return GetVpValue(iniValue, 1); +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; +} - case T_FLOAT: - d = RFLOAT_VALUE(iniValue); - if (!isfinite(d)) { - Real *pv = VpCreateRbObject(1, NULL); - VpDtoV(pv, d); - return pv; +static VALUE +rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + assert(RB_TYPE_P(val, T_BIGNUM)); + + 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); } - 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); + 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 { + 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); + } +} - 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; +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) +{ + assert(RB_FLOAT_TYPE_P(val)); + + double d = RFLOAT_VALUE(val); + + 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) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, + "can't omit precision for a %"PRIsVALUE".", + CLASS_OF(val)); + } + 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[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 > BIGDECIMAL_DOUBLE_FIGURES) { + /* TODO: Presumably, rounding should be done here. */ + len10 = BIGDECIMAL_DOUBLE_FIGURES; + } + 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 +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; + rb_raise(rb_eArgError, + "can't omit precision for a %"PRIsVALUE".", + CLASS_OF(val)); + } + + 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 +rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception) +{ + if (digs == SIZE_MAX) + digs = 0; + + 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) +{ + switch (val) { + case Qnil: + case Qtrue: + case Qfalse: + 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; - case T_STRING: - /* fall through */ default: - break; + break; + } + + if (is_kind_of_BigDecimal(val)) { + if (digs == SIZE_MAX) + return check_exception(val); + + 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); + /* TODO: rounding */ + BigDecimal_wrap_struct(copy, vp); + return VpCheckGetValue(vp); + } + else if (RB_INTEGER_TYPE_P(val)) { + return rb_inum_convert_to_BigDecimal(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_FLOAT_TYPE_P(val)) { + return rb_float_convert_to_BigDecimal(val, digs, raise_exception); } - StringValueCStr(iniValue); - return VpAlloc(mf, RSTRING_PTR(iniValue), 1, exc); + 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"); + } + 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); + } + + /* TODO: chheck to_d */ + /* TODO: chheck to_int */ + + 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(str, digs, raise_exception); } -/* call-seq: - * BigDecimal(initial, digits, exception: true) +/* call-seq: + * BigDecimal(value, exception: true) -> bigdecimal + * BigDecimal(value, ndigits, exception: true) -> bigdecimal * - * Create a new BigDecimal object. + * Returns the \BigDecimal converted from +value+ + * with a precision of +ndigits+ decimal digits. * - * initial:: The initial value, as an Integer, a Float, a Rational, - * a BigDecimal, or a String. + * 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 or 0, - * the number of significant digits is determined from the initial - * value. + * - 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) { - 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]; + 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); + } } - 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; + + return rb_convert_to_BigDecimal(val, digs, exception); } 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; + char const *c_str = StringValueCStr(str); + Real *vp = VpNewRbClass(0, c_str, klass, false, true); + if (!vp) + return Qnil; + else + return VpCheckGetValue(vp); } /* call-seq: @@ -3084,7 +3742,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; @@ -3097,29 +3755,29 @@ 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"); + vy = VpCreateRbObject(prec, "#0", true); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); RB_GC_GUARD(vy->obj); - return ToValue(vy); + return VpCheckGetValue(vy); } } else if (nan) { Real* vy; - vy = VpCreateRbObject(prec, "#0"); + vy = VpCreateRbObject(prec, "#0", true); 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); } 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); @@ -3129,7 +3787,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) VpSetSign(vx, 1); } - one = ToValue(VpCreateRbObject(1, "1")); + one = VpCheckGetValue(VpCreateRbObject(1, "1", true)); y = one; d = y; i = 1; @@ -3144,8 +3802,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 */ @@ -3237,7 +3895,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; @@ -3257,17 +3915,17 @@ 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 ToValue(vy); + 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 ToValue(vy); + return VpCheckGetValue(vy); } else if (zero || negative) { rb_raise(rb_eMathDomainError, @@ -3276,18 +3934,18 @@ 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", 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) { 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, true)), vn); } else { expo = 0; @@ -3305,8 +3963,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); @@ -3319,7 +3977,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); } @@ -3327,6 +3985,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. * @@ -3370,6 +4068,18 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) * * (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 @@ -3598,17 +4308,40 @@ 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); 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); @@ -3627,7 +4360,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); @@ -3718,9 +4451,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); @@ -3736,9 +4469,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 */ @@ -3749,11 +4479,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 @@ -3779,7 +4505,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, \ @@ -3795,8 +4521,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); @@ -3805,20 +4531,41 @@ VpGetException (void) static void VpSetException(unsigned short f) { - rmpd_set_thread_local_exception_mode(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. */ -#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 @@ -3830,8 +4577,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); @@ -3841,7 +4588,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; } @@ -3849,7 +4596,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, \ @@ -3865,8 +4612,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); @@ -3894,7 +4641,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; } @@ -3971,7 +4718,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) { @@ -3983,7 +4730,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); } @@ -4132,13 +4878,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 + * BIGDECIMAL_DOUBLE_FIGURES ... OK */ VP_EXPORT size_t -VpInit(BDIGIT BaseVal) +VpInit(DECDIG BaseVal) { /* Setup +/- Inf NaN -0 */ VpGetDoubleNegZero(); @@ -4153,16 +4899,16 @@ 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("\tBIGDECIMAL_DOUBLE_FIGURES = %d\n", BIGDECIMAL_DOUBLE_FIGURES); } #endif /* BIGDECIMAL_DEBUG */ - return rmpd_double_figures(); + return BIGDECIMAL_DOUBLE_FIGURES; } VP_EXPORT Real * @@ -4210,7 +4956,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; @@ -4305,14 +5051,13 @@ 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; } /* Check on Inf & NaN */ - if ((vp = rmpd_parse_special_string(szVal)) != NULL) { + if ((vp = bigdecimal_parse_special_string(szVal)) != NULL) { return vp; } @@ -4481,7 +5226,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); @@ -4520,7 +5264,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 */ @@ -4551,7 +5295,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) { @@ -4679,7 +5423,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; @@ -4689,7 +5433,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) { @@ -4774,7 +5518,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; @@ -4784,7 +5528,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) { @@ -4891,7 +5635,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; @@ -5006,8 +5750,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 @@ -5061,7 +5805,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. */ @@ -5081,15 +5825,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) { @@ -5135,9 +5879,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) { @@ -5209,7 +5953,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; @@ -5246,17 +5990,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; @@ -5268,16 +6012,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) { @@ -5359,9 +6103,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; } @@ -5484,7 +6228,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, */ @@ -5519,7 +6263,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) { @@ -5628,7 +6372,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); @@ -5711,7 +6455,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; @@ -5759,7 +6503,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; @@ -5834,7 +6578,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] == '-') { @@ -5971,7 +6715,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; @@ -6035,7 +6779,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; @@ -6049,7 +6793,7 @@ VpDtoV(Real *m, double d) { size_t ind_m, mm; SIGNED_VALUE ne; - BDIGIT i; + DECDIG i; double val, val2; if (isnan(d)) { @@ -6084,12 +6828,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); @@ -6097,7 +6841,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 @@ -6236,7 +6980,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); @@ -6293,8 +7037,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; @@ -6316,8 +7060,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) @@ -6325,10 +7069,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. @@ -6354,7 +7098,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) @@ -6373,7 +7117,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 */ @@ -6401,11 +7145,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 { @@ -6453,7 +7197,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; @@ -6484,7 +7228,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; @@ -6534,7 +7278,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; @@ -6606,7 +7350,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; @@ -6693,8 +7437,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 */ @@ -6729,12 +7473,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 28f3363b..bd1c4674 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -10,62 +10,74 @@ #define RUBY_BIG_DECIMAL_H 1 #define RUBY_NO_OLD_COMPATIBILITY - #include "ruby/ruby.h" -#include +#include "missing.h" -#ifndef RB_UNUSED_VAR -# ifdef __GNUC__ -# define RB_UNUSED_VAR(x) x __attribute__ ((unused)) -# else -# define RB_UNUSED_VAR(x) x -# endif +#ifdef HAVE_FLOAT_H +# include #endif -#ifndef UNREACHABLE -# define UNREACHABLE /* unreachable */ -#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 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 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 -#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" +#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_DOUBLE_FIGURES (1+DBL_DIG) #if defined(__cplusplus) extern "C" { @@ -74,115 +86,8 @@ 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 -# define RMPD_COMPONENT_FIGURES 38 -# define RMPD_BASE ((BDIGIT)100000000000000000000000000000000000000U) -#elif SIZEOF_BDIGITS >= 8 -# define RMPD_COMPONENT_FIGURES 19 -# define RMPD_BASE ((BDIGIT)10000000000000000000U) -#elif SIZEOF_BDIGITS >= 4 -# define RMPD_COMPONENT_FIGURES 9 -# define RMPD_BASE ((BDIGIT)1000000000U) -#elif SIZEOF_BDIGITS >= 2 -# define RMPD_COMPONENT_FIGURES 4 -# define RMPD_BASE ((BDIGIT)10000U) -#else -# define RMPD_COMPONENT_FIGURES 2 -# define RMPD_BASE ((BDIGIT)100U) -#endif - - /* * NaN & Infinity */ @@ -207,9 +112,8 @@ 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 +#define BIGDECIMAL_EXCEPTION_MODE_DEFAULT 0U /* Computation mode */ #define VP_ROUND_MODE ((unsigned short)0x0100) @@ -221,7 +125,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 */ @@ -263,7 +167,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; /* @@ -272,21 +176,13 @@ typedef struct { * ------------------ */ -VP_EXPORT Real * -VpNewRbClass(size_t mx, char const *str, VALUE klass); - -VP_EXPORT Real *VpCreateRbObject(size_t mx,const char *str); +VP_EXPORT Real *VpNewRbClass(size_t mx, char const *str, VALUE klass, bool strict_p, bool raise_exception); -static inline BDIGIT -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; } +VP_EXPORT Real *VpCreateRbObject(size_t mx, const char *str, bool raise_exception); -#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); @@ -308,7 +204,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); @@ -334,7 +230,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); diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h new file mode 100644 index 00000000..6e1e4776 --- /dev/null +++ b/ext/bigdecimal/bits.h @@ -0,0 +1,141 @@ +#ifndef BIGDECIMAL_BITS_H +#define BIGDECIMAL_BITS_H + +#include "feature.h" +#include "static_assert.h" + +#if defined(__x86_64__) && defined(HAVE_X86INTRIN_H) +# include /* for _lzcnt_u64, etc. */ +#elif defined(_MSC_VER) && defined(HAVE_INTRIN_H) +# include /* for the following intrinsics */ +#endif + +#if defined(_MSC_VER) && defined(__AVX2__) +# pragma intrinsic(__lzcnt) +# 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) : \ + (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__) && 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(HAVE__LZCNT_U32) + return (unsigned int)_lzcnt_u32(x); + +#elif defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE) + 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__) && defined(HAVE___LZCNT64) + return (unsigned int)__lzcnt64(x); + +#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) + unsigned long r; + return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; + +#elif __has_builtin(__builtin_clzl) && __has_builtin(__builtin_clzll) && !(defined(__sun) && defined(__sparc)) + 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 63123e28..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('.') @@ -16,6 +13,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/missing.c b/ext/bigdecimal/missing.c new file mode 100644 index 00000000..703232d9 --- /dev/null +++ b/ext/bigdecimal/missing.c @@ -0,0 +1,27 @@ +#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 + +#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 +#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 new file mode 100644 index 00000000..79698491 --- /dev/null +++ b/ext/bigdecimal/missing.h @@ -0,0 +1,235 @@ +#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 defined(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 + +/* 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 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 */ 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 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/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/testbase.rb b/test/bigdecimal/helper.rb similarity index 75% rename from test/bigdecimal/testbase.rb rename to test/bigdecimal/helper.rb index 22b05f09..46721fb9 100644 --- a/test/bigdecimal/testbase.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 445a3f2e..bbfcbec4 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1,10 +1,30 @@ # frozen_string_literal: false -require_relative "testbase" +require_relative "helper" require 'bigdecimal/math' 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] + 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, + "INT64_MAX" => 9223372036854775807, + "UINT64_MAX" => 18446744073709551615, + }.freeze + end + ROUNDING_MODE_MAP = [ [ BigDecimal::ROUND_UP, :up], [ BigDecimal::ROUND_DOWN, :down], @@ -80,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', @@ -104,10 +137,21 @@ 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(LIMITS["FIXNUM_MIN"].to_s), BigDecimal(LIMITS["FIXNUM_MIN"])) + + assert_equal(BigDecimal(LIMITS["FIXNUM_MAX"].to_s), BigDecimal(LIMITS["FIXNUM_MAX"])) + + assert_equal(BigDecimal(LIMITS["INT64_MIN"].to_s), BigDecimal(LIMITS["INT64_MIN"])) + + assert_equal(BigDecimal(LIMITS["INT64_MAX"].to_s), BigDecimal(LIMITS["INT64_MAX"])) + + assert_equal(BigDecimal(LIMITS["UINT64_MAX"].to_s), BigDecimal(LIMITS["UINT64_MAX"])) end def test_BigDecimal_with_rational @@ -119,21 +163,28 @@ 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) } + 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 @@ -201,9 +252,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)) } @@ -213,6 +273,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 @@ -612,6 +675,7 @@ def test_cmp_data 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 @@ -868,6 +932,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 @@ -888,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")) @@ -900,18 +969,37 @@ 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_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) end 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) @@ -955,6 +1043,21 @@ 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) + + 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 + def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) @@ -988,6 +1091,40 @@ 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_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) @@ -1491,16 +1628,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}") @@ -1919,6 +2057,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) @@ -1928,10 +2070,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) @@ -1939,12 +2085,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) @@ -1962,6 +2104,70 @@ 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_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) @@ -2022,6 +2228,15 @@ def test_initialize_copy_dup_clone_frozen_error assert_raise(err) { bd.send(:initialize_dup, bd2) } end + def test_llong_min_gh_200 + # https://github.com/ruby/bigdecimal/issues/199 + # Between LLONG_MIN and -ULLONG_MAX + assert_equal(BigDecimal(LIMITS["LLONG_MIN"].to_s), BigDecimal(LIMITS["LLONG_MIN"]), "[GH-200]") + + minus_ullong_max = -LIMITS["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) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" super(["-rbigdecimal"], diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index b855fd58..2f27163e 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) @@ -18,11 +19,15 @@ 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("65.6", 65.6.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) @@ -31,6 +36,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 @@ -49,6 +56,23 @@ 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_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 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..798cc494 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require_relative "testbase" +require_relative "helper" class TestBigDecimalRactor < Test::Unit::TestCase include TestBigDecimalBase def setup super - skip unless defined? Ractor + omit unless defined? Ractor end def test_ractor_shareable diff --git a/test/lib/test/unit/assertions.rb b/test/lib/core_assertions.rb similarity index 56% rename from test/lib/test/unit/assertions.rb rename to test/lib/core_assertions.rb index 8eb80574..a4abff41 100644 --- a/test/lib/test/unit/assertions.rb +++ b/test/lib/core_assertions.rb @@ -1,147 +1,161 @@ -# frozen_string_literal: false -require 'minitest/unit' -require 'pp' +# frozen_string_literal: true module Test module Unit module Assertions - include MiniTest::Assertions + def _assertions= n # :nodoc: + @_assertions = n + end - def mu_pp(obj) #:nodoc: - obj.pretty_inspect.chomp + def _assertions # :nodoc: + @_assertions ||= 0 end - MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: + ## + # Returns a proc that will output +msg+ along with the default message. - # :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 + 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 - # :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 + module CoreAssertions + if defined?(MiniTest) + require_relative '../../envutil' + # for ruby core testing + include MiniTest::Assertions - # :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 + # Compatibility hack for assert_raise + Test::Unit::AssertionFailedError = MiniTest::Assertion + else + module MiniTest + class Assertion < Exception; end + class Skip < Assertion; end 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 + require 'pp' + require_relative 'envutil' + include Test::Unit::Assertions + end - exp = exp.first if exp.size == 1 + def mu_pp(obj) #:nodoc: + obj.pretty_inspect.chomp + end - flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) + def assert_file + AssertFile end - def assert_raises(*exp, &b) - raise NoMethodError, "use assert_raise", caller + FailDesc = proc do |status, message = "", out = ""| + now = Time.now + proc do + EnvUtil.failure_description(status, now, message, out) + 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 + 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 - raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" + 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 - ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) {yield} - msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} + 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 - if assert == :assert_equal - assert_equal(expected, ex.message, msg) - else - msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp ex.message}" } - assert expected =~ ex.message, msg - block.binding.eval("proc{|_|$~=_}").call($~) + 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::MJIT) && RubyVM::MJIT.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 - ex + 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: @@ -180,36 +194,162 @@ def assert_nothing_raised(*args) 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)}>" } + 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 - nil 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) + 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) + 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}" + } + 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[:ios] = [res_c] + end + src = < error - raise error if /\Auncaught throw (.+)\z/m !~ error.message - msg = message(msg) { "<#{$1}> was thrown when nothing was expected" } - flunk(msg) + res = Marshal.load(res.unpack1("m")) + rescue => marshal_error + ignore_stderr = nil + res = nil end - assert(true, "Expected nothing to be thrown") - ret + 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: @@ -242,446 +382,172 @@ def assert_throw(tag, msg = nil) 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 - if priv - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv}" - } - return assert obj.respond_to?(meth, priv), msg - end - #get rid of overcounting - super if !caller[0].rindex(MINI_DIR, 0) || !obj.respond_to?(meth) - 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 ) + # assert_raise( *args, &block ) # - # Passes if the method send doesn't return a true value. + #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. # - # +send_array+ is composed of: - # * A receiver - # * A method - # * Arguments to the method + # assert_raise do #Fails, no Exceptions are raised + # end # - # 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_' << 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 - - def assert_valid_syntax(code, fname = caller_locations(1, 1)[0], mesg = fname.to_s, verbose: nil) - code = code.b - code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { - "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" - } - code.force_encoding(Encoding::UTF_8) - verbose, $VERBOSE = $VERBOSE, verbose - yield if defined?(yield) - case - when Array === fname - fname, line = *fname - when defined?(fname.path) && defined?(fname.lineno) - fname, line = fname.path, fname.lineno - else - line = 0 - end - assert_nothing_raised(SyntaxError, mesg) do - assert_equal(:ok, catch {|tag| eval(code, binding, fname, line)}, mesg) - end - ensure - $VERBOSE = verbose - end - - def assert_syntax_error(code, error, fname = caller_locations(1, 1)[0], mesg = fname.to_s) - code = code.b - code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { - "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ng}\n" - } - code.force_encoding(Encoding::US_ASCII) - verbose, $VERBOSE = $VERBOSE, nil - yield if defined?(yield) - case - when Array === fname - fname, line = *fname - when defined?(fname.path) && defined?(fname.lineno) - fname, line = fname.path, fname.lineno - else - line = 0 - end - e = assert_raise(SyntaxError, mesg) do - catch {|tag| eval(code, binding, fname, line)} - end - assert_match(error, e.message, mesg) - ensure - $VERBOSE = verbose - 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, EnvUtil.rubybin, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc << " (core dumped)" - end - full_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 << "\n" if /\n\z/ !~ full_message - end - if log - full_message << "\n#{log.b.gsub(/^/, '| ')}" - end - full_message + # 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 - faildesc - end - def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, **opt) - stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) - if signo = status.termsig - EnvUtil.diagnostic_reports(Signal.signame(signo), EnvUtil.rubybin, status.pid, Time.now) - end - 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(message) 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 + 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 - end - status - end - 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 + assert expected, proc { + flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"}) + } - ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") + return e + ensure + unless e + exp = exp.first if exp.size == 1 - 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 + flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) + end end - line -= 5 # lines until src - src = < marshal_error - ignore_stderr = nil + 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 - 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) + + 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 - raise res + m = ex.message end + msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} - # 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]) + 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 - assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) - raise marshal_error if marshal_error + ex end - def assert_warning(pat, msg = nil) - stderr = EnvUtil.verbose_warning { yield } - msg = message(msg) {diff pat, stderr} - assert(pat === stderr, msg) - end + MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: - def assert_warn(*args) - assert_warning(*args) {$VERBOSE = false; yield} + # :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 - def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) - require_relative '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 + # :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 - 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}"}) + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + return if obj.respond_to?(meth) end - rescue LoadError - skip - end - - def assert_is_minus_zero(f) - assert(1.0/f == -Float::INFINITY, "#{f} is not -0.0") + super(obj, meth, msg) end - def assert_file - AssertFile + # :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 :*. @@ -705,13 +571,13 @@ def assert_pattern_list(pattern_list, actual, message=nil) msg = message(msg) { expect_msg = "Expected #{mu_pp pattern}\n" if /\n[^\n]/ =~ rest - actual_mesg = "to match\n" + 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}" + actual_mesg = "to match " + mu_pp(rest) end actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" expect_msg + actual_mesg @@ -727,40 +593,38 @@ def assert_pattern_list(pattern_list, actual, message=nil) 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 + 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 - 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 + + def assert_deprecated_warn(mesg = /deprecated/) + assert_warn(mesg) do + Warning[:deprecated] = true + yield end - values end class << (AssertFile = Struct.new(:failure_message).new) - include Assertions + include CoreAssertions def assert_file_predicate(predicate, *args) if /\Anot_/ =~ predicate predicate = $' @@ -768,7 +632,7 @@ def assert_file_predicate(predicate, *args) end result = File.__send__(predicate, *args) result = !result if neg - mesg = "Expected file " << args.shift.inspect + 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 @@ -796,12 +660,24 @@ def for(key) @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.gsub(/^/, ' | ')}" + v = v.message + "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}" }.join("\n") end @@ -810,17 +686,45 @@ def pass? end end - def all_assertions(msg = nil) - all = AllFailures.new - yield all + # 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 - assert(all.pass?, message(msg) {all.message}) + 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 build_message(head, template=nil, *arguments) #:nodoc: - template &&= template.chomp - template.gsub(/\G((?:[^\\]|\\.)*?)(\\)?\?/) { $1 + ($2 ? "?" : mu_pp(arguments.shift)) } + 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 @@ -839,6 +743,22 @@ def message(msg = nil, *args, &default) # :nodoc: super 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 end end end diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb index a0b907c2..0391b90c 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, + stdout_filter: nil, stderr_filter: nil, ios: 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 @@ -50,17 +141,25 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = 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 + 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 +170,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 +182,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 +193,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 +201,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 +242,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 +271,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 +284,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 +308,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!(/(?$()]/ ? 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/parallel.rb b/test/lib/test/unit/parallel.rb deleted file mode 100644 index 1e12d184..00000000 --- a/test/lib/test/unit/parallel.rb +++ /dev/null @@ -1,191 +0,0 @@ -# frozen_string_literal: false -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../.." -require 'test/unit' - -module Test - module Unit - class Worker < Runner # :nodoc: - class << self - undef autorun - end - - alias orig_run_suite mini_run_suite - undef _run_suite - undef _run_suites - undef run - - def increment_io(orig) # :nodoc: - *rest, io = 32.times.inject([orig.dup]){|ios, | ios << ios.last.dup } - rest.each(&:close) - io - end - - def _run_suites(suites, type) # :nodoc: - suites.map do |suite| - _run_suite(suite, type) - end - end - - def _run_suite(suite, type) # :nodoc: - @partial_report = [] - orig_testout = MiniTest::Unit.output - i,o = IO.pipe - - MiniTest::Unit.output = o - orig_stdin, orig_stdout = $stdin, $stdout - - th = Thread.new do - begin - while buf = (self.verbose ? i.gets : i.readpartial(1024)) - _report "p", buf - end - rescue IOError - rescue Errno::EPIPE - end - end - - e, f, s = @errors, @failures, @skips - - begin - result = orig_run_suite(suite, type) - rescue Interrupt - @need_exit = true - result = [nil,nil] - end - - MiniTest::Unit.output = orig_testout - $stdin = orig_stdin - $stdout = orig_stdout - - o.close - begin - th.join - rescue IOError - raise unless ["stream closed","closed stream"].include? $!.message - end - i.close - - result << @partial_report - @partial_report = nil - result << [@errors-e,@failures-f,@skips-s] - result << ($: - @old_loadpath) - result << suite.name - - begin - _report "done", Marshal.dump(result) - rescue Errno::EPIPE; end - return result - ensure - MiniTest::Unit.output = orig_stdout - $stdin = orig_stdin if orig_stdin - $stdout = orig_stdout if orig_stdout - o.close if o && !o.closed? - i.close if i && !i.closed? - end - - def run(args = []) # :nodoc: - process_args args - @@stop_auto_run = true - @opts = @options.dup - @need_exit = false - - @old_loadpath = [] - begin - begin - @stdout = increment_io(STDOUT) - @stdin = increment_io(STDIN) - rescue - exit 2 - end - exit 2 unless @stdout && @stdin - - @stdout.sync = true - _report "ready!" - while buf = @stdin.gets - case buf.chomp - when /^loadpath (.+?)$/ - @old_loadpath = $:.dup - $:.push(*Marshal.load($1.unpack("m")[0].force_encoding("ASCII-8BIT"))).uniq! - when /^run (.+?) (.+?)$/ - _report "okay" - - @options = @opts.dup - suites = MiniTest::Unit::TestCase.test_suites - - begin - require File.realpath($1) - rescue LoadError - _report "after", Marshal.dump([$1, ProxyError.new($!)]) - _report "ready" - next - end - _run_suites MiniTest::Unit::TestCase.test_suites-suites, $2.to_sym - - if @need_exit - begin - _report "bye" - rescue Errno::EPIPE; end - exit - else - _report "ready" - end - when /^quit$/ - begin - _report "bye" - rescue Errno::EPIPE; end - exit - end - end - rescue Errno::EPIPE - rescue Exception => 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