/*
  sf.c
  Ruby/GSL: Ruby extension library for GSL (GNU Scientific Library)
    (C) Copyright 2001-2006 by Yoshiki Tsunesada

  Ruby/GSL is free software: you can redistribute it and/or modify it
  under the terms of the GNU General Public License.
  This library is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/

#include "include/rb_gsl_array.h"
#include "include/rb_gsl_sf.h"

VALUE cgsl_sf_result, cgsl_sf_result_e10;

VALUE rb_gsl_sf_result_new(VALUE klass)
{
  gsl_sf_result *rslt = NULL;
  return Data_Make_Struct(klass, gsl_sf_result, 0, free, rslt);
}

static VALUE rb_gsl_sf_result_print(VALUE obj)
{
  gsl_sf_result *rslt = NULL;
  Data_Get_Struct(obj, gsl_sf_result, rslt);
  printf("%10.9e %10.9e\n", rslt->val, rslt->err);
  return obj;
}

static VALUE rb_gsl_sf_result_to_s(VALUE obj);

static VALUE rb_gsl_sf_result_inspect(VALUE obj)
{
  char buf[64];
  VALUE str;
  sprintf(buf, "%s\n", rb_class2name(CLASS_OF(obj)));
  str = rb_str_new2(buf);
  return rb_str_concat(str, rb_gsl_sf_result_to_s(obj));
}

static VALUE rb_gsl_sf_result_val(VALUE obj)
{
  gsl_sf_result *rslt = NULL;
  Data_Get_Struct(obj, gsl_sf_result, rslt);
  return rb_float_new(rslt->val);
}

static VALUE rb_gsl_sf_result_err(VALUE obj)
{
  gsl_sf_result *rslt = NULL;
  Data_Get_Struct(obj, gsl_sf_result, rslt);
  return rb_float_new(rslt->err);
}

static VALUE rb_gsl_sf_result_to_a(VALUE obj)
{
  gsl_sf_result *rslt = NULL;
  Data_Get_Struct(obj, gsl_sf_result, rslt);
  return rb_ary_new3(2, rb_float_new(rslt->val), rb_float_new(rslt->err));
}

static VALUE rb_gsl_sf_result_to_s(VALUE obj)
{
  gsl_sf_result *rslt = NULL;
  char str[32];
  Data_Get_Struct(obj, gsl_sf_result, rslt);
  sprintf(str, "%10.9e %10.9e", rslt->val, rslt->err);
  return rb_str_new2(str);
}

static VALUE rb_gsl_sf_result_e10_new(VALUE klass)
{
  gsl_sf_result_e10 *rslt = NULL;
  return Data_Make_Struct(cgsl_sf_result, gsl_sf_result_e10, 0, free, rslt);
}

static VALUE rb_gsl_sf_result_e10_val(VALUE obj)
{
  gsl_sf_result_e10 *rslt = NULL;
  Data_Get_Struct(obj, gsl_sf_result_e10, rslt);
  return rb_float_new(rslt->val);
}

static VALUE rb_gsl_sf_result_e10_err(VALUE obj)
{
  gsl_sf_result_e10 *rslt = NULL;
  Data_Get_Struct(obj, gsl_sf_result_e10, rslt);
  return rb_float_new(rslt->err);
}

static VALUE rb_gsl_sf_result_e10_e10(VALUE obj)
{
  gsl_sf_result_e10 *rslt = NULL;
  Data_Get_Struct(obj, gsl_sf_result_e10, rslt);
  return INT2FIX(rslt->e10);
}

static VALUE rb_gsl_sf_result_e10_to_a(VALUE obj)
{
  gsl_sf_result_e10 *rslt = NULL;
  Data_Get_Struct(obj, gsl_sf_result_e10, rslt);
  return rb_ary_new3(2, rb_float_new(rslt->val), rb_float_new(rslt->err));
}

static VALUE rb_gsl_sf_result_e10_to_s(VALUE obj)
{
  gsl_sf_result_e10 *rslt = NULL;
  char str[32];
  Data_Get_Struct(obj, gsl_sf_result_e10, rslt);
  sprintf(str, "%10.9e %10.9e\n", rslt->val, rslt->err);
  return rb_str_new2(str);
}

VALUE rb_gsl_sf_eval1(double (*func)(double), VALUE argv)
{
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(NUM2DBL(argv)));
    break;
  case T_ARRAY:
    return rb_gsl_ary_eval1(argv, func);
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      return rb_gsl_nary_eval1(argv, func);
    }
#endif
    if (MATRIX_P(argv)) {
      return matrix_eval_create(argv, func);
    } else if (VECTOR_P(argv)) {
      return vector_eval_create(argv, func);
    } else if (COMPLEX_P(argv) || VECTOR_COMPLEX_P(argv) || MATRIX_COMPLEX_P(argv)) {
      return rb_gsl_sf_eval_complex(func, argv);
    } else {
      rb_raise(rb_eTypeError, "wrong argument type %s", rb_class2name(CLASS_OF(argv)));
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_int_double(double (*func)(int, double), VALUE jj, VALUE argv)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *m = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, j, k, n;
  double val;
  CHECK_FIXNUM(jj);
  j = FIX2INT(jj);
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(j, NUM2DBL(argv)));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(j, NUM2DBL(xx));
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      argv = na_change_type(argv, NA_DFLOAT);
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(j, ptr1[i]);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, m);
      mnew = gsl_matrix_alloc(m->size1, m->size2);
      for (i = 0; i < m->size1; i++) {
        for (k = 0; k < m->size2; k++) {
          val = (*func)(j, gsl_matrix_get(m, i, k));
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(j, gsl_vector_get(v, i));
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_double_int(double (*func)(double, int), VALUE argv, VALUE jj)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *m = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, j, k, n;
  double val;
  CHECK_FIXNUM(jj);
  j = FIX2INT(jj);
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(NUM2DBL(argv), j));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(NUM2DBL(xx), j);
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      argv = na_change_type(argv, NA_DFLOAT);
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(ptr1[i], j);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, m);
      mnew = gsl_matrix_alloc(m->size1, m->size2);
      for (i = 0; i < m->size1; i++) {
        for (k = 0; k < m->size2; k++) {
          val = (*func)(gsl_matrix_get(m, i, k), j);
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(gsl_vector_get(v, i), j);
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
  return Qnil;
}

VALUE rb_gsl_sf_eval_int_int_double(double (*func)(int, int, double), VALUE jj,
                                    VALUE jj2, VALUE argv)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *m = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, j, k, j2, n;
  double val;
  CHECK_FIXNUM(jj); CHECK_FIXNUM(jj2);
  j = FIX2INT(jj);
  j2 = FIX2INT(jj2);
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(j, j2, NUM2DBL(argv)));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(j, j2, NUM2DBL(xx));
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      argv = na_change_type(argv, NA_DFLOAT);
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(j, j2, ptr1[i]);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, m);
      mnew = gsl_matrix_alloc(m->size1, m->size2);
      for (i = 0; i < m->size1; i++) {
        for (k = 0; k < m->size2; k++) {
          val = (*func)(j, j2, gsl_matrix_get(m, i, k));
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(j, j2, gsl_vector_get(v, i));
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_int_double_double(double (*func)(int, double, double), VALUE jj,
                                       VALUE ff, VALUE argv)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *m = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, j, k, n;
  double f, val;
  CHECK_FIXNUM(jj);
  Need_Float(ff);
  j = FIX2INT(jj);
  f = NUM2DBL(ff);
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(j, f, NUM2DBL(argv)));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(j, f, NUM2DBL(xx));
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      argv = na_change_type(argv, NA_DFLOAT);
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(j, f, ptr1[i]);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, m);
      mnew = gsl_matrix_alloc(m->size1, m->size2);
      for (i = 0; i < m->size1; i++) {
        for (k = 0; k < m->size2; k++) {
          val = (*func)(j, f, gsl_matrix_get(m, i, k));
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(j, f, gsl_vector_get(v, i));
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_double_double(double (*func)(double, double), VALUE ff, VALUE argv)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *m = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, k, n;
  double val, f;
  Need_Float(ff);
  f = NUM2DBL(ff);
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(f, NUM2DBL(argv)));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(f, NUM2DBL(xx));
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      argv = na_change_type(argv, NA_DFLOAT);
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(f, ptr1[i]);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, m);
      mnew = gsl_matrix_alloc(m->size1, m->size2);
      for (i = 0; i < m->size1; i++) {
        for (k = 0; k < m->size2; k++) {
          gsl_matrix_set(mnew, i, k, (*func)(f, gsl_matrix_get(m, i, k)));
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      vnew = gsl_vector_alloc(v->size);
      for (i = 0; i < v->size; i++) {
        gsl_vector_set(vnew, i, (*func)(f, gsl_vector_get(v, i)));
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_double3(double (*func)(double, double, double),
                             VALUE ff, VALUE ff2, VALUE argv)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *m = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, k, n;
  double val, f, f2;
  Need_Float(ff); Need_Float(ff2);
  f = NUM2DBL(ff);
  f2 = NUM2DBL(ff2);
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(f, f2, NUM2DBL(argv)));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(f, f2, NUM2DBL(xx));
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      argv = na_change_type(argv, NA_DFLOAT);
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(f, f2, ptr1[i]);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, m);
      mnew = gsl_matrix_alloc(m->size1, m->size2);
      for (i = 0; i < m->size1; i++) {
        for (k = 0; k < m->size2; k++) {
          val = (*func)(f, f2, gsl_matrix_get(m, i, k));
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(f, f2, gsl_vector_get(v, i));
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_double4(double (*func)(double, double, double, double),
                             VALUE ff, VALUE ff2, VALUE ff3, VALUE argv)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *m = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, k, n;
  double val, f, f2, f3;
  Need_Float(ff); Need_Float(ff2); Need_Float(ff3);
  f = NUM2DBL(ff);
  f2 = NUM2DBL(ff2);
  f3 = NUM2DBL(ff3);
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(f, f2, f3, NUM2DBL(argv)));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(f, f2, f3, NUM2DBL(xx));
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      argv = na_change_type(argv, NA_DFLOAT);
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(f, f2, f3, ptr1[i]);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, m);
      mnew = gsl_matrix_alloc(m->size1, m->size2);
      for (i = 0; i < m->size1; i++) {
        for (k = 0; k < m->size2; k++) {
          val = (*func)(f, f2, f3, gsl_matrix_get(m, i, k));
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(f, f2, f3, gsl_vector_get(v, i));
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval1_int(double (*func)(int), VALUE argv)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *m = NULL, *mnew = NULL;
  VALUE ary;
  size_t i, k, n;
  double val;
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FIXNUM:
  case T_BIGNUM:
  case T_FLOAT:
    return rb_float_new((*func)(NUM2INT(argv)));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      val = (*func)(NUM2INT(rb_ary_entry(argv, i)));
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      argv = na_change_type(argv, NA_DFLOAT);
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)((int) ptr1[i]);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, m);
      mnew = gsl_matrix_alloc(m->size1, m->size2);
      for (i = 0; i < m->size1; i++) {
        for (k = 0; k < m->size2; k++) {
          val = (*func)((int) gsl_matrix_get(m, i, k));
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)((int) gsl_vector_get(v, i));
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval1_uint(double (*func)(unsigned int), VALUE argv)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *m = NULL, *mnew = NULL;
  VALUE ary;
  size_t i, k, n;
  double val;
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FIXNUM:
  case T_BIGNUM:
  case T_FLOAT:
    return rb_float_new((*func)(NUM2UINT(argv)));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      val = (*func)(NUM2UINT(rb_ary_entry(argv, i)));
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      argv = na_change_type(argv, NA_DFLOAT);
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)((unsigned int) ptr1[i]);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, m);
      mnew = gsl_matrix_alloc(m->size1, m->size2);
      for (i = 0; i < m->size1; i++) {
        for (k = 0; k < m->size2; k++) {
          val = (*func)((unsigned int) gsl_matrix_get(m, i, k));
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)((unsigned int) gsl_vector_get(v, i));
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_double_m(double (*func)(double, gsl_mode_t), VALUE argv, VALUE m)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *mm = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, k, n;
  double val;
  /*gsl_mode_t mode;
  char c;*/
  switch (TYPE(m)) {
  case T_STRING:
    /*c = tolower(NUM2CHR(m));
    if (c == 'd') mode = GSL_PREC_DOUBLE;
    else if (c == 's') mode = GSL_PREC_SINGLE;
    else if (c == 'a') mode = GSL_PREC_APPROX;
    else mode = GSL_PREC_DOUBLE;*/
    break;
  case T_FIXNUM:
    /*mode = FIX2INT(m);*/
    break;
  default:
    rb_raise(rb_eArgError, "wrong type argument %s (String or Fixnum expected)",
             rb_class2name(CLASS_OF(m)));
  }
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(NUM2DBL(argv), m));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(NUM2DBL(xx), m);
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(ptr1[i], m);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, mm);
      mnew = gsl_matrix_alloc(mm->size1, mm->size2);
      for (i = 0; i < mm->size1; i++) {
        for (k = 0; k < mm->size2; k++) {
          val = (*func)(gsl_matrix_get(mm, i, k), m);
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(gsl_vector_get(v, i), m);
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_double2_m(double (*func)(double, double, gsl_mode_t),
                               VALUE argv, VALUE x2, VALUE m)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *mm = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, k, n;
  double val, xx2;
  /*gsl_mode_t mode;
  char c;*/
  Need_Float(x2);
  xx2 = NUM2DBL(x2);
  /*c = tolower(NUM2CHR(m));
  if (c == 'd') mode = GSL_PREC_DOUBLE;
  else if (c == 's') mode = GSL_PREC_SINGLE;
  else if (c == 'a') mode = GSL_PREC_APPROX;
  else mode = GSL_PREC_DOUBLE;*/
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(NUM2DBL(argv), xx2, m));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(NUM2DBL(xx), xx2, m);
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(ptr1[i], xx2, m);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, mm);
      mnew = gsl_matrix_alloc(mm->size1, mm->size2);
      for (i = 0; i < mm->size1; i++) {
        for (k = 0; k < mm->size2; k++) {
          val = (*func)(gsl_matrix_get(mm, i, k), xx2, m);
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(gsl_vector_get(v, i), xx2, m);
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_double3_m(double (*func)(double, double, double, gsl_mode_t),
                               VALUE argv, VALUE x2, VALUE x3, VALUE m)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *mm = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, k, n;
  double val, xx2, xx3;
  /*gsl_mode_t mode;
  char c;*/
  Need_Float(x2); Need_Float(x3);
  xx2 = NUM2DBL(x2);
  xx3 = NUM2DBL(x3);
  /*c = tolower(NUM2CHR(m));
  if (c == 'd') mode = GSL_PREC_DOUBLE;
  else if (c == 's') mode = GSL_PREC_SINGLE;
  else if (c == 'a') mode = GSL_PREC_APPROX;
  else mode = GSL_PREC_DOUBLE;*/
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(NUM2DBL(argv), xx2, xx3, m));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(NUM2DBL(xx), xx2, xx3, m);
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(ptr1[i], xx2, xx3, m);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, mm);
      mnew = gsl_matrix_alloc(mm->size1, mm->size2);
      for (i = 0; i < mm->size1; i++) {
        for (k = 0; k < mm->size2; k++) {
          val = (*func)(gsl_matrix_get(mm, i, k), xx2, xx3, m);
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(gsl_vector_get(v, i), xx2, xx3, m);
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_double4_m(double (*func)(double, double, double, double,
                                              gsl_mode_t),
                               VALUE argv, VALUE x2, VALUE x3, VALUE x4, VALUE m)
{
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *mm = NULL, *mnew = NULL;
  VALUE ary, xx;
  size_t i, k, n;
  double val, xx2, xx3, xx4;
  /*gsl_mode_t mode;
  char c;*/
  Need_Float(x2); Need_Float(x3); Need_Float(x4);
  xx2 = NUM2DBL(x2);  xx3 = NUM2DBL(x3); xx4 = NUM2DBL(x4);
  /*c = tolower(NUM2CHR(m));
  if (c == 'd') mode = GSL_PREC_DOUBLE;
  else if (c == 's') mode = GSL_PREC_SINGLE;
  else if (c == 'a') mode = GSL_PREC_APPROX;
  else mode = GSL_PREC_DOUBLE;*/
  if (CLASS_OF(argv) == rb_cRange) argv = rb_gsl_range2ary(argv);
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(NUM2DBL(argv), NUM2DBL(x2), NUM2DBL(x3),
                                NUM2DBL(x4), m));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(NUM2DBL(xx), xx2, xx3, xx4, m);
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(ptr1[i], xx2, xx3, xx4, m);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, mm);
      mnew = gsl_matrix_alloc(mm->size1, mm->size2);
      for (i = 0; i < mm->size1; i++) {
        for (k = 0; k < mm->size2; k++) {
          val = (*func)(gsl_matrix_get(mm, i, k), xx2, xx3, xx4, m);
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(gsl_vector_get(v, i),  xx2, xx3, xx4, m);
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_e(int (*func)(double, gsl_sf_result*), VALUE x)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  Need_Float(x);
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2DBL(x), rslt);
  return v;
}

VALUE rb_gsl_sf_eval_e_int(int (*func)(int, gsl_sf_result*), VALUE x)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  CHECK_FIXNUM(x);
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2INT(x), rslt);
  return v;
}

