diff options
Diffstat (limited to 'src/services/quick-answers')
| -rw-r--r-- | src/services/quick-answers/math-parser.c | 14 | ||||
| -rw-r--r-- | src/services/quick-answers/math.c | 355 | ||||
| -rw-r--r-- | src/services/quick-answers/math.h | 67 |
3 files changed, 393 insertions, 43 deletions
diff --git a/src/services/quick-answers/math-parser.c b/src/services/quick-answers/math-parser.c index bb94a66..7f93552 100644 --- a/src/services/quick-answers/math-parser.c +++ b/src/services/quick-answers/math-parser.c @@ -104,7 +104,7 @@ FoobarMathExpression* parser_process( next_op = parser_peek( ctx ); } - lhs = foobar_math_expression_operation( parser_operator( op ), lhs, rhs ); + lhs = foobar_math_expression_new_operation( parser_operator( op ), lhs, rhs ); op = parser_peek( ctx ); op_precedence = parser_operator_precedence( op ); @@ -148,11 +148,11 @@ FoobarMathExpression* parser_process_identifier( Parser* ctx ) FoobarMathToken const* token = parser_pop( ctx ); if ( parser_match_identifier( token, "pi" ) ) { - return foobar_math_expression_constant( FOOBAR_MATH_CONSTANT_PI ); + return foobar_math_expression_new_constant( FOOBAR_MATH_CONSTANT_PI ); } else if ( parser_match_identifier( token, "e" ) ) { - return foobar_math_expression_constant( FOOBAR_MATH_CONSTANT_E ); + return foobar_math_expression_new_constant( FOOBAR_MATH_CONSTANT_E ); } else if ( parser_match_identifier( token, "exp" ) ) { @@ -219,13 +219,13 @@ FoobarMathExpression* parser_process_identifier( Parser* ctx ) FoobarMathExpression* parser_process_number( Parser* ctx ) { FoobarMathToken const* token = parser_pop( ctx ); - FoobarMathValue value; + FoobarMathValue value = { 0 }; if ( !foobar_math_value_from_string( token->data, token->length, &value ) ) { return NULL; } - return foobar_math_expression_value( value ); + return foobar_math_expression_new_value( value ); } FoobarMathExpression* parser_process_function( @@ -241,7 +241,7 @@ FoobarMathExpression* parser_process_function( FoobarMathExpression* arg = parser_process_parens( ctx ); if ( !arg ) { return NULL; } - return foobar_math_expression_function( function, arg ); + return foobar_math_expression_new_function( function, arg ); } FoobarMathExpression* parser_process_parens( Parser* ctx ) @@ -268,7 +268,7 @@ FoobarMathExpression* parser_process_sign( Parser* ctx ) if ( token->type == FOOBAR_MATH_TOKEN_MINUS ) { - return foobar_math_expression_function( FOOBAR_MATH_FUNCTION_NEGATE, result ); + return foobar_math_expression_new_function( FOOBAR_MATH_FUNCTION_NEGATE, result ); } else { diff --git a/src/services/quick-answers/math.c b/src/services/quick-answers/math.c index 5ec79c0..4596af2 100644 --- a/src/services/quick-answers/math.c +++ b/src/services/quick-answers/math.c @@ -1,4 +1,7 @@ +#define _GNU_SOURCE + #include "services/quick-answers/math.h" +#include <math.h> struct _FoobarMathExpression { @@ -31,7 +34,7 @@ struct _FoobarMathExpression }; }; -FoobarMathExpression* foobar_math_expression_value( FoobarMathValue value ) +FoobarMathExpression* foobar_math_expression_new_value( FoobarMathValue value ) { FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 ); res->type = FOOBAR_MATH_EXPRESSION_VALUE; @@ -39,7 +42,7 @@ FoobarMathExpression* foobar_math_expression_value( FoobarMathValue value ) return res; } -FoobarMathExpression* foobar_math_expression_function( +FoobarMathExpression* foobar_math_expression_new_function( FoobarMathFunction function, FoobarMathExpression* input ) { @@ -50,7 +53,7 @@ FoobarMathExpression* foobar_math_expression_function( return res; } -FoobarMathExpression* foobar_math_expression_constant( FoobarMathConstant constant ) +FoobarMathExpression* foobar_math_expression_new_constant( FoobarMathConstant constant ) { FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 ); res->type = FOOBAR_MATH_EXPRESSION_CONSTANT; @@ -58,7 +61,7 @@ FoobarMathExpression* foobar_math_expression_constant( FoobarMathConstant consta return res; } -FoobarMathExpression* foobar_math_expression_operation( +FoobarMathExpression* foobar_math_expression_new_operation( FoobarMathOperation operation, FoobarMathExpression* lhs, FoobarMathExpression* rhs ) @@ -107,7 +110,7 @@ void foobar_math_expression_print( { case FOOBAR_MATH_EXPRESSION_VALUE: { - g_autofree gchar* val = foobar_math_value_format( expr->value.v ); + g_autofree gchar* val = foobar_math_value_to_string( expr->value.v ); g_print( "%s\n", val ); break; } @@ -210,6 +213,13 @@ void foobar_math_expression_print( } } +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; +} + gboolean foobar_math_value_from_string( gchar const* input, gsize input_length, @@ -230,9 +240,8 @@ gboolean foobar_math_value_from_string( } } - out_value->type = FOOBAR_MATH_VALUE_INT; + foobar_math_value_new_int( out_value ); out_value->int_value.decimal_places = input_length - 1 - sep_pos; - mpz_init( out_value->int_value.v ); if ( mpz_init_set_str( out_value->int_value.v, without_sep, 10 ) == -1 ) { mpz_clear( out_value->int_value.v ); @@ -242,22 +251,173 @@ gboolean foobar_math_value_from_string( return TRUE; } +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; +} + +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; + } +} + void foobar_math_value_free( FoobarMathValue value ) { - if ( value.type == FOOBAR_MATH_VALUE_INT ) + switch ( value.type ) { - mpz_clear( value.int_value.v ); + case FOOBAR_MATH_VALUE_INT: + mpz_clear( value.int_value.v ); + break; + case FOOBAR_MATH_VALUE_FLOAT: + break; + default: + g_warn_if_reached( ); + break; } } -gboolean foobar_math_evaluate( - FoobarMathExpression const* expression, - FoobarMathValue* out_value ) +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; + } +} + +gboolean foobar_math_value_add( + FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ) +{ + if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) ) + { + foobar_math_value_new_int( out_value ); + 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; +} + +gboolean foobar_math_value_sub( + FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ) +{ + if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) ) + { + foobar_math_value_new_int( out_value ); + 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; +} + +gboolean foobar_math_value_mul( + FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ) +{ + if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) ) + { + foobar_math_value_new_int( out_value ); + 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; +} + +gboolean foobar_math_value_div( + FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ) +{ + if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) ) + { + if ( mpz_sgn( rhs.int_value.v ) == 0 ) { return FALSE; } + + foobar_math_value_new_int( out_value ); + 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; +} + +gboolean foobar_math_value_pow( + FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ) { - return FALSE; + if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) && + mpz_fits_ulong_p( rhs.int_value.v ) ) + { + foobar_math_value_new_int( out_value ); + mpz_pow_ui( out_value->int_value.v, lhs.int_value.v, mpz_get_ui( 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 ); + 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; +} + +gboolean foobar_math_value_is_simple_int( FoobarMathValue value ) +{ + return value.type == FOOBAR_MATH_VALUE_INT && value.int_value.decimal_places == 0; } -gchar* foobar_math_value_format( FoobarMathValue value ) +gchar* foobar_math_value_to_string( FoobarMathValue value ) { switch ( value.type ) { @@ -296,3 +456,170 @@ gchar* foobar_math_value_format( FoobarMathValue value ) return NULL; } } + +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; + } +} + +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; + } +} diff --git a/src/services/quick-answers/math.h b/src/services/quick-answers/math.h index 77c587a..6bc07fe 100644 --- a/src/services/quick-answers/math.h +++ b/src/services/quick-answers/math.h @@ -98,27 +98,50 @@ typedef enum typedef struct _FoobarMathExpression FoobarMathExpression; -FoobarMathToken* foobar_math_lex ( gchar const* input, - gsize input_length, - gsize* out_count ); -FoobarMathExpression* foobar_math_parse ( FoobarMathToken const* tokens, - gsize tokens_count ); -FoobarMathExpression* foobar_math_expression_value ( FoobarMathValue value ); -FoobarMathExpression* foobar_math_expression_function ( FoobarMathFunction function, - FoobarMathExpression* input ); -FoobarMathExpression* foobar_math_expression_constant ( FoobarMathConstant constant ); -FoobarMathExpression* foobar_math_expression_operation( FoobarMathOperation operation, - FoobarMathExpression* lhs, - FoobarMathExpression* rhs ); -void foobar_math_expression_print ( FoobarMathExpression const* expr, - gint indentation ); -void foobar_math_expression_free ( FoobarMathExpression* expression ); -gboolean foobar_math_value_from_string ( gchar const* input, - gsize input_length, - FoobarMathValue* out_value ); -void foobar_math_value_free ( FoobarMathValue value ); -gchar* foobar_math_value_format ( FoobarMathValue value ); -gboolean foobar_math_evaluate ( FoobarMathExpression const* expression, - FoobarMathValue* out_value ); +FoobarMathToken* foobar_math_lex ( gchar const* input, + gsize input_length, + gsize* out_count ); +FoobarMathExpression* foobar_math_parse ( FoobarMathToken const* tokens, + gsize tokens_count ); +FoobarMathExpression* foobar_math_expression_new_value ( FoobarMathValue value ); +FoobarMathExpression* foobar_math_expression_new_function ( FoobarMathFunction function, + FoobarMathExpression* input ); +FoobarMathExpression* foobar_math_expression_new_constant ( FoobarMathConstant constant ); +FoobarMathExpression* foobar_math_expression_new_operation( FoobarMathOperation operation, + FoobarMathExpression* lhs, + FoobarMathExpression* rhs ); +void foobar_math_expression_print ( FoobarMathExpression const* expr, + gint indentation ); +void foobar_math_expression_free ( FoobarMathExpression* expression ); +void foobar_math_value_new_int ( FoobarMathValue* out_value ); +gboolean foobar_math_value_from_string ( gchar const* input, + gsize input_length, + FoobarMathValue* out_value ); +void foobar_math_value_from_float ( long double value, + FoobarMathValue* out_value ); +void foobar_math_value_copy ( FoobarMathValue value, + FoobarMathValue* out_value ); +void foobar_math_value_free ( FoobarMathValue value ); +void foobar_math_value_negate ( FoobarMathValue* value ); +gboolean foobar_math_value_add ( FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ); +gboolean foobar_math_value_sub ( FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ); +gboolean foobar_math_value_mul ( FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ); +gboolean foobar_math_value_div ( FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ); +gboolean foobar_math_value_pow ( FoobarMathValue lhs, + FoobarMathValue rhs, + FoobarMathValue* out_value ); +gboolean foobar_math_value_is_simple_int ( FoobarMathValue value ); +gchar* foobar_math_value_to_string ( FoobarMathValue value ); +long double foobar_math_value_to_float ( FoobarMathValue value ); +gboolean foobar_math_evaluate ( FoobarMathExpression const* expr, + FoobarMathValue* out_value ); G_END_DECLS |
