Package ioke.lang

Source Code of ioke.lang.Decimal

/*
* See LICENSE file in distribution for copyright and licensing information.
*/
package ioke.lang;

import java.math.BigDecimal;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;

import java.util.Arrays;
import java.util.Locale;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import ioke.lang.exceptions.ControlFlow;

import gnu.math.RatNum;
import gnu.math.BigSquareRoot;

/**
*
* @author <a href="mailto:ola.bini@gmail.com">Ola Bini</a>
*/
public class Decimal extends IokeData {
    private final static DecimalFormatSymbols SYMBOLS = new DecimalFormatSymbols(Locale.US);
    private final BigDecimal value;

    public Decimal(String textRepresentation) {
        this.value = new BigDecimal(textRepresentation).stripTrailingZeros();
    }

    public Decimal(BigDecimal value) {
        this.value = value;
    }

    public static Decimal decimal(String val) {
        return new Decimal(val);
    }

    public static Decimal decimal(RatNum val) {
        return new Decimal(val.asBigDecimal());
    }

    public static Decimal decimal(BigDecimal val) {
        return new Decimal(val);
    }

    public static BigDecimal value(Object number) {
        return ((Decimal)IokeObject.data(number)).value;
    }

    public String asJavaString() {
        DecimalFormat format = new DecimalFormat("0.0", SYMBOLS);
        format.setMaximumFractionDigits(340);
        return format.format(value);
    }

    @Override
    public String toString() {
        return asJavaString();
    }

    @Override
    public String toString(IokeObject obj) {
        return asJavaString();
    }

    @Override
    public IokeObject convertToDecimal(IokeObject self, IokeObject m, final IokeObject context, boolean signalCondition) throws ControlFlow {
        return self;
    }

    public static String getInspect(Object on) {
        return ((Decimal)(IokeObject.data(on))).inspect(on);
    }

    public String inspect(Object obj) {
        return asJavaString();
    }

    @Override
    public void init(IokeObject obj) throws ControlFlow {
        final Runtime runtime = obj.runtime;
        final IokeObject decimal = obj;

        decimal.setKind("Number Decimal");
        runtime.decimal = decimal;

        obj.registerMethod(runtime.newNativeMethod("returns a hash for the decimal number", new NativeMethod.WithNoArguments("hash") {
                @Override
                public Object activate(IokeObject method, IokeObject context, IokeObject message, Object on) throws ControlFlow {
                    getArguments().getEvaluatedArguments(context, message, on, new ArrayList<Object>(), new HashMap<String, Object>());
                    return context.runtime.newNumber(((Decimal)IokeObject.data(on)).value.hashCode());
                }
            }));

        obj.registerMethod(runtime.newNativeMethod("returns the square root of the receiver. this should return the same result as calling ** with 0.5", new NativeMethod.WithNoArguments("sqrt") {
                @Override
                public Object activate(IokeObject method, IokeObject context, IokeObject message, Object on) throws ControlFlow {
                    getArguments().getEvaluatedArguments(context, message, on, new ArrayList<Object>(), new HashMap<String, Object>());


                    BigDecimal value = ((Decimal)IokeObject.data(on)).value;

                    if(value.compareTo(BigDecimal.ZERO) < 1) {
                        final IokeObject condition = IokeObject.as(IokeObject.getCellChain(context.runtime.condition,
                                                                                           message,
                                                                                           context,
                                                                                           "Error",
                                                                                           "Arithmetic"), context).mimic(message, context);
                        condition.setCell("message", message);
                        condition.setCell("context", context);
                        condition.setCell("receiver", on);

                        context.runtime.errorCondition(condition);
                    }

                    return context.runtime.newDecimal(new BigSquareRoot().get(value));
                }
            }));

        obj.registerMethod(runtime.newNativeMethod("returns true if the left hand side decimal is equal to the right hand side decimal.", new TypeCheckingNativeMethod("==") {
                private final TypeCheckingArgumentsDefinition ARGUMENTS = TypeCheckingArgumentsDefinition
                    .builder()
                    .receiverMustMimic(runtime.decimal)
                    .withRequiredPositional("other")
                    .getArguments();

                @Override
                public TypeCheckingArgumentsDefinition getArguments() {
                    return ARGUMENTS;
                }

                @Override
                public Object activate(IokeObject self, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    getArguments().getEvaluatedArguments(context, message, on, args, new HashMap<String, Object>());
                    Decimal d = (Decimal)IokeObject.data(on);
                    Object other = args.get(0);
                    return ((other instanceof IokeObject) &&
                            (IokeObject.data(other) instanceof Decimal)
                            && ((on == runtime.decimal && other == on) ||
                                d.value.equals(((Decimal)IokeObject.data(other)).value))) ? context.runtime._true : context.runtime._false;
                }
            }));

        decimal.registerMethod(runtime.newNativeMethod("Returns a text representation of the object", new NativeMethod.WithNoArguments("asText") {
                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    return runtime.newText(on.toString());
                }
            }));