VALUE rb_gsl_sf_eval_e_uint(int (*func)(unsigned int, gsl_sf_result*), VALUE x)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2UINT(x), rslt);
  return v;
}

VALUE rb_gsl_sf_eval_e_int_uint(int (*func)(int, unsigned int, gsl_sf_result*),
                                VALUE n, VALUE x)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  CHECK_FIXNUM(n);
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(FIX2INT(n), NUM2UINT(x), rslt);
  return v;
}

VALUE rb_gsl_sf_eval_e_double_uint(int (*func)(double, unsigned int, gsl_sf_result*),
                                   VALUE y, VALUE x)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  Need_Float(y);
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2DBL(y), NUM2UINT(x), rslt);
  return v;
}

VALUE rb_gsl_sf_eval_e_int_double(int (*func)(int, double, gsl_sf_result*),
                                  VALUE n, VALUE x)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  CHECK_FIXNUM(n);
  Need_Float(x);
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(FIX2INT(n), NUM2DBL(x), rslt);
  return v;
}

VALUE rb_gsl_sf_eval_e_int_double2(int (*func)(int, double, double, gsl_sf_result*),
                                   VALUE n, VALUE x1, VALUE x2)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  CHECK_FIXNUM(n);
  Need_Float(x1); Need_Float(x2);
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(FIX2INT(n), NUM2DBL(x1), NUM2DBL(x2), rslt);
  return v;
}


