ruby-calc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +52 -0
- data/.rubocop_todo.yml +11 -0
- data/.travis.yml +15 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +497 -0
- data/Rakefile +23 -0
- data/bin/console +10 -0
- data/bin/install_calc.sh +18 -0
- data/bin/makefile.patch +48 -0
- data/bin/setup +7 -0
- data/bin/todo.rb +374 -0
- data/ext/calc/c.c +775 -0
- data/ext/calc/calc.c +192 -0
- data/ext/calc/calc.h +71 -0
- data/ext/calc/config.c +239 -0
- data/ext/calc/convert.c +193 -0
- data/ext/calc/extconf.rb +29 -0
- data/ext/calc/math_error.c +72 -0
- data/ext/calc/numeric.c +623 -0
- data/ext/calc/q.c +2755 -0
- data/lib/calc.rb +214 -0
- data/lib/calc/c.rb +371 -0
- data/lib/calc/import.rb +6 -0
- data/lib/calc/numeric.rb +208 -0
- data/lib/calc/q.rb +628 -0
- data/lib/calc/version.rb +3 -0
- data/ruby-calc.gemspec +29 -0
- metadata +167 -0
data/ext/calc/convert.c
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
#include "calc.h"
|
2
|
+
|
3
|
+
/* attempt to convert a bignum a long then to Calc::Q using itoq().
|
4
|
+
* NUM2LONG will raise a RangeError if arg doesn't fit in a long */
|
5
|
+
static VALUE
|
6
|
+
bignum_to_calc_q_via_long(VALUE arg)
|
7
|
+
{
|
8
|
+
return wrap_number(itoq(NUM2LONG(arg)));
|
9
|
+
}
|
10
|
+
|
11
|
+
/* handles exceptions during via_long. convert the arg to a string, then
|
12
|
+
* Calc::Q using str2q() */
|
13
|
+
static VALUE
|
14
|
+
bignum_to_calc_q_via_string(VALUE arg, VALUE e)
|
15
|
+
{
|
16
|
+
VALUE string;
|
17
|
+
|
18
|
+
if (!rb_obj_is_kind_of(e, rb_eRangeError)) {
|
19
|
+
/* something other than RangeError; re-raise it */
|
20
|
+
rb_exc_raise(e);
|
21
|
+
}
|
22
|
+
string = rb_funcall(arg, rb_intern("to_s"), 0);
|
23
|
+
return wrap_number(str2q(StringValueCStr(string)));
|
24
|
+
}
|
25
|
+
|
26
|
+
/* convert a Bignum to Calc::Q
|
27
|
+
* first tries to convert via a long; if that raises an exception, convert via
|
28
|
+
* a string */
|
29
|
+
static VALUE
|
30
|
+
bignum_to_calc_q(VALUE arg)
|
31
|
+
{
|
32
|
+
return rb_rescue(&bignum_to_calc_q_via_long, arg, &bignum_to_calc_q_via_string, arg);
|
33
|
+
}
|
34
|
+
|
35
|
+
/* convert a ruby Rational to a NUMBER*. Since the denominator/numerator of
|
36
|
+
* the rational number could be too big for long, they are converted to NUMBER*
|
37
|
+
* first.
|
38
|
+
*/
|
39
|
+
static NUMBER *
|
40
|
+
rational_to_number(VALUE arg)
|
41
|
+
{
|
42
|
+
NUMBER *qresult, *qnum, *qden;
|
43
|
+
|
44
|
+
qnum = value_to_number(rb_funcall(arg, rb_intern("numerator"), 0), 0);
|
45
|
+
qden = value_to_number(rb_funcall(arg, rb_intern("denominator"), 0), 0);
|
46
|
+
qresult = qqdiv(qnum, qden);
|
47
|
+
qfree(qnum);
|
48
|
+
qfree(qden);
|
49
|
+
return qresult;
|
50
|
+
}
|
51
|
+
|
52
|
+
/* converts a ruby value into a NUMBER*. Allowed types:
|
53
|
+
* - Fixnum
|
54
|
+
* - Bignum
|
55
|
+
* - Calc::Q
|
56
|
+
* - Rational
|
57
|
+
* - String (using libcalc str2q)
|
58
|
+
* - Float (will be converted to a Rational first)
|
59
|
+
*
|
60
|
+
* the caller is responsible for freeing the returned number. storing it in
|
61
|
+
* a Calc::Q is sufficient for the ruby GC to get it.
|
62
|
+
*/
|
63
|
+
NUMBER *
|
64
|
+
value_to_number(VALUE arg, int string_allowed)
|
65
|
+
{
|
66
|
+
NUMBER *qresult;
|
67
|
+
VALUE num, tmp;
|
68
|
+
|
69
|
+
if (TYPE(arg) == T_FIXNUM) {
|
70
|
+
qresult = itoq(NUM2LONG(arg));
|
71
|
+
}
|
72
|
+
else if (TYPE(arg) == T_BIGNUM) {
|
73
|
+
num = bignum_to_calc_q(arg);
|
74
|
+
qresult = qlink((NUMBER *) DATA_PTR(num));
|
75
|
+
}
|
76
|
+
else if (CALC_Q_P(arg)) {
|
77
|
+
qresult = qlink((NUMBER *) DATA_PTR(arg));
|
78
|
+
}
|
79
|
+
else if (TYPE(arg) == T_RATIONAL) {
|
80
|
+
qresult = rational_to_number(arg);
|
81
|
+
}
|
82
|
+
else if (TYPE(arg) == T_FLOAT) {
|
83
|
+
tmp = rb_funcall(arg, rb_intern("to_r"), 0);
|
84
|
+
qresult = rational_to_number(tmp);
|
85
|
+
}
|
86
|
+
else if (string_allowed && TYPE(arg) == T_STRING) {
|
87
|
+
qresult = str2q(StringValueCStr(arg));
|
88
|
+
/* libcalc str2q allows a 0 denominator */
|
89
|
+
if (ziszero(qresult->den)) {
|
90
|
+
qfree(qresult);
|
91
|
+
rb_raise(rb_eZeroDivError, "division by zero");
|
92
|
+
}
|
93
|
+
}
|
94
|
+
else {
|
95
|
+
rb_raise(rb_eArgError, "%" PRIsVALUE " (%" PRIsVALUE ") can't be converted to Calc::Q",
|
96
|
+
arg, rb_obj_class(arg));
|
97
|
+
}
|
98
|
+
return qresult;
|
99
|
+
}
|
100
|
+
|
101
|
+
/* convert a ruby value into a COMPLEX*. Allowed types:
|
102
|
+
* - Complex
|
103
|
+
* - Any type allowed by value_to_number (except string).
|
104
|
+
*
|
105
|
+
* libcalc doesn't provide any way to convert a string to a complex number.
|
106
|
+
*
|
107
|
+
* caller is responseible for freeing the returned complex. storing it in
|
108
|
+
* a Calc::C is sufficient for the ruby GC to get it.
|
109
|
+
*/
|
110
|
+
COMPLEX *
|
111
|
+
value_to_complex(VALUE arg)
|
112
|
+
{
|
113
|
+
COMPLEX *cresult;
|
114
|
+
VALUE real, imag;
|
115
|
+
|
116
|
+
if (CALC_C_P(arg)) {
|
117
|
+
cresult = clink((COMPLEX *) DATA_PTR(arg));
|
118
|
+
}
|
119
|
+
else if (TYPE(arg) == T_COMPLEX) {
|
120
|
+
real = rb_funcall(arg, rb_intern("real"), 0);
|
121
|
+
imag = rb_funcall(arg, rb_intern("imag"), 0);
|
122
|
+
cresult = qqtoc(value_to_number(real, 0), value_to_number(imag, 0));
|
123
|
+
}
|
124
|
+
else if (CALC_Q_P(arg)) {
|
125
|
+
cresult = qqtoc(DATA_PTR(arg), &_qzero_);
|
126
|
+
}
|
127
|
+
else if (FIXNUM_P(arg) || TYPE(arg) == T_BIGNUM || TYPE(arg) == T_RATIONAL
|
128
|
+
|| TYPE(arg) == T_FLOAT) {
|
129
|
+
cresult = qqtoc(value_to_number(arg, 0), &_qzero_);
|
130
|
+
}
|
131
|
+
else {
|
132
|
+
rb_raise(rb_eArgError, "%" PRIsVALUE " (%" PRIsVALUE ") can't be converted to Calc::C",
|
133
|
+
arg, rb_obj_class(arg));
|
134
|
+
}
|
135
|
+
|
136
|
+
return cresult;
|
137
|
+
}
|
138
|
+
|
139
|
+
/* get a long out of a ruby VALUE. if it is fractional, or too big to be
|
140
|
+
* represented, raises an error */
|
141
|
+
long
|
142
|
+
value_to_long(VALUE v)
|
143
|
+
{
|
144
|
+
long l;
|
145
|
+
NUMBER *q;
|
146
|
+
|
147
|
+
if (FIXNUM_P(v)) {
|
148
|
+
return FIX2LONG(v);
|
149
|
+
}
|
150
|
+
q = value_to_number(v, 0);
|
151
|
+
if (qisfrac(q)) {
|
152
|
+
qfree(q);
|
153
|
+
rb_raise(rb_eTypeError, "Fraction (%" PRIsVALUE ") can't be converted to long", v);
|
154
|
+
}
|
155
|
+
if (zgtmaxlong(q->num)) {
|
156
|
+
qfree(q);
|
157
|
+
rb_raise(rb_eTypeError, "%" PRIsVALUE " is too large to convert to long", v);
|
158
|
+
}
|
159
|
+
l = qtoi(q);
|
160
|
+
qfree(q);
|
161
|
+
return l;
|
162
|
+
}
|
163
|
+
|
164
|
+
/* wrap a COMPLEX* into a ruby VALUE of class Calc::C (if there is a non-zero
|
165
|
+
* imaginary part) or Calc::Q (otherwise).
|
166
|
+
*/
|
167
|
+
VALUE
|
168
|
+
wrap_complex(COMPLEX * c)
|
169
|
+
{
|
170
|
+
VALUE result;
|
171
|
+
|
172
|
+
if (cisreal(c)) {
|
173
|
+
result = cq_new();
|
174
|
+
DATA_PTR(result) = qlink(c->real);
|
175
|
+
comfree(c);
|
176
|
+
}
|
177
|
+
else {
|
178
|
+
result = cc_new();
|
179
|
+
DATA_PTR(result) = c;
|
180
|
+
}
|
181
|
+
return result;
|
182
|
+
}
|
183
|
+
|
184
|
+
/* wrap a NUMBER* into a ruby VALUE of class Calc::Q. */
|
185
|
+
VALUE
|
186
|
+
wrap_number(NUMBER * n)
|
187
|
+
{
|
188
|
+
VALUE result;
|
189
|
+
|
190
|
+
result = cq_new();
|
191
|
+
DATA_PTR(result) = n;
|
192
|
+
return result;
|
193
|
+
}
|
data/ext/calc/extconf.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "mkmf"
|
2
|
+
|
3
|
+
unless find_header("calc/cmath.h")
|
4
|
+
abort "calc/cmath.h is missing, please install calc development headers"
|
5
|
+
end
|
6
|
+
|
7
|
+
if RUBY_PLATFORM =~ /darwin/
|
8
|
+
# on macosx, detection of libcustcalc doesn't work. just assume it is
|
9
|
+
# required.
|
10
|
+
$libs = append_library($libs, "custcalc")
|
11
|
+
unless have_library("calc", "libcalc_call_me_first")
|
12
|
+
abort "can't find libcalc, please install calc"
|
13
|
+
end
|
14
|
+
# on macosx, i can't get the linker to use our version of math_error().
|
15
|
+
# this macro uses an alternative error hander based on setjmp/longjmp.
|
16
|
+
$defs << "-DJUMP_ON_MATH_ERROR"
|
17
|
+
else
|
18
|
+
# try libcalc by itself first - this will work if libcustcalc wasn't made,
|
19
|
+
# but will fail if it was
|
20
|
+
unless have_library("calc", "libcalc_call_me_first")
|
21
|
+
puts "trying again with -lcustcalc"
|
22
|
+
$libs = append_library($libs, "custcalc")
|
23
|
+
unless have_library("calc", "libcalc_call_me_first")
|
24
|
+
abort "can't find libcalc, please install calc"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
create_makefile("calc/calc")
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#include <stdarg.h>
|
2
|
+
#include "calc.h"
|
3
|
+
|
4
|
+
/* Document-class: Calc::MathError
|
5
|
+
*
|
6
|
+
* Exceptions of this class are raised whenever a libcalc function encounters
|
7
|
+
* an error.
|
8
|
+
*/
|
9
|
+
VALUE e_MathError;
|
10
|
+
|
11
|
+
#ifdef JUMP_ON_MATH_ERROR
|
12
|
+
|
13
|
+
/* this is an alternative error handler used on systems when we can't tell
|
14
|
+
* the linker to use our version of math_error. uses setjmp to tell libcalc's
|
15
|
+
* default math_error to return here instead of exiting. a ruby exception
|
16
|
+
* is raised if the longjmp is made.
|
17
|
+
*
|
18
|
+
* the downside of this is that each c function which calls libcalc methods
|
19
|
+
* that /could/ call math_error needs to call this first. apart from the small
|
20
|
+
* performance hit, this is extra unwanted code in many functions. if I
|
21
|
+
* could find a way to tell the macosx linker to replace math_error() like
|
22
|
+
* it works on Linux, this method would not be necessary.
|
23
|
+
*
|
24
|
+
* unfortunately it is not possible to call this once (say, during definition
|
25
|
+
* of Calc::MathError) and except later C functions to be able to jump into it.
|
26
|
+
* crossing the ruby/C boundary like that causes crashes or hangs.
|
27
|
+
*
|
28
|
+
* on other systems, this function name is #defined to nothing.
|
29
|
+
*/
|
30
|
+
|
31
|
+
void
|
32
|
+
setup_math_error(void)
|
33
|
+
{
|
34
|
+
int error;
|
35
|
+
VALUE mesg;
|
36
|
+
|
37
|
+
if ((error = setjmp(calc_matherr_jmpbuf)) != 0) {
|
38
|
+
mesg = rb_str_new2(calc_err_msg);
|
39
|
+
reinitialize();
|
40
|
+
rb_exc_raise(rb_exc_new3(e_MathError, mesg));
|
41
|
+
}
|
42
|
+
calc_use_matherr_jmpbuf = 1;
|
43
|
+
}
|
44
|
+
|
45
|
+
#else
|
46
|
+
|
47
|
+
/* provide our own version of math_error which raises a ruby exception
|
48
|
+
* instead of exiting.
|
49
|
+
*
|
50
|
+
* ideally this would call rb_raise, but you can't just pass a varargs list to
|
51
|
+
* another function (see http://c-faq.com/varargs/handoff.html).
|
52
|
+
* so this just does what rb_raise does internally.
|
53
|
+
*/
|
54
|
+
void
|
55
|
+
math_error(char *fmt, ...)
|
56
|
+
{
|
57
|
+
va_list args;
|
58
|
+
VALUE mesg;
|
59
|
+
|
60
|
+
va_start(args, fmt);
|
61
|
+
mesg = rb_vsprintf(fmt, args);
|
62
|
+
va_end(args);
|
63
|
+
rb_exc_raise(rb_exc_new3(e_MathError, mesg));
|
64
|
+
}
|
65
|
+
|
66
|
+
#endif /* JUMP_ON_MATH_ERROR */
|
67
|
+
|
68
|
+
void
|
69
|
+
define_calc_math_error(VALUE m)
|
70
|
+
{
|
71
|
+
e_MathError = rb_define_class_under(m, "MathError", rb_eStandardError);
|
72
|
+
}
|
data/ext/calc/numeric.c
ADDED
@@ -0,0 +1,623 @@
|
|
1
|
+
#include <calc.h>
|
2
|
+
|
3
|
+
/* Document-class: Calc::Numeric
|
4
|
+
*
|
5
|
+
* Parent class to the libraries numeric classes (Calc::Q and Calc::C)
|
6
|
+
*/
|
7
|
+
VALUE cNumeric;
|
8
|
+
|
9
|
+
/* similar to trans_function, but for ln and log; the rational versions (qln,
|
10
|
+
* qlog) return wrong results for self < 0, so call the complex version in that
|
11
|
+
* case.
|
12
|
+
* ref: f_ln() and f_log() in calc's func.c
|
13
|
+
*/
|
14
|
+
static VALUE
|
15
|
+
log_function(int argc, VALUE * argv, VALUE self, NUMBER * (fq) (NUMBER *, NUMBER *),
|
16
|
+
COMPLEX * (*fc) (COMPLEX *, NUMBER *))
|
17
|
+
{
|
18
|
+
VALUE epsilon, result;
|
19
|
+
NUMBER *qepsilon, *qself;
|
20
|
+
COMPLEX *cself;
|
21
|
+
setup_math_error();
|
22
|
+
|
23
|
+
if (rb_scan_args(argc, argv, "01", &epsilon) == 0) {
|
24
|
+
qepsilon = NULL;
|
25
|
+
}
|
26
|
+
else {
|
27
|
+
qepsilon = value_to_number(epsilon, 1);
|
28
|
+
}
|
29
|
+
if (CALC_Q_P(self)) {
|
30
|
+
qself = DATA_PTR(self);
|
31
|
+
if (!qisneg(qself) && !qiszero(qself)) {
|
32
|
+
result = cq_new();
|
33
|
+
DATA_PTR(result) = (*fq) (qself, qepsilon ? qepsilon : conf->epsilon);
|
34
|
+
}
|
35
|
+
else {
|
36
|
+
cself = comalloc();
|
37
|
+
qfree(cself->real);
|
38
|
+
cself->real = qlink(qself);
|
39
|
+
result = wrap_complex((*fc) (cself, qepsilon ? qepsilon : conf->epsilon));
|
40
|
+
comfree(cself);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
else if (CALC_C_P(self)) {
|
44
|
+
cself = DATA_PTR(self);
|
45
|
+
result = wrap_complex((*fc) (cself, qepsilon ? qepsilon : conf->epsilon));
|
46
|
+
}
|
47
|
+
else {
|
48
|
+
rb_raise(e_MathError, "log_function called with invalid receiver");
|
49
|
+
}
|
50
|
+
if (qepsilon) {
|
51
|
+
qfree(qepsilon);
|
52
|
+
}
|
53
|
+
return result;
|
54
|
+
}
|
55
|
+
|
56
|
+
static VALUE
|
57
|
+
shift(VALUE self, VALUE other, BOOL rightshift)
|
58
|
+
{
|
59
|
+
NUMBER *qother;
|
60
|
+
long n;
|
61
|
+
setup_math_error();
|
62
|
+
|
63
|
+
qother = value_to_number(other, 0);
|
64
|
+
if (qisfrac(qother)) {
|
65
|
+
qfree(qother);
|
66
|
+
rb_raise(rb_eArgError, "shift by non-integer");
|
67
|
+
}
|
68
|
+
if (zge31b(qother->num)) {
|
69
|
+
qfree(qother);
|
70
|
+
rb_raise(rb_eArgError, "shift by too many bits");
|
71
|
+
}
|
72
|
+
n = qtoi(qother);
|
73
|
+
qfree(qother);
|
74
|
+
if (rightshift) {
|
75
|
+
n = -n;
|
76
|
+
}
|
77
|
+
if (CALC_Q_P(self)) {
|
78
|
+
return wrap_number(qshift(DATA_PTR(self), n));
|
79
|
+
}
|
80
|
+
return wrap_complex(c_shift(DATA_PTR(self), n));
|
81
|
+
}
|
82
|
+
|
83
|
+
|
84
|
+
NUMBER *
|
85
|
+
sign_of_int(int r)
|
86
|
+
{
|
87
|
+
return (r > 0) ? qlink(&_qone_) : (r < 0) ? qlink(&_qnegone_) : qlink(&_qzero_);
|
88
|
+
}
|
89
|
+
|
90
|
+
/* Left shift an integer by a given number of bits. This multiplies the number
|
91
|
+
* by the appropriate power of 2.
|
92
|
+
*
|
93
|
+
* @param n [Integer] number of bits to shift
|
94
|
+
* @return [Calc::C,Calc::Q]
|
95
|
+
* @raise [Calc::MathError] if self is a non-integer
|
96
|
+
* @raise [ArgumentError] if abs(n) is >= 2^31
|
97
|
+
* @example:
|
98
|
+
* Calc::Q(2) << 3 #=> Calc::Q(16)
|
99
|
+
* Calc::C(2, 3) << 3 #=> Calc::C(16+24i)
|
100
|
+
*/
|
101
|
+
static VALUE
|
102
|
+
cn_shift_left(VALUE self, VALUE other)
|
103
|
+
{
|
104
|
+
return shift(self, other, FALSE);
|
105
|
+
}
|
106
|
+
|
107
|
+
/* Right shift an integer by a given number of bits. This multiplies the
|
108
|
+
* number by the appropriate power of 2. Low bits are truncated.
|
109
|
+
*
|
110
|
+
* @param n [Integer] number of bits to shift
|
111
|
+
* @return [Calc::C,Calc::Q]
|
112
|
+
* @raise [Calc::MathError] if self is a non-integer
|
113
|
+
* @raise [ArgumentError] if abs(n) is >= 2^31
|
114
|
+
* @example:
|
115
|
+
* Calc::Q(8) >> 2 #=> Calc::Q(2)
|
116
|
+
* Calc::C(8, 12) >> 2 #=> Calc::C(2+3i)
|
117
|
+
*/
|
118
|
+
static VALUE
|
119
|
+
cn_shift_right(VALUE self, VALUE other)
|
120
|
+
{
|
121
|
+
return shift(self, other, TRUE);
|
122
|
+
}
|
123
|
+
|
124
|
+
/* Compare 2 values.
|
125
|
+
*
|
126
|
+
* If x and y are both real, returns -1, 0 or 1 according as x < y, x == y or
|
127
|
+
* x > y.
|
128
|
+
*
|
129
|
+
* If one or both of x and y are complex, returns a complex number composed
|
130
|
+
* of the real and imaginary parts being compared individually as above.
|
131
|
+
*
|
132
|
+
* @param y [Numeric] value to compare self to
|
133
|
+
* @example
|
134
|
+
* Calc::Q(3).cmp(4) #=> Calc::Q(-1)
|
135
|
+
* Calc::Q(3).cmp(4+4i) #=> Calc::C(-1-1i)
|
136
|
+
* Calc::C(3i).cmp(3+3i) #=> Calc::Q(-1)
|
137
|
+
*/
|
138
|
+
static VALUE
|
139
|
+
cn_cmp(VALUE self, VALUE other)
|
140
|
+
{
|
141
|
+
VALUE result;
|
142
|
+
NUMBER *qself, *qother;
|
143
|
+
COMPLEX *cself, *cother, *cresult;
|
144
|
+
int r, i;
|
145
|
+
setup_math_error();
|
146
|
+
|
147
|
+
if (CALC_Q_P(self)) {
|
148
|
+
qself = DATA_PTR(self);
|
149
|
+
if (CALC_C_P(other) || TYPE(other) == T_COMPLEX) {
|
150
|
+
cother = value_to_complex(other);
|
151
|
+
r = qrel(qself, cother->real);
|
152
|
+
i = qrel(&_qzero_, cother->imag);
|
153
|
+
comfree(cother);
|
154
|
+
}
|
155
|
+
else {
|
156
|
+
qother = value_to_number(other, 0);
|
157
|
+
r = qrel(qself, qother);
|
158
|
+
i = 0;
|
159
|
+
qfree(qother);
|
160
|
+
}
|
161
|
+
}
|
162
|
+
else if (CALC_C_P(self)) {
|
163
|
+
cself = DATA_PTR(self);
|
164
|
+
if (CALC_C_P(other) || TYPE(other) == T_COMPLEX) {
|
165
|
+
cother = value_to_complex(other);
|
166
|
+
r = qrel(cself->real, cother->real);
|
167
|
+
i = qrel(cself->imag, cother->imag);
|
168
|
+
}
|
169
|
+
else {
|
170
|
+
qother = value_to_number(other, 0);
|
171
|
+
r = qrel(cself->real, qother);
|
172
|
+
i = qrel(cself->imag, &_qzero_);
|
173
|
+
qfree(qother);
|
174
|
+
}
|
175
|
+
}
|
176
|
+
else {
|
177
|
+
rb_raise(rb_eTypeError, "receiver must be Calc::Q or Calc::C");
|
178
|
+
}
|
179
|
+
if (i == 0) {
|
180
|
+
result = cq_new();
|
181
|
+
DATA_PTR(result) = sign_of_int(r);
|
182
|
+
}
|
183
|
+
else {
|
184
|
+
result = cc_new();
|
185
|
+
cresult = comalloc();
|
186
|
+
qfree(cresult->real);
|
187
|
+
cresult->real = sign_of_int(r);
|
188
|
+
qfree(cresult->imag);
|
189
|
+
cresult->imag = sign_of_int(i);
|
190
|
+
DATA_PTR(result) = cresult;
|
191
|
+
}
|
192
|
+
return result;
|
193
|
+
}
|
194
|
+
|
195
|
+
/* combinatorial number
|
196
|
+
*
|
197
|
+
* Returns the number of combinations in which `other` things may be chosen
|
198
|
+
* from `self` items ignoring order.
|
199
|
+
*
|
200
|
+
* @param other [Integer]
|
201
|
+
* @return [Calc::Q,Calc::C]
|
202
|
+
* @raise [MathError] if `other` is too large or not a positive integer
|
203
|
+
* @example
|
204
|
+
* Calc::Q(5).comb(3) #=> Calc::Q(10)
|
205
|
+
* Calc::Q(60).comb(30) #=> Calc::Q(118264581564861424)
|
206
|
+
* Calc::Q("5.1").comb(5) #=> Calc::Q(1.24780425)
|
207
|
+
* Calc::C(8,9).comb(3) #=> Calc::C(-227.5+97.5i)
|
208
|
+
*/
|
209
|
+
static VALUE
|
210
|
+
cn_comb(VALUE self, VALUE other)
|
211
|
+
{
|
212
|
+
VALUE result;
|
213
|
+
NUMBER *qother, *qresult, *qdiv, *qtmp;
|
214
|
+
COMPLEX *cresult, *ctmp1, *ctmp2;
|
215
|
+
long n;
|
216
|
+
setup_math_error();
|
217
|
+
|
218
|
+
qother = value_to_number(other, 0);
|
219
|
+
if (qisfrac(qother)) {
|
220
|
+
qfree(qother);
|
221
|
+
rb_raise(e_MathError, "non-integer argument to comb");
|
222
|
+
}
|
223
|
+
if (qisneg(qother)) {
|
224
|
+
qfree(qother);
|
225
|
+
result = cq_new();
|
226
|
+
DATA_PTR(result) = qlink(&_qzero_);
|
227
|
+
return result;
|
228
|
+
}
|
229
|
+
else if (qiszero(qother)) {
|
230
|
+
qfree(qother);
|
231
|
+
result = cq_new();
|
232
|
+
DATA_PTR(result) = qlink(&_qone_);
|
233
|
+
return result;
|
234
|
+
}
|
235
|
+
else if (qisone(qother)) {
|
236
|
+
qfree(qother);
|
237
|
+
return self;
|
238
|
+
}
|
239
|
+
else if (CALC_Q_P(self)) {
|
240
|
+
qresult = qcomb(DATA_PTR(self), qother);
|
241
|
+
qfree(qother);
|
242
|
+
if (qresult == NULL) {
|
243
|
+
rb_raise(e_MathError, "argument too large for comb");
|
244
|
+
}
|
245
|
+
result = cq_new();
|
246
|
+
DATA_PTR(result) = qresult;
|
247
|
+
return result;
|
248
|
+
}
|
249
|
+
/* if here, self is a Calc::C and qother is integer > 1. algorithm based
|
250
|
+
* on calc's func.c, but only for COMPLEX*. */
|
251
|
+
if (zge24b(qother->num)) {
|
252
|
+
qfree(qother);
|
253
|
+
rb_raise(e_MathError, "argument too large for comb");
|
254
|
+
}
|
255
|
+
n = qtoi(qother);
|
256
|
+
cresult = clink((COMPLEX *) DATA_PTR(self));
|
257
|
+
ctmp1 = c_addq((COMPLEX *) DATA_PTR(self), &_qnegone_);
|
258
|
+
qdiv = qlink(&_qtwo_);
|
259
|
+
n--;
|
260
|
+
for (;;) {
|
261
|
+
ctmp2 = c_mul(cresult, ctmp1);
|
262
|
+
comfree(cresult);
|
263
|
+
cresult = c_divq(ctmp2, qdiv);
|
264
|
+
comfree(ctmp2);
|
265
|
+
if (--n == 0 || ciszero(cresult)) {
|
266
|
+
comfree(ctmp1);
|
267
|
+
qfree(qdiv);
|
268
|
+
result = cc_new();
|
269
|
+
DATA_PTR(result) = cresult;
|
270
|
+
return result;
|
271
|
+
}
|
272
|
+
ctmp2 = c_addq(ctmp1, &_qnegone_);
|
273
|
+
comfree(ctmp1);
|
274
|
+
ctmp1 = ctmp2;
|
275
|
+
qtmp = qinc(qdiv);
|
276
|
+
qfree(qdiv);
|
277
|
+
qdiv = qtmp;
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
/* floor of logarithm to specified integer base
|
282
|
+
*
|
283
|
+
* x.ilog(b) returns the greatest integer for which b^n <= abs(x)
|
284
|
+
*
|
285
|
+
* @param base [Integer]
|
286
|
+
* @return [Calc::Q]
|
287
|
+
* @raise [Calc::MathError] if x is zero, b is non-integer or b is <= 1
|
288
|
+
* @example
|
289
|
+
* Calc::Q(2).ilog(3) #=> Calc::Q(0)
|
290
|
+
* Calc::Q(8).ilog(3) #=> Calc::Q(1)
|
291
|
+
* Calc::Q(9).ilog(3) #=> Calc::Q(2)
|
292
|
+
*/
|
293
|
+
static VALUE
|
294
|
+
cn_ilog(VALUE self, VALUE base)
|
295
|
+
{
|
296
|
+
VALUE result;
|
297
|
+
NUMBER *qbase, *qresult;
|
298
|
+
setup_math_error();
|
299
|
+
|
300
|
+
qbase = value_to_number(base, 0);
|
301
|
+
if (qisfrac(qbase) || qiszero(qbase) || qisunit(qbase) || qisneg(qbase)) {
|
302
|
+
qfree(qbase);
|
303
|
+
rb_raise(e_MathError, "base must be an integer > 1");
|
304
|
+
}
|
305
|
+
if (CALC_Q_P(self)) {
|
306
|
+
qresult = qilog(DATA_PTR(self), qbase->num);
|
307
|
+
}
|
308
|
+
else if (CALC_C_P(self)) {
|
309
|
+
qresult = c_ilog(DATA_PTR(self), qbase->num);
|
310
|
+
}
|
311
|
+
else {
|
312
|
+
rb_raise(rb_eTypeError, "cn_ilog called with invalid receiver");
|
313
|
+
}
|
314
|
+
qfree(qbase);
|
315
|
+
if (!qresult) {
|
316
|
+
rb_raise(e_MathError, "invalid argument for ilog");
|
317
|
+
}
|
318
|
+
result = cq_new();
|
319
|
+
DATA_PTR(result) = qresult;
|
320
|
+
return result;
|
321
|
+
}
|
322
|
+
|
323
|
+
/* Natural logarithm
|
324
|
+
*
|
325
|
+
* Note that this is like using ruby's Math.log.
|
326
|
+
*
|
327
|
+
* @param eps [Numeric,Calc::Q] (optional) calculation accuracy
|
328
|
+
* @return [Calc::Q,Calc::C]
|
329
|
+
* @raise [Calc::MathError] if self is zero
|
330
|
+
* @example
|
331
|
+
* Calc::Q(10).ln #=> Calc::Q(2.30258509299404568402)
|
332
|
+
* Calc::Q(-10).ln #=> Calc::C(2.30258509299404568402+3.14159265358979323846i)
|
333
|
+
* Calc::C(0, 10).ln #=> Calc::C(2.30258509299404568402+1.57079632679489661923i)
|
334
|
+
*/
|
335
|
+
static VALUE
|
336
|
+
cn_ln(int argc, VALUE * argv, VALUE self)
|
337
|
+
{
|
338
|
+
return log_function(argc, argv, self, &qln, &c_ln);
|
339
|
+
}
|
340
|
+
|
341
|
+
/* Base 10 logarithm
|
342
|
+
*
|
343
|
+
* Note that this is like using ruby's Math.log10.
|
344
|
+
*
|
345
|
+
* @param eps [Numeric,Calc::Q] (optional) calculation accuracy
|
346
|
+
* @return [Calc::Q,Calc::C]
|
347
|
+
* @raise [Calc::MathError] if self is zero
|
348
|
+
* @example
|
349
|
+
* Calc::Q(-1).log #=> Calc::C(~1.36437635384184134748i)
|
350
|
+
* Calc::Q(10).log #=> Calc::Q(1)
|
351
|
+
* Calc::Q(100).log #=> Calc::Q(2)
|
352
|
+
* Calc::Q("1e10").log #=> Calc::Q(10)
|
353
|
+
* Calc::C(0, 10).log #=> Calc::C(1+~0.68218817692092067374i)
|
354
|
+
*/
|
355
|
+
static VALUE
|
356
|
+
cn_log(int argc, VALUE * argv, VALUE self)
|
357
|
+
{
|
358
|
+
return log_function(argc, argv, self, &qlog, &c_log);
|
359
|
+
}
|
360
|
+
|
361
|
+
/* Compute integer quotient of a value by a real number (integer division)
|
362
|
+
*
|
363
|
+
* If y is zero, returns zero.
|
364
|
+
*
|
365
|
+
* @param y [Numeric]
|
366
|
+
* @param rnd [Integer] rounding flags, default Calc.config(:quo)
|
367
|
+
* @return [Calc::Q,Calc::C]
|
368
|
+
* @example
|
369
|
+
* Calc::Q(11,5) #=> Calc::Q(2)
|
370
|
+
*/
|
371
|
+
static VALUE
|
372
|
+
cn_quo(int argc, VALUE * argv, VALUE self)
|
373
|
+
{
|
374
|
+
VALUE y, rnd;
|
375
|
+
NUMBER *qy, *qresult;
|
376
|
+
COMPLEX *cself, *cresult;
|
377
|
+
long r;
|
378
|
+
setup_math_error();
|
379
|
+
|
380
|
+
if (rb_scan_args(argc, argv, "11", &y, &rnd) == 2) {
|
381
|
+
r = value_to_long(rnd);
|
382
|
+
}
|
383
|
+
else {
|
384
|
+
r = conf->quo;
|
385
|
+
}
|
386
|
+
qy = value_to_number(y, 1);
|
387
|
+
if (qiszero(qy)) {
|
388
|
+
qfree(qy);
|
389
|
+
rb_raise(rb_eZeroDivError, "division by zero in quo");
|
390
|
+
}
|
391
|
+
if (CALC_Q_P(self)) {
|
392
|
+
qresult = qquo(DATA_PTR(self), qy, r);
|
393
|
+
qfree(qy);
|
394
|
+
return wrap_number(qresult);
|
395
|
+
}
|
396
|
+
cself = DATA_PTR(self);
|
397
|
+
cresult = comalloc();
|
398
|
+
qfree(cresult->real);
|
399
|
+
qfree(cresult->imag);
|
400
|
+
cresult->real = qquo(cself->real, qy, r);
|
401
|
+
cresult->imag = qquo(cself->imag, qy, r);
|
402
|
+
qfree(qy);
|
403
|
+
return wrap_complex(cresult);
|
404
|
+
}
|
405
|
+
|
406
|
+
/* Root of a number
|
407
|
+
*
|
408
|
+
* x.root(n) returns the nth root of x. x can be real or complex, n must be
|
409
|
+
* a positive integer.
|
410
|
+
*
|
411
|
+
* If the nth root of x is a multiple of eps, it will be returned exactly.
|
412
|
+
* Otherwise the returned value will be a multiple of eps close to the real
|
413
|
+
* nth root of x.
|
414
|
+
*
|
415
|
+
* @param n [Integer]
|
416
|
+
* @param eps [Numeric] optional epsilon, default Calc.config(:epsilon)
|
417
|
+
* @return [Calc::Q,Calc::C]
|
418
|
+
* @raise [Calc::MathError] if n is not a positive integer
|
419
|
+
* @example
|
420
|
+
* Calc::Q(7).root(4) #=> Calc::Q(1.62657656169778574321)
|
421
|
+
* Calc::Q(-7).root(4) #=> Calc::C(1.15016331689560300254+1.15016331689560300254i)
|
422
|
+
* Calc::C(7, 7).root(4) #=> Calc::C(1.73971135785495811228+0.34605010474820910752i)
|
423
|
+
*/
|
424
|
+
static VALUE
|
425
|
+
cn_root(int argc, VALUE * argv, VALUE self)
|
426
|
+
{
|
427
|
+
VALUE n, epsilon, result;
|
428
|
+
NUMBER *qn, *qepsilon, *qself;
|
429
|
+
COMPLEX ctmp;
|
430
|
+
setup_math_error();
|
431
|
+
|
432
|
+
if (rb_scan_args(argc, argv, "11", &n, &epsilon) > 1) {
|
433
|
+
qepsilon = value_to_number(epsilon, 1);
|
434
|
+
if (qiszero(qepsilon)) {
|
435
|
+
qfree(qepsilon);
|
436
|
+
rb_raise(e_MathError, "zero epsilon for root");
|
437
|
+
}
|
438
|
+
}
|
439
|
+
else {
|
440
|
+
qepsilon = qlink(conf->epsilon);
|
441
|
+
}
|
442
|
+
qn = value_to_number(n, 0);
|
443
|
+
if (qisneg(qn) || qiszero(qn) || qisfrac(qn)) {
|
444
|
+
qfree(qepsilon);
|
445
|
+
qfree(qn);
|
446
|
+
rb_raise(e_MathError, "non-positive integer root");
|
447
|
+
}
|
448
|
+
if (CALC_Q_P(self)) {
|
449
|
+
qself = DATA_PTR(self);
|
450
|
+
if (!qisneg(qself)) {
|
451
|
+
result = wrap_number(qroot(qself, qn, qepsilon));
|
452
|
+
}
|
453
|
+
else {
|
454
|
+
ctmp.real = qself;
|
455
|
+
ctmp.imag = &_qzero_;
|
456
|
+
ctmp.links = 1;
|
457
|
+
result = wrap_complex(c_root(&ctmp, qn, qepsilon));
|
458
|
+
}
|
459
|
+
}
|
460
|
+
else {
|
461
|
+
result = wrap_complex(c_root(DATA_PTR(self), qn, qepsilon));
|
462
|
+
}
|
463
|
+
qfree(qepsilon);
|
464
|
+
qfree(qn);
|
465
|
+
return result;
|
466
|
+
}
|
467
|
+
|
468
|
+
/* Scale a number by a power of 2
|
469
|
+
*
|
470
|
+
* x.scale(n) returns the value of 2**n * x.
|
471
|
+
*
|
472
|
+
* Unlike the << and >> operators, this function works on fractional x.
|
473
|
+
*
|
474
|
+
* @param n [Integer]
|
475
|
+
* @return [Calc::C,Calc::Q]
|
476
|
+
* @raise [ArgumentError] if n isn't an integer < 2^31
|
477
|
+
* @example
|
478
|
+
* Calc::Q(3).scale(2) #=> Calc::Q(12)
|
479
|
+
* Calc::Q(3).scale(-2) #=> Calc::Q(0.75)
|
480
|
+
* Calc::C(3, 3).scale(2) #=> Calc::C(12+12i)
|
481
|
+
*/
|
482
|
+
static VALUE
|
483
|
+
cn_scale(VALUE self, VALUE other)
|
484
|
+
{
|
485
|
+
NUMBER *qother;
|
486
|
+
long n;
|
487
|
+
setup_math_error();
|
488
|
+
|
489
|
+
qother = value_to_number(other, 0);
|
490
|
+
if (qisfrac(qother)) {
|
491
|
+
qfree(qother);
|
492
|
+
rb_raise(rb_eArgError, "scale by non-integer");
|
493
|
+
}
|
494
|
+
if (zge31b(qother->num)) {
|
495
|
+
qfree(qother);
|
496
|
+
rb_raise(rb_eArgError, "scale factor must be < 2^31");
|
497
|
+
}
|
498
|
+
n = qtoi(qother);
|
499
|
+
qfree(qother);
|
500
|
+
if (CALC_Q_P(self)) {
|
501
|
+
return wrap_number(qscale(DATA_PTR(self), n));
|
502
|
+
}
|
503
|
+
else {
|
504
|
+
return wrap_complex(c_scale(DATA_PTR(self), n));
|
505
|
+
}
|
506
|
+
}
|
507
|
+
|
508
|
+
/* Indicates sign of a real or complex number
|
509
|
+
*
|
510
|
+
* For real x, x.sgn returns
|
511
|
+
* -1 if x < 0
|
512
|
+
* 0 if x == 0
|
513
|
+
* 1 if x > 0
|
514
|
+
*
|
515
|
+
* For complex x, x.sgn returns Calc::C(x.re.sgn, x.im.sgn)
|
516
|
+
*
|
517
|
+
* @return [Calc::C,Calc::Q]
|
518
|
+
* @example
|
519
|
+
* Calc::Q(9).sgn #=> Calc::Q(1)
|
520
|
+
* Calc::Q(0).sgn #=> Calc::Q(0)
|
521
|
+
* Calc::Q(-9).sgn #=> Calc::Q(-1)
|
522
|
+
* Calc::C(1, -1).sgn #=> Calc::C(1-1i)
|
523
|
+
*/
|
524
|
+
static VALUE
|
525
|
+
cn_sgn(VALUE self)
|
526
|
+
{
|
527
|
+
COMPLEX *cself, *cresult;
|
528
|
+
setup_math_error();
|
529
|
+
|
530
|
+
if (CALC_Q_P(self)) {
|
531
|
+
return wrap_number(qsign(DATA_PTR(self)));
|
532
|
+
}
|
533
|
+
cself = DATA_PTR(self);
|
534
|
+
cresult = comalloc();
|
535
|
+
qfree(cresult->real);
|
536
|
+
qfree(cresult->imag);
|
537
|
+
cresult->real = qsign(cself->real);
|
538
|
+
cresult->imag = qsign(cself->imag);
|
539
|
+
return wrap_complex(cresult);
|
540
|
+
}
|
541
|
+
|
542
|
+
/* Square root
|
543
|
+
*
|
544
|
+
* Calculates the square root of self (rational or complex). If eps
|
545
|
+
* is provided, it specifies the accuracy/error of the calculation, otherwise
|
546
|
+
* config("epsilon") is used.
|
547
|
+
* If z is provided, it controls the sign and rounding if required, otherwise
|
548
|
+
* config("sqrt") is used.
|
549
|
+
* Type "help sqrt" in calc for a full explanation of z.
|
550
|
+
*
|
551
|
+
* @param eps [Numeric,Calc::Q] (optional) calculation accuracy
|
552
|
+
* @param z [Integer] (optional) sign and rounding flags
|
553
|
+
* @example
|
554
|
+
* Calc::Q(4).sqrt #=> Calc::Q(2)
|
555
|
+
* Calc::Q(5).sqrt #=> Calc::Q(2.23606797749978969641)
|
556
|
+
* Calc::C(0,8).sqrt #=> Calc::C(2+2i)
|
557
|
+
*/
|
558
|
+
static VALUE
|
559
|
+
cn_sqrt(int argc, VALUE * argv, VALUE self)
|
560
|
+
{
|
561
|
+
VALUE result, epsilon, z;
|
562
|
+
NUMBER *qtmp, *qepsilon;
|
563
|
+
COMPLEX *cresult;
|
564
|
+
long R;
|
565
|
+
int n;
|
566
|
+
setup_math_error();
|
567
|
+
|
568
|
+
n = rb_scan_args(argc, argv, "02", &epsilon, &z);
|
569
|
+
if (n >= 1) {
|
570
|
+
qepsilon = value_to_number(epsilon, 1);
|
571
|
+
}
|
572
|
+
else {
|
573
|
+
qepsilon = conf->epsilon;
|
574
|
+
}
|
575
|
+
if (n == 2) {
|
576
|
+
R = FIX2LONG(z);
|
577
|
+
}
|
578
|
+
else {
|
579
|
+
R = conf->sqrt;
|
580
|
+
}
|
581
|
+
if (CALC_Q_P(self) && !qisneg((NUMBER *) DATA_PTR(self))) {
|
582
|
+
/* non-negative rational */
|
583
|
+
result = cq_new();
|
584
|
+
DATA_PTR(result) = qsqrt(DATA_PTR(self), qepsilon, R);
|
585
|
+
}
|
586
|
+
else {
|
587
|
+
if (CALC_Q_P(self)) {
|
588
|
+
/* negative rational */
|
589
|
+
qtmp = qneg(DATA_PTR(self));
|
590
|
+
cresult = comalloc();
|
591
|
+
qfree(cresult->imag);
|
592
|
+
cresult->imag = qsqrt(qtmp, qepsilon, R);
|
593
|
+
qfree(qtmp);
|
594
|
+
}
|
595
|
+
else {
|
596
|
+
/* complex */
|
597
|
+
cresult = c_sqrt(DATA_PTR(self), qepsilon, R);
|
598
|
+
}
|
599
|
+
result = wrap_complex(cresult);
|
600
|
+
}
|
601
|
+
if (n >= 1) {
|
602
|
+
qfree(qepsilon);
|
603
|
+
}
|
604
|
+
return result;
|
605
|
+
}
|
606
|
+
|
607
|
+
void
|
608
|
+
define_calc_numeric(VALUE m)
|
609
|
+
{
|
610
|
+
cNumeric = rb_define_class_under(m, "Numeric", rb_cData);
|
611
|
+
rb_define_method(cNumeric, "<<", cn_shift_left, 1);
|
612
|
+
rb_define_method(cNumeric, ">>", cn_shift_right, 1);
|
613
|
+
rb_define_method(cNumeric, "cmp", cn_cmp, 1);
|
614
|
+
rb_define_method(cNumeric, "comb", cn_comb, 1);
|
615
|
+
rb_define_method(cNumeric, "ilog", cn_ilog, 1);
|
616
|
+
rb_define_method(cNumeric, "ln", cn_ln, -1);
|
617
|
+
rb_define_method(cNumeric, "log", cn_log, -1);
|
618
|
+
rb_define_method(cNumeric, "quo", cn_quo, -1);
|
619
|
+
rb_define_method(cNumeric, "root", cn_root, -1);
|
620
|
+
rb_define_method(cNumeric, "scale", cn_scale, 1);
|
621
|
+
rb_define_method(cNumeric, "sgn", cn_sgn, 0);
|
622
|
+
rb_define_method(cNumeric, "sqrt", cn_sqrt, -1);
|
623
|
+
}
|