Skip to content

Commit 5823261

Browse files
authored
Merge pull request #178 from ruby/conversion_from_small_integers
Implement special conversions for 64-bit integers
2 parents 271cebe + 9bf7a04 commit 5823261

File tree

9 files changed

+395
-8
lines changed

9 files changed

+395
-8
lines changed

.github/workflows/benchmark.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
run: |
3939
bundle install
4040
gem install bigdecimal -v 3.0.0
41-
rake install
41+
42+
- run: rake compile
4243

4344
- run: rake benchmark

benchmark/from_integer.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ contexts:
1111
prelude: |-
1212
figs = (0..9).to_a
1313
14+
big_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x }
15+
big_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x }
1416
big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x }
1517
1618
benchmark:
19+
big_n9: BigDecimal(big_n9)
20+
big_n19: BigDecimal(big_n19)
1721
big_n10000: BigDecimal(big_n10000)

bigdecimal.gemspec

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Gem::Specification.new do |s|
1919
bigdecimal.gemspec
2020
ext/bigdecimal/bigdecimal.c
2121
ext/bigdecimal/bigdecimal.h
22+
ext/bigdecimal/bits.h
23+
ext/bigdecimal/feature.h
24+
ext/bigdecimal/static_assert.h
2225
lib/bigdecimal.rb
2326
lib/bigdecimal/jacobian.rb
2427
lib/bigdecimal/ludcmp.rb
@@ -33,6 +36,7 @@ Gem::Specification.new do |s|
3336
s.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
3437

3538
s.add_development_dependency "benchmark_driver"
39+
s.add_development_dependency "fiddle"
3640
s.add_development_dependency "rake", ">= 12.3.3"
3741
s.add_development_dependency "rake-compiler", ">= 0.9"
3842
s.add_development_dependency "minitest", "< 5.0.0"

ext/bigdecimal/bigdecimal.c

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,19 @@
3030
#include <ieeefp.h>
3131
#endif
3232

33+
#include "bits.h"
34+
#include "static_assert.h"
35+
3336
/* #define ENABLE_NUMERIC_STRING */
3437

35-
#define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \
36-
(a) == 0 ? 0 : \
37-
(a) == -1 ? (b) < -(max) : \
38-
(a) > 0 ? \
39-
((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \
40-
((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b)))
4138
#define SIGNED_VALUE_MAX INTPTR_MAX
4239
#define SIGNED_VALUE_MIN INTPTR_MIN
4340
#define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX)
4441

42+
#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0])))
43+
#define roomof(x, y) (((x) + (y) - 1) / (y))
44+
#define type_roomof(x, y) roomof(sizeof(x), sizeof(y))
45+
4546
VALUE rb_cBigDecimal;
4647
VALUE rb_mBigMath;
4748

@@ -80,6 +81,8 @@ static ID id_half;
8081
#define DBLE_FIG rmpd_double_figures() /* figure of double */
8182
#endif
8283

84+
#define LOG10_2 0.3010299956639812
85+
8386
#ifndef RRATIONAL_ZERO_P
8487
# define RRATIONAL_ZERO_P(x) (FIXNUM_P(rb_rational_num(x)) && \
8588
FIX2LONG(rb_rational_num(x)) == 0)
@@ -2754,12 +2757,80 @@ check_exception(VALUE bd)
27542757
}
27552758