VALUE rb_gsl_sf_eval_e_int_int_double(int (*func)(int, int, double, gsl_sf_result*),
                                      VALUE n1, VALUE n2, VALUE x)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  CHECK_FIXNUM(n1); CHECK_FIXNUM(n2);
  Need_Float(x);
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(FIX2INT(n1), FIX2INT(n2), NUM2DBL(x), rslt);
  return v;
}

VALUE rb_gsl_sf_eval_e_double2(int (*func)(double, double, gsl_sf_result*),
                               VALUE x1, VALUE x2)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  Need_Float(x1); Need_Float(x2);
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2DBL(x1), NUM2DBL(x2), rslt);
  return v;
}


VALUE rb_gsl_sf_eval_e_double3(int (*func)(double, double, double, gsl_sf_result*),
                               VALUE x1, VALUE x2, VALUE x3)
{
  gsl_sf_result *rslt = NULL;
  VALUE v;
  Need_Float(x1); Need_Float(x2); Need_Float(x3);
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2DBL(x1), NUM2DBL(x2),NUM2DBL(x3), rslt);
  return v;
}

VALUE rb_gsl_sf_eval_e_m(int (*func)(double, gsl_mode_t, gsl_sf_result*),
                         VALUE x, VALUE m)
{
  gsl_mode_t mode;
  char c;
  gsl_sf_result *rslt = NULL;
  VALUE v;
  Need_Float(x);
  switch (TYPE(m)) {
  case T_STRING:
    c = tolower(NUM2CHR(m));
    if (c == 'd') mode = GSL_PREC_DOUBLE;
    else if (c == 's') mode = GSL_PREC_SINGLE;
    else if (c == 'a') mode = GSL_PREC_APPROX;
    else mode = GSL_PREC_DOUBLE;
    break;
  case T_FIXNUM:
    mode = FIX2INT(m);
    break;
  default:
    rb_raise(rb_eArgError, "wrong type argument %s (String or Fixnum expected)",
             rb_class2name(CLASS_OF(m)));
    break;
  }
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2DBL(x), mode, rslt);
  return v;
}


