/*
*
* Ruby BigDecimal(Variable decimal precision) extension library.
*
* Copyright(C) 2002 by Shigeo Kobayashi(shigeo@tinyforest.gr.jp)
*
* You may distribute under the terms of either the GNU General Public
* License or the Artistic License, as specified in the README file
* of this BigDecimal distribution.
*
* NOTES:
* 2003-04-17
* Bug in negative.exp(n) reported by Hitoshi Miyazaki fixed.
* 2003-03-28
* V1.0 checked in to CVS(ruby/ext/bigdecimal).
* use rb_str2cstr() instead of STR2CSTR().
* 2003-01-03
* assign instead of asign(by knu),use string.h functions(by t.saito).
* 2002-12-06
* The sqrt() bug reported by Bret Jolly fixed.
* 2002-5-6
* The bug reported by Sako Hiroshi (ruby-list:34988) in to_i fixed.
* 2002-4-17
* methods prec and double_fig(class method) added(S.K).
* 2002-04-04
* Copied from BigFloat 1.1.9 and
* hash method changed according to the suggestion from Akinori MUSHA <knu@iDaemons.org>.
* All ! class methods deactivated(but not actually removed).
* to_s and to_s2 merged to one to_s[(n)].
*
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ruby.h"
#include "math.h"
#include "version.h"
/* #define USE_MUTABLE_METHOD */
VALUE rb_cBigDecimal;
#include "bigdecimal.h"
/* MACRO's to guard objects from GC by keeping them in stack */
#define ENTER(n) volatile VALUE vStack[n];int iStack=0
#define PUSH(x) vStack[iStack++] = (unsigned long)(x);
#define SAVE(p) PUSH(p->obj);
#define GUARD_OBJ(p,y) {p=y;SAVE(p);}
/* ETC */
#define MemCmp(x,y,z) memcmp(x,y,z)
#define StrCmp(x,y) strcmp(x,y)
static int VpIsDefOP(Real *c,Real *a,Real *b,int sw);
static int AddExponent(Real *a,S_INT n);
static unsigned short VpGetException(void);
static void VpSetException(unsigned short f);
static int VpAddAbs(Real *a,Real *b,Real *c);
static int VpSubAbs(Real *a,Real *b,Real *c);
static U_LONG VpSetPTR(Real *a,Real *b,Real *c,U_LONG *a_pos,U_LONG *b_pos,U_LONG *c_pos,U_LONG *av,U_LONG *bv);
static int VpNmlz(Real *a);
static void VpFormatSt(char *psz,S_INT fFmt);
static int VpRdup(Real *m,U_LONG ind_m);
static int VpInternalRound(Real *c,int ixDigit,U_LONG vPrev,U_LONG v);
static U_LONG SkipWhiteChar(char *szVal);
/*
* ================== Ruby Interface part ==========================
*/
static ID coerce;
/* Following functions borrowed from numeric.c */
static VALUE
coerce_body(VALUE *x)
{
return rb_funcall(x[1], coerce, 1, x[0]);
}
static VALUE
coerce_rescue(VALUE *x)
{
rb_raise(rb_eTypeError, "%s can't be coerced into %s",
rb_special_const_p(x[1])?
rb_str2cstr(rb_inspect(x[1]),0):
rb_class2name(CLASS_OF(x[1])),
rb_class2name(CLASS_OF(x[0])));
return (VALUE)0;
}
static void
do_coerce(VALUE *x, VALUE *y)
{
VALUE ary;
VALUE a[2];
a[0] = *x; a[1] = *y;
ary = rb_rescue(coerce_body, (VALUE)a, coerce_rescue, (VALUE)a);
if (TYPE(ary) != T_ARRAY || RARRAY(ary)->len != 2) {
rb_raise(rb_eTypeError, "coerce must return [x, y]");
}
*x = RARRAY(ary)->ptr[0];
*y = RARRAY(ary)->ptr[1];
}
static VALUE
DoSomeOne(VALUE x, VALUE y)
{
do_coerce(&x, &y);
return rb_funcall(x, rb_frame_last_func(), 1, y);
}
static void
BigDecimal_delete(Real *pv)
{
VpFree(pv);
}
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;
}
static Real *
GetVpValue(VALUE v, int must)
{
double dv;
Real *pv;
VALUE bg;
char szD[128];
switch(TYPE(v))
{
case T_DATA:
if(RDATA(v)->dfree ==(void *) BigDecimal_delete) {
Data_Get_Struct(v, Real, pv);
return pv;
} else {
goto SomeOneMayDoIt;
}
break;
case T_FIXNUM:
sprintf(szD, "%d", FIX2INT(v));
return VpCreateRbObject(VpBaseFig() * 2 + 1, szD);
case T_FLOAT:
pv = VpCreateRbObject(VpDblFig()*2,"0");
dv = RFLOAT(v)->value;
/* From float */
if (isinf(dv)) {
VpException(VP_EXCEPTION_INFINITY,"Computation including infinity",0);
if(dv==VpGetDoublePosInf()) {
VpSetPosInf(pv);
} else {
VpSetNegInf(pv);
}
} else
if (isnan(dv)) {
VpException(VP_EXCEPTION_NaN,"Computation including NaN(Not a number)",0);
VpSetNaN(pv);
} else {
if (VpIsNegDoubleZero(dv)) {
VpSetNegZero(pv);
} else if(dv==0.0) {
VpSetPosZero(pv);
} else if(dv==1.0) {
VpSetOne(pv);
} else if(dv==-1.0) {
VpSetOne(pv);
pv->sign = -pv->sign;
} else {
VpDtoV(pv,dv);
}
}
return pv;
case T_STRING:
SafeStringValue(v);
return VpCreateRbObject(strlen(RSTRING(v)->ptr) + VpBaseFig() + 1,
RSTRING(v)->ptr);
case T_BIGNUM:
bg = rb_big2str(v, 10);
return VpCreateRbObject(strlen(RSTRING(bg)->ptr) + VpBaseFig() + 1,
RSTRING(bg)->ptr);
default:
goto SomeOneMayDoIt;
}
SomeOneMayDoIt:
if(must) {
rb_raise(rb_eTypeError, "%s can't be coerced into BigDecimal",
rb_special_const_p(v)?
rb_str2cstr(rb_inspect(v),0):
rb_class2name(CLASS_OF(v))
);
}
return NULL; /* NULL means to coerce */
}
static VALUE
BigDecimal_double_fig(VALUE self)
{
return INT2FIX(VpDblFig());
}
static VALUE
BigDecimal_prec(VALUE self)
{
ENTER(1);
Real *p;
VALUE obj;
GUARD_OBJ(p,GetVpValue(self,1));
obj = rb_ary_new();
obj = rb_ary_push(obj,INT2NUM(p->Prec*VpBaseFig()));
obj = rb_ary_push(obj,INT2NUM(p->MaxPrec*VpBaseFig()));
return obj;
}
static VALUE
BigDecimal_hash(VALUE self)
{
ENTER(1);
Real *p;
U_LONG hash,i;
GUARD_OBJ(p,GetVpValue(self,1));
hash = (U_LONG)p->sign;
/* hash!=2: the case for 0(1),NaN(0) or +-Infinity(3) is sign itself */
if(hash==2) {
for(i = 0; i < p->Prec;i++) {
hash = 31 * hash + p->frac[i];
hash ^= p->frac[i];
}
hash += p->exponent;
}
return INT2FIX(hash);
}
static VALUE
BigDecimal_dump(int argc, VALUE *argv, VALUE self)
{
ENTER(5);
char sz[50];
Real *vp;
char *psz;
VALUE dummy;
rb_scan_args(argc, argv, "01", &dummy);
GUARD_OBJ(vp,GetVpValue(self,1));
sprintf(sz,"%lu:",VpMaxPrec(vp)*VpBaseFig());
psz = ALLOCA_N(char,(unsigned int)VpNumOfChars(vp)+strlen(sz));
sprintf(psz,"%s",sz);
VpToString(vp, psz+strlen(psz), 0);
return rb_str_new2(psz);
}
static VALUE
BigDecimal_load(VALUE self, VALUE str)
{
ENTER(2);
Real *pv;
unsigned char *pch;
unsigned char ch;
unsigned long m=0;
SafeStringValue(str);
pch = RSTRING(str)->ptr;
/* First get max prec */
while((*pch)!=(unsigned char)'\0' && (ch=*pch++)!=(unsigned char)':') {
if(ch<'0' || ch>'9') {
rb_raise(rb_eTypeError, "Load failed: invalid character in the marshaled string");
}
m = m*10 + (unsigned long)(ch-'0');
}
if(m>VpBaseFig()) m -= VpBaseFig();
GUARD_OBJ(pv,VpNewRbClass(m,pch,self));
m /= VpBaseFig();
if(m && pv->MaxPrec>m) pv->MaxPrec = m+1;
return ToValue(pv);
}
static VALUE
BigDecimal_mode(VALUE self, VALUE which, VALUE val)
{
unsigned long f,fo;
if(TYPE(which)!=T_FIXNUM) return Qnil;
f = (unsigned long)FIX2INT(which);
if(f&VP_EXCEPTION_ALL) {
/* Exception mode setting */
fo = VpGetException();
if(val!=Qfalse && val!=Qtrue) return Qnil;
if(f&VP_EXCEPTION_INFINITY) {
VpSetException((unsigned short)((val==Qtrue)?(fo|VP_EXCEPTION_INFINITY):
(fo&(~VP_EXCEPTION_INFINITY))));
}
if(f&VP_EXCEPTION_NaN) {
VpSetException((unsigned short)((val==Qtrue)?(fo|VP_EXCEPTION_NaN):
(fo&(~VP_EXCEPTION_NaN))));
}
return INT2FIX(fo);
}
if(VP_COMP_MODE==f) {
/* Computaion mode setting */
if(TYPE(val)!=T_FIXNUM) return Qnil;
fo = VpSetCompMode((unsigned long)FIX2INT(val));
return INT2FIX(fo);
}
return Qnil;
}
static U_LONG
GetAddSubPrec(Real *a, Real *b)
{
U_LONG mxs;
U_LONG mx = a->Prec;
S_INT d;
if(!VpIsDef(a) || !VpIsDef(b)) return (-1L);
if(mx < b->Prec) mx = b->Prec;
if(a->exponent!=b->exponent) {
mxs = mx;
d = a->exponent - b->exponent;
if(d<0) d = -d;
mx = mx+(U_LONG)d;
if(mx<mxs) {
return VpException(VP_EXCEPTION_INFINITY,"Exponent overflow",0);
}
}
return mx;
}
static S_INT
GetPositiveInt(VALUE v)
{
S_INT n;
Check_Type(v, T_FIXNUM);
n = FIX2INT(v);
if(n <= 0) {
rb_fatal("Zero or negative argument not permitted.");
}
return n;
}
VP_EXPORT Real *
VpNewRbClass(U_LONG mx, char *str, VALUE klass)
{
Real *pv = VpAlloc(mx,str);
pv->obj = (VALUE)Data_Wrap_Struct(klass, 0, BigDecimal_delete, pv);
return pv;
}
VP_EXPORT Real *
VpCreateRbObject(U_LONG mx, char *str)
{
Real *pv = VpAlloc(mx,str);
pv->obj = (VALUE)Data_Wrap_Struct(rb_cBigDecimal, 0, BigDecimal_delete, pv);
return pv;
}
static VALUE
BigDecimal_IsNaN(VALUE self)
{
Real *p = GetVpValue(self,1);
if(VpIsNaN(p)) return Qtrue;
return Qfalse;
}
static VALUE
BigDecimal_IsInfinite(VALUE self)
{
Real *p = GetVpValue(self,1);
if(VpIsPosInf(p)) return INT2FIX(1);
if(VpIsNegInf(p)) return INT2FIX(-1);
return Qnil;
}
static VALUE
BigDecimal_IsFinite(VALUE self)
{
Real *p = GetVpValue(self,1);
if(VpIsNaN(p)) return Qfalse;
if(VpIsInf(p)) return Qfalse;
return Qtrue;
}
static VALUE
BigDecimal_to_i(VALUE self)
{
ENTER(5);
int e,n,i,nf;
U_LONG v,b,j;
char *psz,*pch;
Real *p;
GUARD_OBJ(p,GetVpValue(self,1));
if(!VpIsDef(p)) return Qnil; /* Infinity or NaN not converted. */
e = VpExponent10(p);
if(e<=0) return INT2FIX(0);
nf = VpBaseFig();
if(e<=nf) {
e = VpGetSign(p)*p->frac[0];
return INT2FIX(e);
}
psz = ALLOCA_N(char,(unsigned int)(e+nf+2));
n = (e+nf-1)/nf;
pch = psz;
if(VpGetSign(p)<0) *pch++ = '-';
for(i=0;i<n;++i) {
b = VpBaseVal()/10;
if(i>=(int)p->Prec) {
while(b) {
*pch++ = '0';
b /= 10;
}
continue;
}
v = p->frac[i];
while(b) {
j = v/b;
*pch++ = (char)(j + '0');
v -= j*b;
b /= 10;
}
}
*pch++ = 0;
return rb_cstr2inum(psz,10);
}
static VALUE
BigDecimal_induced_from(VALUE self, VALUE x)
{
Real *p = GetVpValue(x,1);
return p->obj;
}
static VALUE
BigDecimal_coerce(VALUE self, VALUE other)
{
ENTER(2);
VALUE obj;
Real *b;
GUARD_OBJ(b,GetVpValue(other,1));
obj = rb_ary_new();
obj = rb_ary_push(obj, b->obj);
obj = rb_ary_push(obj, self);
return obj;
}
static VALUE
BigDecimal_uplus(VALUE self)
{
return self;
}
static VALUE
BigDecimal_add(VALUE self, VALUE r)
{
ENTER(5);
Real *c, *a, *b;
U_LONG mx;
GUARD_OBJ(a,GetVpValue(self,1));
b = GetVpValue(r,0);
if(!b) return DoSomeOne(self,r);
SAVE(b);
if(VpIsNaN(b)) return b->obj;
if(VpIsNaN(a)) return a->obj;
mx = GetAddSubPrec(a,b);
if(mx==(-1L)) {
GUARD_OBJ(c,VpCreateRbObject(VpBaseFig() + 1, "0"));
VpAddSub(c, a, b, 1);
} else {
GUARD_OBJ(c,VpCreateRbObject(mx *(VpBaseFig() + 1), "0"));
if(!mx) {
VpSetInf(c,VpGetSign(a));
} else {
VpAddSub(c, a, b, 1);
}
}
return ToValue(c);
}
static VALUE
BigDecimal_sub(VALUE self, VALUE r)
{
ENTER(5);
Real *c, *a, *b;
U_LONG mx;
GUARD_OBJ(a,GetVpValue(self,1));
b = GetVpValue(r,0);
if(!b) return DoSomeOne(self,r);
SAVE(b);
if(VpIsNaN(b)) return b->obj;
if(VpIsNaN(a)) return a->obj;
mx = GetAddSubPrec(a,b);
if(mx==(-1L)) {
GUARD_OBJ(c,VpCreateRbObject(VpBaseFig() + 1, "0"));
VpAddSub(c, a, b, -1);
} else {
GUARD_OBJ(c,VpCreateRbObject(mx *(VpBaseFig() + 1), "0"));
if(!mx) {
VpSetInf(c,VpGetSign(a));
} else {
VpAddSub(c, a, b, -1);
}
}
return ToValue(c);
}
static S_INT
BigDecimalCmp(VALUE self, VALUE r)
{
ENTER(5);
Real *a, *b;
GUARD_OBJ(a,GetVpValue(self,1));
b = GetVpValue(r,0);
if(!b) return DoSomeOne(self,r);
SAVE(b);
return VpComp(a, b);
}
static VALUE
BigDecimal_zero(VALUE self)
{
Real *a = GetVpValue(self,1);
return VpIsZero(a) ? Qtrue : Qfalse;
}
static VALUE
BigDecimal_nonzero(VALUE self)
{
Real *a = GetVpValue(self,1);
return VpIsZero(a) ? Qnil : self;
}
static VALUE
BigDecimal_comp(VALUE self, VALUE r)
{
S_INT e;
e = BigDecimalCmp(self, r);
if(e==999) return rb_float_new(VpGetDoubleNaN());
return INT2FIX(e);
}
static VALUE
BigDecimal_eq(VALUE self, VALUE r)
{
ENTER(5);
Real *a, *b;
GUARD_OBJ(a,GetVpValue(self,1));
b = GetVpValue(r,0);
if(!b) return Qfalse; /* Not comparable */
SAVE(b);
return VpComp(a, b)? Qfalse:Qtrue;
}
static VALUE
BigDecimal_ne(VALUE self, VALUE r)
{
ENTER(5);
Real *a, *b;
GUARD_OBJ(a,GetVpValue(self,1));
b = GetVpValue(r,0);
if(!b) return Qtrue; /* Not comparable */
SAVE(b);
return VpComp(a, b) ? Qtrue : Qfalse;
}
static VALUE
BigDecimal_lt(VALUE self, VALUE r)
{
S_INT e;
e = BigDecimalCmp(self, r);
if(e==999) return Qfalse;
return(e < 0) ? Qtrue : Qfalse;
}
static VALUE
BigDecimal_le(VALUE self, VALUE r)
{
S_INT e;
e = BigDecimalCmp(self, r);
if(e==999) return Qfalse;
return(e <= 0) ? Qtrue : Qfalse;
}
static VALUE
BigDecimal_gt(VALUE self, VALUE r)
{
S_INT e;
e = BigDecimalCmp(self, r);
if(e==999) return Qfalse;
return(e > 0) ? Qtrue : Qfalse;
}
static VALUE
BigDecimal_ge(VALUE self, VALUE r)
{
S_INT e;
e = BigDecimalCmp(self, r);
if(e==999) return Qfalse;
return(e >= 0) ? Qtrue : Qfalse;
}
static VALUE
BigDecimal_neg(VALUE self, VALUE r)
{
ENTER(5);
Real *c, *a;
GUARD_OBJ(a,GetVpValue(self,1));
GUARD_OBJ(c,VpCreateRbObject(a->Prec *(VpBaseFig() + 1), "0"));
VpAsgn(c, a, -1);
return ToValue(c);
}
static VALUE
BigDecimal_mult(VALUE self, VALUE r)
{
ENTER(5);
Real *c, *a, *b;
U_LONG mx;
GUARD_OBJ(a,GetVpValue(self,1));
b = GetVpValue(r,0);
if(!b) return DoSomeOne(self,r);
SAVE(b);
mx = a->Prec + b->Prec;
GUARD_OBJ(c,VpCreateRbObject(mx *(VpBaseFig() + 1), "0"));
VpMult(c, a, b);
return ToValue(c);
}
static VALUE
BigDecimal_divide(Real **c, Real **res, Real **div, VALUE self, VALUE r)
/* For c,res = self.div(r): no round operation */
{
ENTER(5);
Real *a, *b;
U_LONG mx;
GUARD_OBJ(a,GetVpValue(self,1));
b = GetVpValue(r,0);
if(!b) return DoSomeOne(self,r);
SAVE(b);
*div = b;
mx =(a->MaxPrec + b->MaxPrec) *VpBaseFig();
GUARD_OBJ((*c),VpCreateRbObject(mx, "0"));
GUARD_OBJ((*res),VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0"));
VpDivd(*c, *res, a, b);
return (VALUE)0;
}
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(&c, &res, &div, self, r);
SAVE(c);SAVE(res);SAVE(div);
if(r!=(VALUE)0) return r; /* coerced by other */
/* a/b = c + r/b */
/* c xxxxx
r 00000yyyyy ==> (y/b)*BASE >= HALF_BASE
*/
/* Round */
if(VpIsDef(c)) {
VpInternalRound(c,0,c->frac[c->Prec-1],(VpBaseVal()*res->frac[0])/div->frac[0]);
}
return ToValue(c);
}
/*
* %: mod = a%b = a - (a.to_f/b).floor * b
* div = (a.to_f/b).floor
*/
static VALUE
BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod)
{
ENTER(8);
Real *c=NULL, *d=NULL, *res=NULL;
Real *a, *b;
U_LONG mx;
GUARD_OBJ(a,GetVpValue(self,1));
b = GetVpValue(r,0);
if(!b) return DoSomeOne(self,r);
SAVE(b);
mx = a->Prec;
if(mx<b->Prec) mx = b->Prec;
mx =(mx + 1) * VpBaseFig();
GUARD_OBJ(c,VpCreateRbObject(mx, "0"));
GUARD_OBJ(res,VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0"));
VpDivd(c, res, a, b);
mx = c->Prec *(VpBaseFig() + 1);
GUARD_OBJ(d,VpCreateRbObject(mx, "0"));
VpActiveRound(d,c,VP_COMP_MODE_FLOOR,0);
VpMult(res,d,b);
VpAddSub(c,a,res,-1);
*div = d;
*mod = c;
return (VALUE)0;
}
|