27562759
static VALUE
2757-
rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception)
2760+
rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int raise_exception)
2761+
{
2762+
VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0);
2763+
2764+
Real *vp;
2765+
if (uval == 0) {
2766+
vp = VpAllocReal(1);
2767+
vp->MaxPrec = 1;
2768+
vp->Prec = 1;
2769+
vp->exponent = 1;
2770+
VpSetZero(vp, 1);
2771+
vp->frac[0] = 0;
2772+
}
2773+
else if (uval < BASE) {
2774+
vp = VpAllocReal(1);
2775+
vp->MaxPrec = 1;
2776+
vp->Prec = 1;
2777+
vp->exponent = 1;
2778+
VpSetSign(vp, 1);
2779+
vp->frac[0] = (BDIGIT)uval;
2780+
}
2781+
else {
2782+
const size_t len10 = ceil(LOG10_2 * bit_length(uval));
2783+
size_t len = roomof(len10, BASE_FIG);
2784+
2785+
vp = VpAllocReal(len);
2786+
vp->MaxPrec = len;
2787+
vp->Prec = len;
2788+
vp->exponent = len;
2789+
VpSetSign(vp, 1);
2790+
2791+
size_t i;
2792+
for (i = 0; i < len; ++i) {
2793+
BDIGIT r = uval % BASE;
2794+
vp->frac[len - i - 1] = r;
2795+
uval /= BASE;
2796+
}
2797+
}
2798+
2799+
return BigDecimal_wrap_struct(obj, vp);
2800+
}
2801+
2802+
static VALUE
2803+
rb_int64_convert_to_BigDecimal(int64_t ival, size_t digs, int raise_exception)
2804+
{
2805+
const uint64_t uval = (ival < 0) ? (((uint64_t)-(ival+1))+1) : (uint64_t)ival;
2806+
VALUE bd = rb_uint64_convert_to_BigDecimal(uval, digs, raise_exception);
2807+
if (ival < 0) {
2808+
Real *vp;
2809+
TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp);
2810+
VpSetSign(vp, -1);
2811+
}
2812+
return bd;
2813+
}
2814+
2815+
static VALUE
2816+
rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception)
27582817
{
27592818
Real *vp = GetVpValue(val, 1);
27602819
return check_exception(vp->obj);
27612820
}
27622821