VALUE rb_gsl_sf_eval_e_double2_m(int (*func)(double, double, gsl_mode_t, gsl_sf_result*),
                                 VALUE x1, VALUE x2, VALUE m)
{
  gsl_mode_t mode;
  char c;
  gsl_sf_result *rslt = NULL;
  VALUE v;
  Need_Float(x1);  Need_Float(x2);
  switch (TYPE(m)) {
  case T_STRING:
    c = tolower(NUM2CHR(m));
    if (c == 'd') mode = GSL_PREC_DOUBLE;
    else if (c == 's') mode = GSL_PREC_SINGLE;
    else if (c == 'a') mode = GSL_PREC_APPROX;
    else mode = GSL_PREC_DOUBLE;
    break;
  case T_FIXNUM:
    mode = FIX2INT(m);
    break;
  default:
    rb_raise(rb_eArgError, "wrong type argument %s (String or Fixnum expected)",
             rb_class2name(CLASS_OF(m)));
    break;
  }
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2DBL(x1), NUM2DBL(x2), mode, rslt);
  return v;
}

VALUE rb_gsl_sf_eval_e_double3_m(int (*func)(double, double, double, gsl_mode_t, gsl_sf_result*),
                                 VALUE x1, VALUE x2, VALUE x3, VALUE m)
{
  gsl_mode_t mode;
  char c;
  gsl_sf_result *rslt = NULL;
  VALUE v;
  Need_Float(x1); Need_Float(x2); Need_Float(x3);
  switch (TYPE(m)) {
  case T_STRING:
    c = tolower(NUM2CHR(m));
    if (c == 'd') mode = GSL_PREC_DOUBLE;
    else if (c == 's') mode = GSL_PREC_SINGLE;
    else if (c == 'a') mode = GSL_PREC_APPROX;
    else mode = GSL_PREC_DOUBLE;
    break;
  case T_FIXNUM:
    mode = FIX2INT(m);
    break;
  default:
    rb_raise(rb_eArgError, "wrong type argument %s (String or Fixnum expected)",
             rb_class2name(CLASS_OF(m)));
    break;
  }
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2DBL(x1), NUM2DBL(x2),NUM2DBL(x3), mode, rslt);
  return v;
}


