#include "ruby.h" #define DECNUMDIGITS 34 #define DECSUBSET 0 #include "decNumber.h" #include /* Backwards compat for old rubies */ #if !defined(RSTRING_LEN) # define RSTRING_LEN(x) (RSTRING(x)->len) # define RSTRING_PTR(x) (RSTRING(x)->ptr) #endif #if !defined(DEBUGPRINT) # define WHERESTR "[file %s, line %d]: " # define WHEREARG __FILE__, __LINE__ # define DEBUGPRINT2(...) fprintf(stderr, __VA_ARGS__) # define DEBUGPRINT(_fmt, ...) DEBUGPRINT2(WHERESTR _fmt, WHEREARG, __VA_ARGS__) #endif VALUE cDecNumber; VALUE cDecContext; #define dec_num_setup( result, self, result_ptr, self_ptr, context_ptr ) \ Data_Get_Struct( rb_iv_get(self, "@context"), decContext, context_ptr); \ Data_Get_Struct( self, decNumber, self_ptr); \ result = rb_funcall( cDecNumber, rb_intern("new"), 0 ); \ Data_Get_Struct(result, decNumber, result_ptr) #define dec_num_setup_with_new_context( result, self, result_ptr, self_ptr, context, context_ptr ) \ context = rb_funcall( cDecContext, rb_intern("new"), 0 ); \ Data_Get_Struct( context, decContext, context_ptr); \ Data_Get_Struct( self, decNumber, self_ptr); \ result = rb_funcall( cDecNumber, rb_intern("new"), 0 ); \ Data_Get_Struct(result, decNumber, result_ptr) #define dec_num_setup_without_context( result, self, result_ptr, self_ptr ) \ Data_Get_Struct( self, decNumber, self_ptr); \ result = rb_funcall( cDecNumber, rb_intern("new"), 0 ); \ Data_Get_Struct(result, decNumber, result_ptr) #define dec_num_setup_rval( result, self, rval, result_ptr, self_ptr, rval_ptr, context_ptr ) \ Data_Get_Struct( rb_iv_get(self, "@context"), decContext, context_ptr); \ rval = rb_funcall( rval, rb_intern("to_dec_number"), 0 ); \ Data_Get_Struct( self, decNumber, self_ptr); \ Data_Get_Struct( rval, decNumber, rval_ptr); \ result = rb_funcall( cDecNumber, rb_intern("new"), 0 ); \ Data_Get_Struct(result, decNumber, result_ptr) #define dec_num_setup_rval_with_new_context( result, self, rval, result_ptr, self_ptr, rval_ptr, context, context_ptr ) \ context = rb_funcall( cDecContext, rb_intern("new"), 0 ); \ Data_Get_Struct( context, decContext, context_ptr); \ rval = rb_funcall( rval, rb_intern("to_dec_number"), 0 ); \ Data_Get_Struct( self, decNumber, self_ptr); \ Data_Get_Struct( rval, decNumber, rval_ptr); \ result = rb_funcall( cDecNumber, rb_intern("new"), 0 ); \ Data_Get_Struct(result, decNumber, result_ptr) /***** DecContext *****/ static VALUE con_alloc(VALUE klass) { decContext self_struct, *self_ptr; VALUE self; self_ptr = &self_struct; self = Data_Make_Struct(klass, decContext, 0, free, self_ptr); return self; } static VALUE con_set_digits(VALUE self, VALUE new_value) { decContext *self_ptr; Data_Get_Struct(self, decContext, self_ptr); self_ptr->digits = FIX2INT(new_value); return INT2FIX(self_ptr->digits); } static VALUE con_initialize(int argc, VALUE *argv, VALUE self) { decContext *self_ptr; VALUE digits; rb_scan_args( argc, argv, "01", &digits ); Data_Get_Struct(self, decContext, self_ptr); decContextDefault(self_ptr, DEC_INIT_BASE); self_ptr->traps = 0; /* no traps TODO: error handling */ if ( NIL_P(digits) ) { self_ptr->digits = DECNUMDIGITS; } else { con_set_digits(self,digits); } /* TODO: Handle arguments */ return self; } static VALUE con_get_rounding(VALUE self) { decContext *self_ptr; enum rounding round; VALUE name_sym, rounding_names; Data_Get_Struct(self, decContext, self_ptr); round = decContextGetRounding(self_ptr); rounding_names = rb_const_get( cDecContext, rb_intern("ROUNDING_NAMES") ); name_sym = rb_hash_aref( rounding_names, INT2FIX(round) ); return name_sym; } static VALUE con_set_rounding(VALUE self, VALUE new_rounding) { decContext *self_ptr; enum rounding round; VALUE rounding_numbers; Data_Get_Struct(self, decContext, self_ptr); if ( SYMBOL_P( new_rounding ) ) { rounding_numbers = rb_const_get( cDecContext, rb_intern("ROUNDING_NUMBERS") ); new_rounding = rb_hash_aref( rounding_numbers, new_rounding ); } if( ! FIXNUM_P( new_rounding ) ) { rb_raise(rb_eTypeError, "wrong argument type"); } round = FIX2INT(new_rounding); decContextSetRounding(self_ptr,round); return new_rounding; } static VALUE con_get_digits(VALUE self) { decContext *self_ptr; int digits; VALUE r_fixnum; Data_Get_Struct(self, decContext, self_ptr); digits = self_ptr->digits; r_fixnum = INT2FIX(digits); return r_fixnum; } static VALUE con_get_emin(VALUE self) { decContext *self_ptr; Data_Get_Struct(self, decContext, self_ptr); return INT2FIX(self_ptr->emin); } static VALUE con_set_emin(VALUE self, VALUE new_value) { decContext *self_ptr; Data_Get_Struct(self, decContext, self_ptr); self_ptr->emin = FIX2INT(new_value); return INT2FIX(self_ptr->emin); } static VALUE con_get_emax(VALUE self) { decContext *self_ptr; Data_Get_Struct(self, decContext, self_ptr); return INT2FIX(self_ptr->emax); } static VALUE con_set_emax(VALUE self, VALUE new_value) { decContext *self_ptr; Data_Get_Struct(self, decContext, self_ptr); self_ptr->emax = FIX2INT(new_value); return INT2FIX(self_ptr->emax); } /* /DecContext */ /***** DecNumber *****/ static VALUE num_alloc(VALUE klass) { decNumber self_struct, *self_ptr; decContext context; VALUE self; self_ptr = &self_struct; self = Data_Make_Struct(klass, decNumber, 0, free, self_ptr); decContextDefault(&context, DEC_INIT_BASE); (*self_ptr).bits = DECNAN; return self; } static VALUE num_initialize(int argc, VALUE *argv, VALUE self) { decNumber *self_ptr; decContext *context_ptr; VALUE from, r_str, context, digits; rb_scan_args( argc, argv, "02", &from, &digits ); context = rb_funcall( cDecContext, rb_intern("new"), 1, digits ); rb_iv_set( self, "@context", context ); Data_Get_Struct(context, decContext, context_ptr); Data_Get_Struct(self, decNumber, self_ptr); if ( NIL_P(from) ) { /* decNumberFromString(dec_num_ptr, "0", &dec_context); */ } else { r_str = rb_funcall(from, rb_intern("to_s"), 0); decNumberFromString(self_ptr, StringValuePtr( r_str ), context_ptr); } return self; } /* TODO: does this work? */ static VALUE dec_number_from_struct(decNumber source_dec_num) { VALUE new; decNumber *dec_num_ptr; new = rb_funcall( cDecNumber, rb_intern("new"), 0 ); Data_Get_Struct(new, decNumber, dec_num_ptr); decNumberCopy( dec_num_ptr, &source_dec_num ); return new; } static VALUE dec_number_from_string(VALUE obj, VALUE from) { decNumber *dec_num_ptr; VALUE new, ret, err, r_str; ret = Qnil; new = rb_funcall( cDecNumber, rb_intern("new"), 1, from ); Data_Get_Struct(new, decNumber, dec_num_ptr); if ( decNumberIsNaN( dec_num_ptr ) ) { /* FIXME: Just use the sprintf from C, silly */ r_str = rb_funcall( from, rb_intern( "to_s" ), 0 ); err = rb_funcall( rb_mKernel, rb_intern( "sprintf" ), 2, rb_str_new2("invalid value for DecNumber: %s"), r_str ); rb_raise(rb_eArgError, StringValuePtr( err ) ); } else { ret = new; } return ret; } /* TODO: Not sure if this should this return as a NaN when not a number, or as a 0 like nil.to_i */ static VALUE to_dec_number(VALUE obj) { decNumber *dec_num_ptr; VALUE dec_num; VALUE ret; VALUE err; if ( rb_obj_is_kind_of( obj, cDecNumber ) ) { return obj; } ret = Qnil; dec_num = rb_funcall( cDecNumber, rb_intern("new"), 1, obj ); Data_Get_Struct(dec_num, decNumber, dec_num_ptr); if ( decNumberIsNaN( dec_num_ptr ) ) { decNumberZero( dec_num_ptr ); ret = dec_num; } else { ret = dec_num; } return ret; } static VALUE num_coerce( VALUE self, VALUE rhs ) { VALUE result_arr; if ( rb_obj_classname(rhs) != rb_obj_classname(self) ) { rhs = rb_funcall( rhs, rb_intern("to_dec_number"), 0 ); } result_arr = rb_ary_new2(2); rb_ary_store( result_arr, 0, rhs); rb_ary_store( result_arr, 1, self); return result_arr; } static VALUE num_to_s(VALUE self) { decNumber *dec_num_ptr; VALUE str; char c_str[DECNUMDIGITS+14]; Data_Get_Struct(self, decNumber, dec_num_ptr); decNumberToString( dec_num_ptr, c_str ); str = rb_str_new2(c_str); return str; } static VALUE num_to_i(VALUE self) { VALUE result, context; decContext *context_ptr; decNumber *self_ptr, *result_ptr; int32_t c_int; dec_num_setup_with_new_context( result, self, result_ptr, self_ptr, context, context_ptr ); context_ptr->round = DEC_ROUND_DOWN; decNumberToIntegralValue( result_ptr, self_ptr, context_ptr); c_int = decNumberToInt32( result_ptr, context_ptr ); return INT2NUM(c_int); } static VALUE num_to_f(VALUE self) { return rb_funcall(num_to_s(self), rb_intern("to_f"), 0); } static VALUE num_zero(VALUE self) { decNumber *dec_num_ptr; Data_Get_Struct(self, decNumber, dec_num_ptr); if ( decNumberIsZero( dec_num_ptr ) ) { return Qtrue; } else { return Qfalse; } } static VALUE num_nonzero(VALUE self) { decNumber *dec_num_ptr; Data_Get_Struct(self, decNumber, dec_num_ptr); if ( decNumberIsZero( dec_num_ptr ) ) { return Qfalse; } else { return Qtrue; } } static VALUE num_negative(VALUE self) { decNumber *dec_num_ptr; Data_Get_Struct(self, decNumber, dec_num_ptr); if ( decNumberIsNegative( dec_num_ptr ) ) { return Qtrue; } else { return Qfalse; } } static VALUE num_negate(VALUE self) { decNumber *dec_num_ptr; decNumber *new_dec_num_ptr; VALUE new; new = rb_funcall( cDecNumber, rb_intern("new"), 0 ); Data_Get_Struct(self, decNumber, dec_num_ptr); if ( decNumberIsNaN( dec_num_ptr ) ) { rb_raise(rb_eTypeError, "can't negate a NaN" ); return Qnil; } Data_Get_Struct(new, decNumber, new_dec_num_ptr); decNumberCopyNegate( new_dec_num_ptr, dec_num_ptr ); if ( decNumberIsNaN( new_dec_num_ptr ) ) { rb_raise(rb_eTypeError, "negate failed" ); return Qnil; } return new; } /* needs to check for things like nil and false, etc, not convert everything. Probably only convert numbers. */ static VALUE num_compare(VALUE self, VALUE rval) { VALUE ret, result, tmp_obj; int32_t tmp_n, klass; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_ptr; ret = Qnil; if ( SYMBOL_P( rval ) ) { return Qnil; } dec_num_setup_rval( result, self, rval, result_ptr, self_ptr, rval_ptr, context_ptr ); decNumberCompare(result_ptr, self_ptr, rval_ptr, context_ptr); if ( decNumberIsNaN( result_ptr ) ) { rb_raise(rb_eArgError, "FAIL: TODO: This error (should usually fail earlier)"); return Qnil; } else { tmp_n = decNumberToInt32( result_ptr, context_ptr ); ret = INT2FIX( tmp_n ); } return ret; } static VALUE num_divide(VALUE self, VALUE rval) { VALUE result; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_ptr; dec_num_setup_rval( result, self, rval, result_ptr, self_ptr, rval_ptr, context_ptr ); if ( decNumberIsZero(rval_ptr) ) { (*result_ptr).bits = DECNAN; } else { decNumberDivide( result_ptr, self_ptr, rval_ptr, context_ptr); } return result; } static VALUE num_div(VALUE self, VALUE rval) { VALUE result; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_ptr; dec_num_setup_rval( result, self, rval, result_ptr, self_ptr, rval_ptr, context_ptr ); decNumberDivideInteger( result_ptr, self_ptr, rval_ptr, context_ptr); return result; } static VALUE num_multiply(VALUE self, VALUE rval) { VALUE result; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_ptr; dec_num_setup_rval( result, self, rval, result_ptr, self_ptr, rval_ptr, context_ptr ); decNumberMultiply( result_ptr, self_ptr, rval_ptr, context_ptr); return result; } static VALUE num_add(VALUE self, VALUE rval) { VALUE result; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_ptr; dec_num_setup_rval( result, self, rval, result_ptr, self_ptr, rval_ptr, context_ptr ); decNumberAdd( result_ptr, self_ptr, rval_ptr, context_ptr); return result; } static VALUE num_subtract(VALUE self, VALUE rval) { VALUE result; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_ptr; dec_num_setup_rval( result, self, rval, result_ptr, self_ptr, rval_ptr, context_ptr ); decNumberSubtract( result_ptr, self_ptr, rval_ptr, context_ptr); return result; } static VALUE num_abs(VALUE self) { VALUE result; decContext *context_ptr; decNumber *self_ptr, *result_ptr; dec_num_setup( result, self, result_ptr, self_ptr, context_ptr ); decNumberAbs( result_ptr, self_ptr, context_ptr); return result; } static VALUE num_ceil(VALUE self) { VALUE result, context; decContext *context_ptr; decNumber *self_ptr, *result_ptr; dec_num_setup_without_context( result, self, result_ptr, self_ptr ); context = rb_funcall( cDecContext, rb_intern("new"), 0 ); Data_Get_Struct( context, decContext, context_ptr); \ (*context_ptr).round = DEC_ROUND_CEILING; decNumberToIntegralValue( result_ptr, self_ptr, context_ptr); return result; } static VALUE num_floor(VALUE self) { VALUE result, context; decContext *context_ptr; decNumber *self_ptr, *result_ptr; dec_num_setup_without_context( result, self, result_ptr, self_ptr ); context = rb_funcall( cDecContext, rb_intern("new"), 0 ); Data_Get_Struct( context, decContext, context_ptr); \ (*context_ptr).round = DEC_ROUND_FLOOR; decNumberToIntegralValue( result_ptr, self_ptr, context_ptr); return result; } static VALUE num_divmod(VALUE self, VALUE rval) { VALUE result_int, result_rem, result_ary; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_int_ptr, *result_rem_ptr; dec_num_setup_rval( result_int, self, rval, result_int_ptr, self_ptr, rval_ptr, context_ptr ); result_rem = rb_funcall( cDecNumber, rb_intern("new"), 0 ); \ Data_Get_Struct(result_rem, decNumber, result_rem_ptr); /* There might be a more efficient way to get these */ decNumberDivideInteger( result_int_ptr, self_ptr, rval_ptr, context_ptr ); decNumberRemainder( result_rem_ptr, self_ptr, result_int_ptr, context_ptr); result_ary = rb_ary_new3( 2, result_int, result_rem ); return result_ary; } static VALUE num_eql(VALUE self, VALUE rval) { VALUE result; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_ptr; if ( rb_obj_is_kind_of( rval, cDecNumber ) && FIX2INT( num_compare( self, rval ) ) == 0 ) { return Qtrue; } else { return Qfalse; } } static VALUE num_modulo(VALUE self, VALUE rval) { VALUE result, context; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_ptr; dec_num_setup_rval_with_new_context( result, self, rval, result_ptr, self_ptr, rval_ptr, context, context_ptr ); context_ptr->round = DEC_ROUND_DOWN; decNumberRemainder( result_ptr, self_ptr, rval_ptr, context_ptr); return result; } static VALUE num_round(int argc, VALUE *argv, VALUE self) { VALUE result, rval; decContext *context_ptr; decNumber *self_ptr, *result_ptr, *rval_ptr; rb_scan_args( argc, argv, "01", &rval ); if ( NIL_P( rval ) ) { rval = INT2FIX( 0 ); } else { /* probably not necessary */ if ( ! rb_obj_is_kind_of( rval, cDecNumber ) ) { rval = rb_funcall( rval, rb_intern("to_i"), 0 ); } } dec_num_setup_rval( result, self, rval, result_ptr, self_ptr, rval_ptr, context_ptr ); decNumberMinus( rval_ptr, rval_ptr, context_ptr); decNumberRescale( result_ptr, self_ptr, rval_ptr, context_ptr); return result; } static VALUE num_power(VALUE self, VALUE rval) { VALUE result; decContext *context_ptr; decNumber *self_ptr, *rval_ptr, *result_ptr; dec_num_setup_rval( result, self, rval, result_ptr, self_ptr, rval_ptr, context_ptr ); decNumberPower( result_ptr, self_ptr, rval_ptr, context_ptr); return result; } void con_setup_rounding_constant( VALUE rounding_names, VALUE rounding_numbers, const char *sym_name, const char *const_name, enum rounding round ) { VALUE round_num, round_sym; round_num = INT2FIX(round); round_sym = ID2SYM(rb_intern(sym_name)); rb_define_const( cDecContext, const_name, round_num ); rb_hash_aset( rounding_names, round_num, round_sym ); rb_hash_aset( rounding_numbers, round_sym, round_num ); return; } void Init_dec_number() { /**** DecContext ****/ /* *** constants and enums */ enum rounding round; VALUE rounding_names, rounding_numbers, round_default_num; cDecContext = rb_define_class("DecContext", rb_cObject); rounding_names = rb_hash_new(); rb_define_const( cDecContext, "ROUNDING_NAMES", rounding_names ); rounding_numbers = rb_hash_new(); rb_define_const( cDecContext, "ROUNDING_NUMBERS", rounding_numbers ); con_setup_rounding_constant( rounding_names, rounding_numbers, "ceil", "ROUND_CEIL", DEC_ROUND_CEILING ); con_setup_rounding_constant( rounding_names, rounding_numbers, "down", "ROUND_DOWN", DEC_ROUND_DOWN ); con_setup_rounding_constant( rounding_names, rounding_numbers, "floor", "ROUND_FLOOR", DEC_ROUND_FLOOR ); con_setup_rounding_constant( rounding_names, rounding_numbers, "half_down", "ROUND_HALF_DOWN", DEC_ROUND_HALF_DOWN ); con_setup_rounding_constant( rounding_names, rounding_numbers, "half_even", "ROUND_HALF_EVEN", DEC_ROUND_HALF_EVEN ); con_setup_rounding_constant( rounding_names, rounding_numbers, "half_up", "ROUND_HALF_UP", DEC_ROUND_HALF_UP ); con_setup_rounding_constant( rounding_names, rounding_numbers, "up", "ROUND_UP", DEC_ROUND_UP ); con_setup_rounding_constant( rounding_names, rounding_numbers, "up05", "ROUND_05UP", DEC_ROUND_05UP ); /* DEC_ROUND_DEFAULT is a macro with an extra ; so be careful */ round = DEC_ROUND_DEFAULT; round_default_num = INT2FIX(round); rb_hash_aset( rounding_numbers, ID2SYM(rb_intern("default")), round_default_num ); rb_define_const( cDecContext, "ROUND_DEFAULT", round_default_num ); /* *** methods */ rb_define_alloc_func(cDecContext, con_alloc); rb_define_method(cDecContext, "initialize", con_initialize, -1); rb_define_method(cDecContext, "rounding", con_get_rounding, 0); rb_define_method(cDecContext, "rounding=", con_set_rounding, 1); rb_define_method(cDecContext, "digits", con_get_digits, 0); rb_define_method(cDecContext, "digits=", con_set_digits, 1); rb_define_method(cDecContext, "emin", con_get_emin, 0); rb_define_method(cDecContext, "emin=", con_set_emin, 1); rb_define_method(cDecContext, "emax", con_get_emax, 0); rb_define_method(cDecContext, "emax=", con_set_emax, 1); /**** DecNumber ****/ cDecNumber = rb_define_class("DecNumber", rb_cNumeric ); rb_define_alloc_func(cDecNumber, num_alloc); rb_define_attr( cDecNumber, "context", 1, 1); rb_define_method(cDecNumber, "initialize", num_initialize, -1); rb_define_method(cDecNumber, "to_s", num_to_s, 0); rb_define_method(cDecNumber, "to_i", num_to_i, 0); rb_define_method(cDecNumber, "to_f", num_to_f, 0); rb_define_method(cDecNumber, "-@", num_negate, 0); rb_define_method(cDecNumber, "<=>", num_compare, 1); rb_define_method(cDecNumber, "/", num_divide, 1); rb_define_alias( cDecNumber, "quo", "/"); rb_define_alias( cDecNumber, "fdiv", "quo"); rb_define_method(cDecNumber, "coerce", num_coerce, 1); rb_define_method(cDecNumber, "div", num_div, 1); rb_define_method(cDecNumber, "*", num_multiply, 1); rb_define_method(cDecNumber, "+", num_add, 1); rb_define_method(cDecNumber, "-", num_subtract, 1); rb_define_method(cDecNumber, "abs", num_abs, 0); rb_define_method(cDecNumber, "ceil", num_ceil, 0); rb_define_method(cDecNumber, "floor", num_floor, 0); rb_define_method(cDecNumber, "divmod", num_divmod, 1); rb_define_method(cDecNumber, "eql?", num_eql, 1); rb_define_method(cDecNumber, "%", num_modulo, 1); rb_define_alias( cDecNumber, "modulo", "%"); rb_define_method(cDecNumber, "zero?", num_zero, 0); rb_define_method(cDecNumber, "nonzero?", num_nonzero, 0); rb_define_method(cDecNumber, "round", num_round, -1); rb_define_method(cDecNumber, "**", num_power, 1); rb_define_method(cDecNumber, "negative?", num_negative, 0); rb_define_method(rb_cObject, "DecNumber", dec_number_from_string, 1); rb_define_method(rb_cObject, "to_dec_number", to_dec_number, 0); }