        decimal.registerMethod(obj.runtime.newNativeMethod("Returns a text inspection of the object", new TypeCheckingNativeMethod.WithNoArguments("inspect", decimal) {
                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    return method.runtime.newText(Decimal.getInspect(on));
                }
            }));

        decimal.registerMethod(obj.runtime.newNativeMethod("Returns a brief text inspection of the object", new TypeCheckingNativeMethod.WithNoArguments("notice", decimal) {
                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    return method.runtime.newText(Decimal.getInspect(on));
                }
            }));

        decimal.registerMethod(runtime.newNativeMethod("compares this number against the argument, true if this number is the same, otherwise false", new TypeCheckingNativeMethod("==") {
                private final TypeCheckingArgumentsDefinition ARGUMENTS = TypeCheckingArgumentsDefinition
                    .builder()
                    .receiverMustMimic(decimal)
                    .withRequiredPositional("other")
                    .getArguments();

                @Override
                public TypeCheckingArgumentsDefinition getArguments() {
                    return ARGUMENTS;
                }

                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    Object arg = args.get(0);
                    if(IokeObject.data(arg) instanceof Number) {
                        return (Decimal.value(on).compareTo(Number.value(arg).asBigDecimal()) == 0) ? context.runtime._true : context.runtime._false;
                    } else if(IokeObject.data(arg) instanceof Decimal) {
                        return (Decimal.value(on).compareTo(Decimal.value(arg)) == 0) ? context.runtime._true : context.runtime._false;
                    } else {
                        return context.runtime._false;
                    }
                }
            }));

        decimal.registerMethod(runtime.newNativeMethod("compares this number against the argument, returning -1, 0 or 1 based on which one is larger. if the argument is a rational, it will be converted into a form suitable for comparing against a decimal, and then compared. if the argument is neither a Rational nor a Decimal, it tries to call asDecimal, and if that doesn't work it returns nil.", new TypeCheckingNativeMethod("<=>") {
                private final TypeCheckingArgumentsDefinition ARGUMENTS = TypeCheckingArgumentsDefinition
                    .builder()
                    .receiverMustMimic(decimal)
                    .withRequiredPositional("other")
                    .getArguments();

                @Override
                public TypeCheckingArgumentsDefinition getArguments() {
                    return ARGUMENTS;
                }

                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    Object arg = args.get(0);

                    IokeData data = IokeObject.data(arg);

                    if(data instanceof Number) {
                        return context.runtime.newNumber(Decimal.value(on).compareTo(Number.value(arg).asBigDecimal()));
                    } else {
                        if(!(data instanceof Decimal)) {
                            arg = IokeObject.convertToDecimal(arg, message, context, false);
                            if(!(IokeObject.data(arg) instanceof Decimal)) {
                                // Can't compare, so bail out
                                return context.runtime.nil;
                            }
                        }

                        if(on == context.runtime.decimal || arg == context.runtime.decimal) {
                            if(arg == on) {
                                return context.runtime.newNumber(0);
                            }
                            return context.runtime.nil;
                        }

                        return context.runtime.newNumber(Decimal.value(on).compareTo(Decimal.value(arg)));
                    }
                }
            }));

        decimal.registerMethod(runtime.newNativeMethod("returns the difference between this number and the argument. if the argument is a rational, it will be converted into a form suitable for subtracting against a decimal, and then subtracted. if the argument is neither a Rational nor a Decimal, it tries to call asDecimal, and if that fails it signals a condition.", new TypeCheckingNativeMethod("-") {
                private final TypeCheckingArgumentsDefinition ARGUMENTS = TypeCheckingArgumentsDefinition
                    .builder()
                    .receiverMustMimic(decimal)
                    .withRequiredPositional("subtrahend")
                    .getArguments();

                @Override
                public TypeCheckingArgumentsDefinition getArguments() {
                    return ARGUMENTS;
                }

                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    Object arg = args.get(0);

                    IokeData data = IokeObject.data(arg);

                    if(data instanceof Number) {
                        return context.runtime.newDecimal(Decimal.value(on).subtract(Number.value(arg).asBigDecimal()));
                    } else {
                        if(!(data instanceof Decimal)) {
                            arg = IokeObject.convertToDecimal(arg, message, context, true);
                        }

                        return context.runtime.newDecimal(Decimal.value(on).subtract(Decimal.value(arg)));
                    }
                }
            }));

        decimal.registerMethod(runtime.newNativeMethod("returns the sum of this number and the argument. if the argument is a rational, it will be converted into a form suitable for addition against a decimal, and then added. if the argument is neither a Rational nor a Decimal, it tries to call asDecimal, and if that fails it signals a condition.", new TypeCheckingNativeMethod("+") {
                private final TypeCheckingArgumentsDefinition ARGUMENTS = TypeCheckingArgumentsDefinition
                    .builder()
                    .receiverMustMimic(decimal)
                    .withRequiredPositional("addend")
                    .getArguments();

                @Override
                public TypeCheckingArgumentsDefinition getArguments() {
                    return ARGUMENTS;
                }

                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    Object arg = args.get(0);

                    IokeData data = IokeObject.data(arg);

                    if(data instanceof Number) {
                        return context.runtime.newDecimal(Decimal.value(on).add(Number.value(arg).asBigDecimal()));
                    } else {
                        if(!(data instanceof Decimal)) {
                            arg = IokeObject.convertToDecimal(arg, message, context, true);
                        }

                        return context.runtime.newDecimal(Decimal.value(on).add(Decimal.value(arg)));
                    }
                }
            }));

        decimal.registerMethod(runtime.newNativeMethod("returns the product of this number and the argument. if the argument is a rational, the receiver will be converted into a form suitable for multiplying against a decimal, and then multiplied. if the argument is neither a Rational nor a Decimal, it tries to call asDecimal, and if that fails it signals a condition.", new TypeCheckingNativeMethod("*") {
                private final TypeCheckingArgumentsDefinition ARGUMENTS = TypeCheckingArgumentsDefinition
                    .builder()
                    .receiverMustMimic(decimal)
                    .withRequiredPositional("multiplier")
                    .getArguments();

                @Override
                public TypeCheckingArgumentsDefinition getArguments() {
                    return ARGUMENTS;
                }

                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    Object arg = args.get(0);

                    IokeData data = IokeObject.data(arg);

                    if(data instanceof Number) {
                        return context.runtime.newDecimal(Decimal.value(on).multiply(Number.value(arg).asBigDecimal()));
                    } else {
                        if(!(data instanceof Decimal)) {
                            arg = IokeObject.convertToDecimal(arg, message, context, true);
                        }

                        return context.runtime.newDecimal(Decimal.value(on).multiply(Decimal.value(arg)));
                    }
                }
            }));

        decimal.registerMethod(runtime.newNativeMethod("returns this number to the power of the argument (which has to be an integer)", new TypeCheckingNativeMethod("**") {
                private final TypeCheckingArgumentsDefinition ARGUMENTS = TypeCheckingArgumentsDefinition
                    .builder()
                    .receiverMustMimic(decimal)
                    .withRequiredPositional("exponent")
                    .getArguments();

                @Override
                public TypeCheckingArgumentsDefinition getArguments() {
                    return ARGUMENTS;
                }

                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, IokeObject context, IokeObject message) throws ControlFlow {
                    Object arg = args.get(0);

                    IokeData data = IokeObject.data(arg);

                    if(!(data instanceof Number)) {
                        arg = IokeObject.convertToRational(arg, message, context, true);
                    }

                    return context.runtime.newDecimal(Decimal.value(on).pow(Number.intValue(arg).intValue()));
                }
            }));

        decimal.registerMethod(runtime.newNativeMethod("returns the quotient of this number and the argument.", new TypeCheckingNativeMethod("/") {
                private final TypeCheckingArgumentsDefinition ARGUMENTS = TypeCheckingArgumentsDefinition
                    .builder()
                    .receiverMustMimic(decimal)
                    .withRequiredPositional("divisor")
                    .getArguments();

                @Override
                public TypeCheckingArgumentsDefinition getArguments() {
                    return ARGUMENTS;
                }

                @Override
                public Object activate(IokeObject method, Object on, List<Object> args, Map<String, Object> keywords, final IokeObject context, IokeObject message) throws ControlFlow {
                    Object arg = args.get(0);

                    IokeData data = IokeObject.data(arg);

                    if(data instanceof Number) {
                        return context.runtime.newDecimal(Decimal.value(on).divide(Number.value(arg).asBigDecimal()).stripTrailingZeros());
                    } else {
                        if(!(data instanceof Decimal)) {
                            arg = IokeObject.convertToDecimal(arg, message, context, true);
                        }

                        while(Decimal.value(arg).compareTo(BigDecimal.ZERO) == 0) {
                            final IokeObject condition = IokeObject.as(IokeObject.getCellChain(context.runtime.condition,
                                                                                               message,
                                                                                               context,
                                                                                               "Error",
                                                                                               "Arithmetic",
                                                                                               "DivisionByZero"), context).mimic(message, context);
                            condition.setCell("message", message);
                            condition.setCell("context", context);
                            condition.setCell("receiver", on);

                            final Object[] newCell = new Object[]{arg};

                            context.runtime.withRestartReturningArguments(new RunnableWithControlFlow() {
                                    public void run() throws ControlFlow {
                                        context.runtime.errorCondition(condition);
                                    }},
                                context,
                                new Restart.ArgumentGivingRestart("useValue") {
                                    public List<String> getArgumentNames() {
                                        return new ArrayList<String>(Arrays.asList("newValue"));
                                    }

                                    public IokeObject invoke(IokeObject c2, List<Object> arguments) throws ControlFlow {
                                        newCell[0] = arguments.get(0);
                                        return c2.runtime.nil;
                                    }
                                }
                                );

                            arg = newCell[0];
                        }

                        BigDecimal result = null;
                        try {
                            result = Decimal.value(on).divide(Decimal.value(arg));
                        } catch(ArithmeticException e) {
                            result = Decimal.value(on).divide(Decimal.value(arg), java.math.MathContext.DECIMAL128);
                        }
                        return context.runtime.newDecimal(result.stripTrailingZeros());
                    }
                }
            }));
    }
}// Decimal
TOP

Related Classes of ioke.lang.Decimal

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.