VALUE rb_gsl_sf_eval_e_double4_m(int (*func)(double, double, double, double, gsl_mode_t, gsl_sf_result*),
                                 VALUE x1, VALUE x2, VALUE x3, VALUE x4, VALUE m)
{
  gsl_mode_t mode;
  char c;
  gsl_sf_result *rslt = NULL;
  VALUE v;
  Need_Float(x1); Need_Float(x2); Need_Float(x3); Need_Float(x4);
  switch (TYPE(m)) {
  case T_STRING:
    c = tolower(NUM2CHR(m));
    if (c == 'd') mode = GSL_PREC_DOUBLE;
    else if (c == 's') mode = GSL_PREC_SINGLE;
    else if (c == 'a') mode = GSL_PREC_APPROX;
    else mode = GSL_PREC_DOUBLE;
    break;
  case T_FIXNUM:
    mode = FIX2INT(m);
    break;
  default:
    rb_raise(rb_eArgError, "wrong type argument %s (String or Fixnum expected)",
             rb_class2name(CLASS_OF(m)));
    break;
  }
  v = Data_Make_Struct(cgsl_sf_result, gsl_sf_result, 0, free, rslt);
  (*func)(NUM2DBL(x1), NUM2DBL(x2),NUM2DBL(x3), NUM2DBL(x4), mode, rslt);
  return v;
}