2822+
static VALUE
2823+
rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception)
2824+
{
2825+
assert(RB_INTEGER_TYPE_P(val));
2826+
if (FIXNUM_P(val)) {
2827+
return rb_int64_convert_to_BigDecimal(FIX2LONG(val), digs, raise_exception);
2828+
}
2829+
else {
2830+
return rb_big_convert_to_BigDecimal(val, digs, raise_exception);
2831+
}
2832+
}
2833+
27632834
static VALUE
27642835
rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception)
27652836
{

ext/bigdecimal/bits.h

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#ifndef BIGDECIMAL_BITS_H
2+
#define BIGDECIMAL_BITS_H
3+
4+
#include "feature.h"
5+
#include "static_assert.h"
6+
7+
#if defined(HAVE_X86INTRIN_H)
8+
# include <x86intrin.h> /* for _lzcnt_u64 */
9+
#elif defined(_MSC_VER) && _MSC_VER >= 1310
10+
# include <intrin.h> /* for the following intrinsics */
11+
#endif
12+
13+
#if defined(_MSC_VER) && defined(__AVX2__)
14+
# pragma intrinsic(__lzcnt)
15+
# pragma intrinsic(__lzcnt64)
16+
#endif
17+
18+
#define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \
19+
(a) == 0 ? 0 : \
20+
(a) == -1 ? (b) < -(max) : \
21+
(a) > 0 ? \
22+
((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \
23+
((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b)))
24+
25+
#ifdef HAVE_UINT128_T
26+
# define bit_length(x) \
27+
(unsigned int) \
28+
(sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \
29+
sizeof(x) <= sizeof(int64_t) ? 64 - nlz_int64((uint64_t)(x)) : \
30+
128 - nlz_int128((uint128_t)(x)))
31+
#else
32+
# define bit_length(x) \
33+
(unsigned int) \
34+
(sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \
35+
64 - nlz_int64((uint64_t)(x)))
36+
#endif
37+
38+
static inline unsigned nlz_int32(uint32_t x);
39+
static inline unsigned nlz_int64(uint64_t x);
40+
#ifdef HAVE_UINT128_T
41+
static inline unsigned nlz_int128(uint128_t x);
42+
#endif
43+
44+
static inline unsigned int
45+
nlz_int32(uint32_t x)
46+
{
47+
#if defined(_MSC_VER) && defined(__AVX2__)
48+
/* Note: It seems there is no such thing like __LZCNT__ predefined in MSVC.
49+
* AMD CPUs have had this instruction for decades (since K10) but for
50+
* Intel, Haswell is the oldest one. We need to use __AVX2__ for maximum
51+
* safety. */
52+
return (unsigned int)__lzcnt(x);
53+
54+
#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */
55+
return (unsigned int)_lzcnt_u32(x);
56+
57+
#elif defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */
58+
unsigned long r;
59+
return _BitScanReverse(&r, x) ? (31 - (int)r) : 32;
60+
61+
#elif __has_builtin(__builtin_clz)
62+
STATIC_ASSERT(sizeof_int, sizeof(int) * CHAR_BIT == 32);
63+
return x ? (unsigned int)__builtin_clz(x) : 32;
64+
65+
#else
66+
uint32_t y;
67+
unsigned n = 32;
68+
y = x >> 16; if (y) {n -= 16; x = y;}
69+
y = x >> 8; if (y) {n -= 8; x = y;}
70+
y = x >> 4; if (y) {n -= 4; x = y;}
71+
y = x >> 2; if (y) {n -= 2; x = y;}
72+
y = x >> 1; if (y) {return n - 2;}
73+
return (unsigned int)(n - x);
74+
#endif
75+
}
76+
77+
static inline unsigned int
78+
nlz_int64(uint64_t x)
79+
{
80+
#if defined(_MSC_VER) && defined(__AVX2__)
81+
return (unsigned int)__lzcnt64(x);
82+
83+
#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */
84+
return (unsigned int)_lzcnt_u64(x);
85+
86+
#elif defined(_WIN64) && defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */
87+
unsigned long r;
88+
return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64;
89+
90+
#elif __has_builtin(__builtin_clzl)
91+
if (x == 0) {
92+
return 64;
93+
}
94+
else if (sizeof(long) * CHAR_BIT == 64) {
95+
return (unsigned int)__builtin_clzl((unsigned long)x);
96+
}
97+
else if (sizeof(long long) * CHAR_BIT == 64) {
98+
return (unsigned int)__builtin_clzll((unsigned long long)x);
99+
}
100+
else {
101+
/* :FIXME: Is there a way to make this branch a compile-time error? */
102+
__builtin_unreachable();
103+
}
104+
105+
#else
106+
uint64_t y;
107+
unsigned int n = 64;
108+
y = x >> 32; if (y) {n -= 32; x = y;}
109+
y = x >> 16; if (y) {n -= 16; x = y;}
110+
y = x >> 8; if (y) {n -= 8; x = y;}
111+
y = x >> 4; if (y) {n -= 4; x = y;}
112+
y = x >> 2; if (y) {n -= 2; x = y;}
113+
y = x >> 1; if (y) {return n - 2;}
114+
return (unsigned int)(n - x);
115+
116+
#endif
117+
}
118+
119+
#ifdef HAVE_UINT128_T
120+
static inline unsigned int
121+
nlz_int128(uint128_t x)
122+
{
123+
uint64_t y = (uint64_t)(x >> 64);
124+
125+
if (x == 0) {
126+
return 128;
127+
}
128+
else if (y == 0) {
129+
return (unsigned int)nlz_int64(x) + 64;
130+
}
131+
else {
132+
return (unsigned int)nlz_int64(y);
133+
}
134+
}
135+
#endif
136+
137+
#endif /* BIGDECIMAL_BITS_H */

ext/bigdecimal/extconf.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@ def check_bigdecimal_version(gemspec_path)
1616
message "#{bigdecimal_version}\n"
1717
end
1818

19+
def have_builtin_func(name, check_expr, opt = "", &b)
20+
checking_for checking_message(name.funcall_style, nil, opt) do
21+
if try_compile(<<SRC, opt, &b)
22+
int foo;
23+
int main() { #{check_expr}; return 0; }
24+
SRC
25+
$defs.push(format("-DHAVE_BUILTIN_%s", name.tr_cpp))
26+
true
27+
else
28+
false
29+
end
30+
end
31+
end
32+
1933
gemspec_name = gemspec_path = nil
2034
unless ['', '../../'].any? {|dir|
2135
gemspec_name = "#{dir}bigdecimal.gemspec"
@@ -28,13 +42,20 @@ def check_bigdecimal_version(gemspec_path)
2842

2943
check_bigdecimal_version(gemspec_path)
3044

45+
have_builtin_func("__builtin_clz", "__builtin_clz(0)")
46+
have_builtin_func("__builtin_clzl", "__builtin_clzl(0)")
47+
3148
have_header("stdbool.h")
49+
have_header("x86intrin.h")
3250

3351
have_func("labs", "stdlib.h")
3452
have_func("llabs", "stdlib.h")
3553
have_func("finite", "math.h")
3654
have_func("isfinite", "math.h")
3755

56+
have_header("ruby/internal/has/builtin.h")
57+
have_header("ruby/internal/static_assert.h")
58+
3859
have_type("struct RRational", "ruby.h")
3960
have_func("rb_rational_num", "ruby.h")
4061
have_func("rb_rational_den", "ruby.h")

0 commit comments

Comments
 (0)