summaryrefslogtreecommitdiff
path: root/csrc/services/quick-answers/math.c
diff options
context:
space:
mode:
Diffstat (limited to 'csrc/services/quick-answers/math.c')
-rw-r--r--csrc/services/quick-answers/math.c851
1 files changed, 851 insertions, 0 deletions
diff --git a/csrc/services/quick-answers/math.c b/csrc/services/quick-answers/math.c
new file mode 100644
index 0000000..69219e2
--- /dev/null
+++ b/csrc/services/quick-answers/math.c
@@ -0,0 +1,851 @@
+#define _GNU_SOURCE
+
+#include "services/quick-answers/math.h"
+#include <math.h>
+
+//
+// FoobarMathExpression:
+//
+// A fully parsed mathematical expression which can be evaluated, for example "sin(pi) - 3" would be represented as:
+// - Operation: SUB
+// - Function: SIN
+// - Constant: PI
+// - Value: 3
+//
+
+struct _FoobarMathExpression
+{
+ FoobarMathExpressionType type;
+
+ union
+ {
+ struct
+ {
+ FoobarMathValue v;
+ } value;
+
+ struct
+ {
+ FoobarMathFunction f;
+ FoobarMathExpression* input;
+ } function;
+
+ struct
+ {
+ FoobarMathConstant c;
+ } constant;
+
+ struct
+ {
+ FoobarMathOperation o;
+ FoobarMathExpression* lhs;
+ FoobarMathExpression* rhs;
+ } operation;
+ };
+};
+
+//
+// FoobarMathValue:
+//
+// Representation of a value. The goal of this memory structure is to store the value as an integer for as long as
+// possible. Because of this, there is a "decimal_places" field, so the actual value of an integer is
+// $v * 10^(-decimal_places)$.
+//
+// If absolutely necessary (for example, because we use a function like sin), the value is converted to a floating-point
+// value.
+//
+// Note that we use GMP to store the integer value to allow for arbitrarily large values. Because of this, the structure
+// should always be freed using foobar_math_value_free.
+//
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Expressions
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Helper to allocate a new FoobarMathExpression representing a value.
+//
+// Ownership of "value" is transferred to the new expression.
+//
+FoobarMathExpression* foobar_math_expression_new_value( FoobarMathValue value )
+{
+ FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 );
+ res->type = FOOBAR_MATH_EXPRESSION_VALUE;
+ res->value.v = value;
+ return res;
+}
+
+//
+// Helper to allocate a new FoobarMathExpression representing a function to be evaluated with another expression as its
+// parameter.
+//
+// Ownership of "input" is transferred to the new expression.
+//
+FoobarMathExpression* foobar_math_expression_new_function(
+ FoobarMathFunction function,
+ FoobarMathExpression* input )
+{
+ FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 );
+ res->type = FOOBAR_MATH_EXPRESSION_FUNCTION;
+ res->function.f = function;
+ res->function.input = input;
+ return res;
+}
+
+//
+// Helper to allocate a new FoobarMathExpression representing a known constant.
+//
+FoobarMathExpression* foobar_math_expression_new_constant( FoobarMathConstant constant )
+{
+ FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 );
+ res->type = FOOBAR_MATH_EXPRESSION_CONSTANT;
+ res->constant.c = constant;
+ return res;
+}
+
+//
+// Helper to allocate a new FoobarMathExpression representing an operation to be evaluated with two expressions as its
+// parameters.
+//
+// Ownership of "lhs" and "rhs" is transferred to the new expression.
+//
+FoobarMathExpression* foobar_math_expression_new_operation(
+ FoobarMathOperation operation,
+ FoobarMathExpression* lhs,
+ FoobarMathExpression* rhs )
+{
+ FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 );
+ res->type = FOOBAR_MATH_EXPRESSION_OPERATION;
+ res->operation.o = operation;
+ res->operation.lhs = lhs;
+ res->operation.rhs = rhs;
+ return res;
+}
+
+//
+// Release the memory associated with a mathematical expression, along with the expressions it owns.
+//
+void foobar_math_expression_free( FoobarMathExpression* expression )
+{
+ if ( expression )
+ {
+ switch ( expression->type )
+ {
+ case FOOBAR_MATH_EXPRESSION_VALUE:
+ foobar_math_value_free( expression->value.v );
+ break;
+ case FOOBAR_MATH_EXPRESSION_FUNCTION:
+ foobar_math_expression_free( expression->function.input );
+ break;
+ case FOOBAR_MATH_EXPRESSION_CONSTANT:
+ break;
+ case FOOBAR_MATH_EXPRESSION_OPERATION:
+ foobar_math_expression_free( expression->operation.lhs );
+ foobar_math_expression_free( expression->operation.rhs );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+
+ g_free( expression );
+ }
+}
+
+//
+// Print a tree representation of a mathematical expression.
+//
+// This is mainly useful for debugging. "indentation" describes the current indentation level for the recursive call
+// (should be 0 initially).
+//
+void foobar_math_expression_print(
+ FoobarMathExpression const* expr,
+ gint indentation )
+{
+ for (gint i = 0; i < indentation; ++i) { g_print("| "); }
+ switch ( expr->type )
+ {
+ case FOOBAR_MATH_EXPRESSION_VALUE:
+ {
+ g_autofree gchar* val = foobar_math_value_to_string( expr->value.v );
+ g_print( "%s\n", val );
+ break;
+ }
+ case FOOBAR_MATH_EXPRESSION_FUNCTION:
+ switch ( expr->function.f )
+ {
+ case FOOBAR_MATH_FUNCTION_NEGATE:
+ g_print( "-\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_SIN:
+ g_print( "sin\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_COS:
+ g_print( "cos\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_SEC:
+ g_print( "sec\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_CSC:
+ g_print( "csc\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_TAN:
+ g_print( "tan\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_COT:
+ g_print( "cot\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCSIN:
+ g_print( "arcsin\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCCOS:
+ g_print( "arccos\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCSEC:
+ g_print( "arcsec\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCCSC:
+ g_print( "arccsc\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCTAN:
+ g_print( "arctan\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCCOT:
+ g_print( "arccot\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_EXP:
+ g_print( "exp\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_SQRT:
+ g_print( "sqrt\n" );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+ foobar_math_expression_print( expr->function.input, indentation + 1 );
+ break;
+ case FOOBAR_MATH_EXPRESSION_CONSTANT:
+ switch ( expr->constant.c )
+ {
+ case FOOBAR_MATH_CONSTANT_PI:
+ g_print( "pi\n" );
+ break;
+ case FOOBAR_MATH_CONSTANT_E:
+ g_print( "e\n" );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+ break;
+ case FOOBAR_MATH_EXPRESSION_OPERATION:
+ switch ( expr->operation.o )
+ {
+ case FOOBAR_MATH_OPERATION_ADD:
+ g_print( "+\n" );
+ break;
+ case FOOBAR_MATH_OPERATION_SUB:
+ g_print( "-\n" );
+ break;
+ case FOOBAR_MATH_OPERATION_MUL:
+ g_print( "*\n" );
+ break;
+ case FOOBAR_MATH_OPERATION_DIV:
+ g_print( "/\n" );
+ break;
+ case FOOBAR_MATH_OPERATION_POW:
+ g_print( "^\n" );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+ foobar_math_expression_print( expr->operation.lhs, indentation + 1 );
+ foobar_math_expression_print( expr->operation.rhs, indentation + 1 );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+}
+
+//
+// Evaluate an expression, producing a value.
+//
+// On success (indicated by the return value TRUE), the value should be freed using foobar_math_value_free.
+//
+gboolean foobar_math_evaluate(
+ FoobarMathExpression const* expr,
+ FoobarMathValue* out_value )
+{
+ switch ( expr->type )
+ {
+ case FOOBAR_MATH_EXPRESSION_VALUE:
+ foobar_math_value_copy( expr->value.v, out_value );
+ return TRUE;
+ case FOOBAR_MATH_EXPRESSION_CONSTANT:
+ switch ( expr->constant.c )
+ {
+ case FOOBAR_MATH_CONSTANT_PI:
+ foobar_math_value_from_float( M_PIl, out_value );
+ return TRUE;
+ case FOOBAR_MATH_CONSTANT_E:
+ foobar_math_value_from_float( M_El, out_value );
+ return TRUE;
+ default:
+ g_warn_if_reached( );
+ return FALSE;
+ }
+ case FOOBAR_MATH_EXPRESSION_FUNCTION:
+ {
+ FoobarMathValue input = { 0 };
+ if ( !foobar_math_evaluate( expr->function.input, &input ) ) { return FALSE; }
+
+ if ( expr->function.f == FOOBAR_MATH_FUNCTION_NEGATE )
+ {
+ *out_value = input;
+ foobar_math_value_negate( out_value );
+ return TRUE;
+ }
+
+ long double input_float = foobar_math_value_to_float( input );
+ foobar_math_value_free( input );
+
+ long double result = NAN;
+ switch ( expr->function.f )
+ {
+ case FOOBAR_MATH_FUNCTION_SIN:
+ result = sinl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_COS:
+ result = cosl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_SEC:
+ result = 1.L / cosl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_CSC:
+ result = 1.L / sinl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_TAN:
+ result = tanl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_COT:
+ result = 1.L / tanl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCSIN:
+ result = asinl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCCOS:
+ result = acosl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCSEC:
+ result = acosl( 1.L / input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCCSC:
+ result = asinl( 1.L / input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCTAN:
+ result = atanl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCCOT:
+ result = atanl( 1.L / input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_EXP:
+ result = expl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_SQRT:
+ result = sqrtl( input_float );
+ break;
+ case FOOBAR_MATH_FUNCTION_NEGATE:
+ default:
+ g_warn_if_reached( );
+ return FALSE;
+ }
+
+ if ( isnanl( result ) || isinfl( result ) ) { return FALSE; }
+
+ foobar_math_value_from_float( result, out_value );
+ return TRUE;
+ }
+ case FOOBAR_MATH_EXPRESSION_OPERATION:
+ {
+ FoobarMathValue lhs = { 0 };
+ if ( !foobar_math_evaluate( expr->operation.lhs, &lhs ) ) { return FALSE; }
+ FoobarMathValue rhs = { 0 };
+ if ( !foobar_math_evaluate( expr->operation.rhs, &rhs ) )
+ {
+ foobar_math_value_free( lhs );
+ return FALSE;
+ }
+
+ gboolean success = FALSE;
+ switch ( expr->operation.o )
+ {
+ case FOOBAR_MATH_OPERATION_ADD:
+ success = foobar_math_value_add( &lhs, &rhs, out_value );
+ break;
+ case FOOBAR_MATH_OPERATION_SUB:
+ success = foobar_math_value_sub( &lhs, &rhs, out_value );
+ break;
+ case FOOBAR_MATH_OPERATION_MUL:
+ success = foobar_math_value_mul( &lhs, &rhs, out_value );
+ break;
+ case FOOBAR_MATH_OPERATION_DIV:
+ success = foobar_math_value_div( &lhs, &rhs, out_value );
+ break;
+ case FOOBAR_MATH_OPERATION_POW:
+ success = foobar_math_value_pow( &lhs, &rhs, out_value );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+
+ foobar_math_value_free( lhs );
+ foobar_math_value_free( rhs );
+
+ return success;
+ }
+ default:
+ g_warn_if_reached( );
+ return FALSE;
+ }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Values
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Initialize a new integer value to zero.
+//
+// After this, the structure should be freed using foobar_math_value_free.
+//
+void foobar_math_value_new_int( FoobarMathValue* out_value )
+{
+ out_value->type = FOOBAR_MATH_VALUE_INT;
+ mpz_init( out_value->int_value.v );
+ out_value->int_value.decimal_places = 0;
+}
+
+//
+// Initialize a new value from its floating point representation.
+//
+// Although technically not necessary, you should still call foobar_math_value_free on the returned value.
+//
+void foobar_math_value_from_float(
+ long double value,
+ FoobarMathValue* out_value )
+{
+ out_value->type = FOOBAR_MATH_VALUE_FLOAT;
+ out_value->float_value.v = value;
+}
+
+//
+// Parse a value from its string representation. This will always return an integer representation.
+//
+// On success (return value TRUE), the structure should be freed using foobar_math_value_free.
+//
+gboolean foobar_math_value_from_string(
+ gchar const* input,
+ gsize input_length,
+ FoobarMathValue* out_value )
+{
+ g_autofree gchar* without_sep = g_new0( gchar, input_length + 1 );
+ gsize sep_pos = input_length - 1;
+ gsize j = 0;
+ for ( gsize i = 0; i < input_length; ++i )
+ {
+ if ( input[i] == '.' )
+ {
+ sep_pos = i;
+ }
+ else
+ {
+ without_sep[j++] = input[i];
+ }
+ }
+
+ foobar_math_value_new_int( out_value );
+ out_value->int_value.decimal_places = input_length - 1 - sep_pos;
+ if ( mpz_init_set_str( out_value->int_value.v, without_sep, 10 ) == -1 )
+ {
+ mpz_clear( out_value->int_value.v );
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+//
+// Create a copy of a value, allocating necessary memory.
+//
+// After this, the structure should be freed using foobar_math_value_free.
+//
+void foobar_math_value_copy(
+ FoobarMathValue value,
+ FoobarMathValue* out_value )
+{
+ *out_value = value;
+
+ switch ( value.type )
+ {
+ case FOOBAR_MATH_VALUE_INT:
+ mpz_init_set( out_value->int_value.v, value.int_value.v );
+ break;
+ case FOOBAR_MATH_VALUE_FLOAT:
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+}
+
+//
+// Release memory associated with a value.
+//
+// This is necessary because the integer representation is stored using GMP, which can store arbitrarily large values.
+//
+void foobar_math_value_free( FoobarMathValue value )
+{
+ switch ( value.type )
+ {
+ case FOOBAR_MATH_VALUE_INT:
+ mpz_clear( value.int_value.v );
+ break;
+ case FOOBAR_MATH_VALUE_FLOAT:
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Operations
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Operation equivalent to "value *= -1".
+//
+void foobar_math_value_negate( FoobarMathValue* value )
+{
+ switch ( value->type )
+ {
+ case FOOBAR_MATH_VALUE_INT:
+ mpz_neg( value->int_value.v, value->int_value.v );
+ break;
+ case FOOBAR_MATH_VALUE_FLOAT:
+ value->float_value.v = -value->float_value.v;
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+}
+
+//
+// Operation setting "out_value = lhs + rhs".
+//
+// After this, the result value should be freed using foobar_math_value_free.
+//
+gboolean foobar_math_value_add(
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
+ FoobarMathValue* out_value )
+{
+ if ( foobar_math_value_unify_integers( lhs, rhs ) )
+ {
+ foobar_math_value_new_int( out_value );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places;
+ mpz_add( out_value->int_value.v, lhs->int_value.v, rhs->int_value.v );
+ return TRUE;
+ }
+
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
+ foobar_math_value_from_float( lhs_float + rhs_float, out_value );
+ return TRUE;
+}
+
+//
+// Operation setting "out_value = lhs - rhs".
+//
+// After this, the result value should be freed using foobar_math_value_free.
+//
+gboolean foobar_math_value_sub(
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
+ FoobarMathValue* out_value )
+{
+ if ( foobar_math_value_unify_integers( lhs, rhs ) )
+ {
+ foobar_math_value_new_int( out_value );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places;
+ mpz_sub( out_value->int_value.v, lhs->int_value.v, rhs->int_value.v );
+ return TRUE;
+ }
+
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
+ foobar_math_value_from_float( lhs_float - rhs_float, out_value );
+ return TRUE;
+}
+
+//
+// Operation setting "out_value = lhs * rhs".
+//
+// After this, the result value should be freed using foobar_math_value_free.
+//
+gboolean foobar_math_value_mul(
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
+ FoobarMathValue* out_value )
+{
+ if ( lhs->type == FOOBAR_MATH_VALUE_INT && rhs->type == FOOBAR_MATH_VALUE_INT )
+ {
+ foobar_math_value_new_int( out_value );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places + rhs->int_value.decimal_places;
+ mpz_mul( out_value->int_value.v, lhs->int_value.v, rhs->int_value.v );
+ return TRUE;
+ }
+
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
+ foobar_math_value_from_float( lhs_float * rhs_float, out_value );
+ return TRUE;
+}
+
+//
+// Operation setting "out_value = lhs / rhs".
+//
+// After this, the result value should be freed using foobar_math_value_free.
+//
+gboolean foobar_math_value_div(
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
+ FoobarMathValue* out_value )
+{
+ if ( lhs->type == FOOBAR_MATH_VALUE_INT && rhs->type == FOOBAR_MATH_VALUE_INT )
+ {
+ if ( mpz_sgn( rhs->int_value.v ) == 0 ) { return FALSE; }
+
+ if ( rhs->int_value.decimal_places > lhs->int_value.decimal_places )
+ {
+ foobar_math_value_unify_integers( lhs, rhs );
+ }
+
+ foobar_math_value_new_int( out_value );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places - rhs->int_value.decimal_places;
+ mpz_t rem;
+ mpz_init( rem );
+ mpz_divmod( out_value->int_value.v, rem, lhs->int_value.v, rhs->int_value.v );
+ gboolean result_is_int = mpz_sgn( rem ) == 0;
+ mpz_clear( rem );
+
+ if ( result_is_int ) { return TRUE; }
+ foobar_math_value_free( *out_value );
+ }
+
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
+ long double result = lhs_float / rhs_float;
+ if ( isnanl( result ) || isinfl( result ) ) { return FALSE; }
+ foobar_math_value_from_float( result, out_value );
+ return TRUE;
+}
+
+//
+// Operation setting "out_value = lhs ^ rhs".
+//
+// After this, the result value should be freed using foobar_math_value_free.
+//
+gboolean foobar_math_value_pow(
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
+ FoobarMathValue* out_value )
+{
+ if ( lhs->type == FOOBAR_MATH_VALUE_INT &&
+ rhs->type == FOOBAR_MATH_VALUE_INT &&
+ rhs->int_value.decimal_places == 0 &&
+ mpz_fits_ulong_p( rhs->int_value.v ) )
+ {
+ unsigned long exp = mpz_get_ui( rhs->int_value.v );
+ foobar_math_value_new_int( out_value );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places * exp;
+ mpz_pow_ui( out_value->int_value.v, lhs->int_value.v, exp );
+ return TRUE;
+ }
+
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
+ long double result = powl( lhs_float, rhs_float );
+ if ( isnanl( result ) || isinfl( result ) ) { return FALSE; }
+ foobar_math_value_from_float( result, out_value );
+ return TRUE;
+}
+
+//
+// Ensure that two integer values have the same number of decimal places.
+//
+// This may change the in-memory-representation of a value, but not its actual value. It only increases the precision of
+// one value
+//
+// If either a or b is not stored as an integer, this will return FALSE.
+//
+gboolean foobar_math_value_unify_integers(
+ FoobarMathValue* a,
+ FoobarMathValue* b )
+{
+ if ( a->type != FOOBAR_MATH_VALUE_INT || b->type != FOOBAR_MATH_VALUE_INT ) { return FALSE; }
+
+ // Extend the less precise representation so both numbers have the same number of decimal places.
+
+ gsize decimal_places = MAX( a->int_value.decimal_places, b->int_value.decimal_places );
+
+ mpz_t exp;
+ mpz_init( exp );
+
+ if ( decimal_places > a->int_value.decimal_places )
+ {
+ mpz_ui_pow_ui( exp, 10, decimal_places - a->int_value.decimal_places );
+ mpz_mul( a->int_value.v, a->int_value.v, exp );
+ }
+
+ if ( decimal_places > b->int_value.decimal_places )
+ {
+ mpz_ui_pow_ui( exp, 10, decimal_places - b->int_value.decimal_places );
+ mpz_mul( b->int_value.v, b->int_value.v, exp );
+ }
+
+ mpz_clear( exp );
+
+ a->int_value.decimal_places = b->int_value.decimal_places = decimal_places;
+
+ return TRUE;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Conversions
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Format a value as a string.
+//
+// The result is a null-terminated string which should be freed using g_free.
+//
+gchar* foobar_math_value_to_string( FoobarMathValue value )
+{
+ switch ( value.type )
+ {
+ case FOOBAR_MATH_VALUE_INT:
+ {
+ gsize cap = mpz_sizeinbase( value.int_value.v, 10 );
+ cap = MAX( cap, value.int_value.decimal_places + 1 ); // pad with zeroes if necessary
+ gchar* result = g_new0( gchar, cap + 3 ); // length + null terminator + sign + separator
+ mpz_get_str( result, 10, value.int_value.v );
+ gsize len = strlen( result ); // actual size may be 1 char less (+ len now also includes sign)
+ gboolean has_sign = result[0] == '-';
+ if ( has_sign )
+ {
+ // add the sign back later, makes everything else easier
+ result += 1;
+ len -= 1;
+ }
+ // pad with leading zeroes if necessary
+ gsize padded_len = MAX( len, value.int_value.decimal_places + 1 );
+ memmove( &result[padded_len - len], result, len + 1 );
+ memset( result, '0', padded_len - len );
+ len = padded_len;
+ gboolean trailing_zeros = TRUE;
+ gsize decimal_places = value.int_value.decimal_places;
+ gsize actual_decimal_places = decimal_places;
+ for ( gsize i = 0; i < decimal_places; ++i )
+ {
+ if ( trailing_zeros && result[len - 1 - i] == '0' )
+ {
+ result[len - 1 - i] = '\0';
+ actual_decimal_places -= 1;
+ }
+ else
+ {
+ trailing_zeros = FALSE;
+ }
+ }
+ if ( actual_decimal_places > 0 )
+ {
+ memmove( &result[len + 1 - decimal_places], &result[len - decimal_places], decimal_places + 1 );
+ result[len - decimal_places] = '.';
+ }
+ if ( has_sign )
+ {
+ result -= 1;
+ len += 1;
+ }
+ return result;
+ }
+ case FOOBAR_MATH_VALUE_FLOAT:
+ {
+ gchar* result = g_strdup_printf( "%.30Lf", value.float_value.v );
+
+ // trim trailing zeros
+ size_t len = strlen( result );
+ for ( gsize i = 0; i < len; ++i )
+ {
+ if ( result[len - 1 - i] == '.' )
+ {
+ result[len - 1 - i] = '\0';
+ break;
+ }
+ else if ( result[len - 1 - i] == '0' )
+ {
+ result[len - 1 - i] = '\0';
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return result;
+ }
+ default:
+ g_warn_if_reached( );
+ return NULL;
+ }
+}
+
+//
+// Convert a value to its floating-point representation.
+//
+// This may be a lossy conversion and should only be done if absolutely necessary.
+//
+long double foobar_math_value_to_float( FoobarMathValue value )
+{
+ switch ( value.type )
+ {
+ case FOOBAR_MATH_VALUE_INT:
+ {
+ mpz_t exp, integer, frac;
+ mpz_inits( exp, integer, frac, NULL );
+ mpz_ui_pow_ui( exp, 10, value.int_value.decimal_places );
+ mpz_mod( frac, value.int_value.v, exp );
+ mpz_div( integer, value.int_value.v, exp );
+ mpf_t f_exp, f_frac;
+ mpf_inits( f_exp, f_frac, NULL );
+ mpf_set_z( f_exp, exp );
+ mpf_set_z( f_frac, frac );
+ mpf_div( f_frac, f_frac, f_exp );
+ long double result = mpz_get_d( integer ) + mpf_get_d( f_frac );
+ mpf_clears( f_exp, f_frac, NULL );
+ mpz_clears( exp, integer, frac, NULL );
+ return result;
+ }
+ case FOOBAR_MATH_VALUE_FLOAT:
+ return value.float_value.v;
+ default:
+ g_warn_if_reached( );
+ return 0;
+ }
+}