VALUE eval_sf(double (*func)(double, gsl_mode_t), VALUE argv)
{
  VALUE ary, xx;
  size_t i, k, n;
  double val;
  gsl_vector *v = NULL, *vnew = NULL;
  gsl_matrix *mm = NULL, *mnew = NULL;
  switch (TYPE(argv)) {
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    return rb_float_new((*func)(NUM2DBL(argv), GSL_PREC_DOUBLE));
    break;
  case T_ARRAY:
    n = RARRAY_LEN(argv);
    ary = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
      xx = rb_ary_entry(argv, i);
      Need_Float(xx);
      val = (*func)(NUM2DBL(xx), GSL_PREC_DOUBLE);
      rb_ary_store(ary, i, rb_float_new(val));
    }
    return ary;
    break;
  default:
#ifdef HAVE_NARRAY_H
    if (NA_IsNArray(argv)) {
      double *ptr1, *ptr2;
      struct NARRAY *na;
      ptr1 = NA_PTR_TYPE(argv, double*);
      GetNArray(argv, na);
      n = na->total;
      ary = na_make_object(NA_DFLOAT, na->rank, na->shape, CLASS_OF(argv));
      ptr2 = NA_PTR_TYPE(ary, double*);
      for (i = 0; i < n; i++) ptr2[i] = (*func)(ptr1[i], GSL_PREC_DOUBLE);
      return ary;
    }
#endif
    if (rb_obj_is_kind_of(argv, cgsl_matrix)) {
      Data_Get_Struct(argv, gsl_matrix, mm);
      mnew = gsl_matrix_alloc(mm->size1, mm->size2);
      for (i = 0; i < mm->size1; i++) {
        for (k = 0; k < mm->size2; k++) {
          val = (*func)(gsl_matrix_get(mm, i, k), GSL_PREC_DOUBLE);
          gsl_matrix_set(mnew, i, k, val);
        }
      }
      return Data_Wrap_Struct(cgsl_matrix, 0, gsl_matrix_free, mnew);
    } else {
      CHECK_VECTOR(argv);
      Data_Get_Struct(argv, gsl_vector, v);
      n = v->size;
      vnew = gsl_vector_alloc(n);
      for (i = 0; i < n; i++) {
        val = (*func)(gsl_vector_get(v, i), GSL_PREC_DOUBLE);
        gsl_vector_set(vnew, i, val);
      }
      return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    }
    break;
  }
}

VALUE rb_gsl_sf_eval_complex(double (*f)(double), VALUE obj)
{
  gsl_complex *z, *znew, c;
  gsl_vector_complex *v, *vnew;
  gsl_matrix_complex *m, *mnew;
  size_t i, j;
  if (COMPLEX_P(obj)) {
    Data_Get_Struct(obj, gsl_complex, z);
    znew = xmalloc(sizeof(gsl_complex));
    GSL_SET_REAL(znew, (*f)(GSL_REAL(*z)));
    GSL_SET_IMAG(znew, (*f)(GSL_IMAG(*z)));
    return Data_Wrap_Struct(cgsl_complex, 0, free, znew);
  } else if (VECTOR_COMPLEX_P(obj)) {
    Data_Get_Struct(obj, gsl_vector_complex, v);
    vnew = gsl_vector_complex_alloc(v->size);
    for (i = 0; i < v->size; i++) {
      z = GSL_COMPLEX_AT(v, i);
      GSL_SET_REAL(&c, (*f)(GSL_REAL(*z)));
      GSL_SET_IMAG(&c, (*f)(GSL_IMAG(*z)));
      gsl_vector_complex_set(vnew, i, c);
    }
    return Data_Wrap_Struct(cgsl_vector_complex, 0, gsl_vector_complex_free, vnew);
  } else if (MATRIX_COMPLEX_P(obj)) {
    Data_Get_Struct(obj, gsl_matrix_complex, m);
    mnew = gsl_matrix_complex_alloc(m->size1, m->size2);
    for (i = 0; i < m->size1; i++) {
      for (j = 0; j < m->size2; j++) {
        c = gsl_matrix_complex_get(m, i, j);
        GSL_SET_REAL(&c, (*f)(GSL_REAL(c)));
        GSL_SET_IMAG(&c, (*f)(GSL_IMAG(c)));
        gsl_matrix_complex_set(mnew, i, j, c);
      }
    }
    return Data_Wrap_Struct(cgsl_matrix_complex, 0, gsl_matrix_complex_free, mnew);
  } else {
    rb_raise(rb_eTypeError,
             "wrong argument type %s "
             " (GSL::Complex or GSL::Vector::Complex expected)",
             rb_class2name(CLASS_OF(obj)));
  }
}

void Init_gsl_sf(VALUE module)
{
  VALUE mgsl_sf;
  mgsl_sf = rb_define_module_under(module, "Sf");

  cgsl_sf_result = rb_define_class_under(mgsl_sf, "Result",
                                         cGSL_Object);
  rb_define_singleton_method(cgsl_sf_result, "new", rb_gsl_sf_result_new,
                             0);
  rb_define_method(cgsl_sf_result, "print", rb_gsl_sf_result_print, 0);
  rb_define_method(cgsl_sf_result, "inspect", rb_gsl_sf_result_inspect, 0);
  rb_define_method(cgsl_sf_result, "val", rb_gsl_sf_result_val, 0);
  rb_define_method(cgsl_sf_result, "err", rb_gsl_sf_result_err, 0);
  rb_define_method(cgsl_sf_result, "to_a", rb_gsl_sf_result_to_a, 0);
  rb_define_method(cgsl_sf_result, "to_s", rb_gsl_sf_result_to_s, 0);

  cgsl_sf_result_e10 = rb_define_class_under(mgsl_sf, "Result_e10",
                                             cGSL_Object);
  rb_define_singleton_method(cgsl_sf_result_e10, "new",
                             rb_gsl_sf_result_e10_new, 0);
  rb_define_method(cgsl_sf_result_e10, "val", rb_gsl_sf_result_e10_val, 0);
  rb_define_method(cgsl_sf_result_e10, "err", rb_gsl_sf_result_e10_err, 0);
  rb_define_method(cgsl_sf_result_e10, "e10", rb_gsl_sf_result_e10_e10, 0);
  rb_define_method(cgsl_sf_result_e10, "to_a", rb_gsl_sf_result_e10_to_a, 0);
  rb_define_method(cgsl_sf_result_e10, "to_s", rb_gsl_sf_result_e10_to_s, 0);

  Init_gsl_sf_airy(mgsl_sf);
  Init_gsl_sf_bessel(mgsl_sf);
  Init_gsl_sf_clausen(mgsl_sf);
  Init_gsl_sf_coulomb(mgsl_sf);
  Init_gsl_sf_coupling(mgsl_sf);
  Init_gsl_sf_dawson(mgsl_sf);
  Init_gsl_sf_debye(mgsl_sf);
  Init_gsl_sf_dilog(mgsl_sf);
  Init_gsl_sf_elementary(mgsl_sf);
  Init_gsl_sf_ellint(mgsl_sf);
  Init_gsl_sf_elljac(mgsl_sf);
  Init_gsl_sf_erfc(mgsl_sf);
  Init_gsl_sf_exp(mgsl_sf);
  Init_gsl_sf_expint(mgsl_sf);
  Init_gsl_sf_fermi_dirac(mgsl_sf);
  Init_gsl_sf_gamma(mgsl_sf);
  Init_gsl_sf_gegenbauer(mgsl_sf);
  Init_gsl_sf_hyperg(mgsl_sf);
  Init_gsl_sf_laguerre(mgsl_sf);
  Init_gsl_sf_lambert(mgsl_sf);
  Init_gsl_sf_legendre(mgsl_sf);
  Init_gsl_sf_log(mgsl_sf);
  Init_gsl_sf_power(mgsl_sf);
  Init_gsl_sf_psi(mgsl_sf);
  Init_gsl_sf_synchrotron(mgsl_sf);
  Init_gsl_sf_transport(mgsl_sf);
  Init_gsl_sf_trigonometric(mgsl_sf);
  Init_gsl_sf_zeta(mgsl_sf);
  Init_sf_mathieu(mgsl_sf);
}
