/*
    Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
    и других спецификаций для функционирования компактных приложений на языке
    Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

    Copyright © 2016–2017, 2019–2022 Малик Разработчик

    Это свободная программа: вы можете перераспространять ее и/или изменять
    ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она будет полезной,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <https://www.gnu.org/licenses/>.
*/

package malik.emulator.util;

public class RealValueRepresenter extends Object
{
    public static final int MIN_ORDER_DIGITS = 2;
    public static final int MAX_ORDER_DIGITS = 4;
    public static final int FLOAT_ORDER_DIGITS = 2;
    public static final int DOUBLE_ORDER_DIGITS = 3;
    public static final int MIN_SIGNIFICAND_DIGITS = 6;
    public static final int MAX_SIGNIFICAND_DIGITS = 18;
    public static final int FLOAT_SIGNIFICAND_DIGITS = 7;
    public static final int DOUBLE_SIGNIFICAND_DIGITS = 15;

    public static double pow10(double value, int power) {
        if(power == Integer.MIN_VALUE) return value * 0.d;
        if(power > 0)
        {
            return value / tab_04_00(power) / tab_08_05(power) / (power >= 0x0200 ? 0.d : 1.d);
        }
        if((power = -power) > 0)
        {
            return value * tab_04_00(power) * tab_08_05(power) * (power >= 0x0200 ? 0.d : 1.d);
        }
        return value;
    }

    private static double tab_04_00(int pow) {
        switch(pow & 0x1f)
        {
        default:
            return 0.d;
        case 0:
            return 1.d;
        case 1:
            return 1.e-001d;
        case 2:
            return 1.e-002d;
        case 3:
            return 1.e-003d;
        case 4:
            return 1.e-004d;
        case 5:
            return 1.e-005d;
        case 6:
            return 1.e-006d;
        case 7:
            return 1.e-007d;
        case 8:
            return 1.e-008d;
        case 9:
            return 1.e-009d;
        case 10:
            return 1.e-010d;
        case 11:
            return 1.e-011d;
        case 12:
            return 1.e-012d;
        case 13:
            return 1.e-013d;
        case 14:
            return 1.e-014d;
        case 15:
            return 1.e-015d;
        case 16:
            return 1.e-016d;
        case 17:
            return 1.e-017d;
        case 18:
            return 1.e-018d;
        case 19:
            return 1.e-019d;
        case 20:
            return 1.e-020d;
        case 21:
            return 1.e-021d;
        case 22:
            return 1.e-022d;
        case 23:
            return 1.e-023d;
        case 24:
            return 1.e-024d;
        case 25:
            return 1.e-025d;
        case 26:
            return 1.e-026d;
        case 27:
            return 1.e-027d;
        case 28:
            return 1.e-028d;
        case 29:
            return 1.e-029d;
        case 30:
            return 1.e-030d;
        case 31:
            return 1.e-031d;
        }
    }

    private static double tab_08_05(int pow) {
        switch((pow >> 5) & 0x0f)
        {
        default:
            return 0.d;
        case 0:
            return 1.d;
        case 1:
            return 1.e-032d;
        case 2:
            return 1.e-064d;
        case 3:
            return 1.e-096d;
        case 4:
            return 1.e-128d;
        case 5:
            return 1.e-160d;
        case 6:
            return 1.e-192d;
        case 7:
            return 1.e-224d;
        case 8:
            return 1.e-256d;
        case 9:
            return 1.e-288d;
        case 10:
            return 1.e-320d;
        case 11:
        case 12:
        case 13:
        case 14:
        case 15:
            return 0.d;
        }
    }

    protected final double limitValueWithFractialPart;
    protected final double limitValueWithoutExponent;
    protected final int orderDigits;
    protected final int significandDigits;
    protected final long minRepresentValue;
    protected final long maxRepresentValue;

    public RealValueRepresenter(int significandDigits, int orderDigits) {
        long maxRepresentValue = 1L;
        if(orderDigits < MIN_ORDER_DIGITS) orderDigits = MIN_ORDER_DIGITS;
        if(orderDigits > MAX_ORDER_DIGITS) orderDigits = MAX_ORDER_DIGITS;
        if(significandDigits < MIN_SIGNIFICAND_DIGITS) significandDigits = MIN_SIGNIFICAND_DIGITS;
        if(significandDigits > MAX_SIGNIFICAND_DIGITS) significandDigits = MAX_SIGNIFICAND_DIGITS;
        for(int i = significandDigits; i-- > 0; maxRepresentValue *= 10L);
        this.limitValueWithFractialPart = pow10(1.d, significandDigits - 1);
        this.limitValueWithoutExponent = pow10(1.d, significandDigits);
        this.orderDigits = orderDigits;
        this.significandDigits = significandDigits;
        this.minRepresentValue = maxRepresentValue / 10L;
        this.maxRepresentValue = maxRepresentValue - 1L;
    }

    public boolean equals(Object anot) {
        RealValueRepresenter r;
        return anot == this || anot instanceof RealValueRepresenter && orderDigits == (r = (RealValueRepresenter) anot).orderDigits && significandDigits == r.significandDigits;
    }

    public int hashCode() {
        return orderDigits << 8 | significandDigits;
    }

    public float parseFloat(String string) throws NumberFormatException {
        double result;
        if("+Infinity".equals(string) || "Infinity".equals(string)) return Float.POSITIVE_INFINITY;
        if("-Infinity".equals(string)) return Float.NEGATIVE_INFINITY;
        if("NaN".equals(string)) return Float.NaN;
        if((result = parse(string)) < (double) -Float.MAX_VALUE || result > (double) Float.MAX_VALUE)
        {
            throw new NumberFormatException("RealValueRepresenter.parse: недопустимый формат числа.");
        }
        return (float) result;
    }

    public double parseDouble(String string) throws NumberFormatException {
        if("+Infinity".equals(string) || "Infinity".equals(string)) return Double.POSITIVE_INFINITY;
        if("-Infinity".equals(string)) return Double.NEGATIVE_INFINITY;
        if("NaN".equals(string)) return Double.NaN;
        return parse(string);
    }

    public int getOrderDigits() {
        return orderDigits;
    }

    public int getSignificandDigits() {
        return significandDigits;
    }

    public String toString(double value) {
        boolean expform;
        char c;
        int i;
        int j;
        int len;
        int order;
        int dotpos;
        int sigdig;
        long intval;
        long tmp;
        char[] buf;
        if(value != value) return "NaN";
        if(value == Double.POSITIVE_INFINITY) return "Infinity";
        if(value == Double.NEGATIVE_INFINITY) return "-Infinity";
        len = 0;
        Array.fill(buf = new char[i = 32], 0, i, '0');
        if((intval = Double.doubleToLongBits(value)) < 0L)
        {
            buf[len++] = '-';
            value = Double.longBitsToDouble(intval &= 0x7fffffffffffffffL);
        }
        if(intval != 0L)
        {
            order = (int) (3.01029995663981195e-001d * Math.log2(value));
            sigdig = significandDigits;
            /* if(expform = value < 1.e-006d || value >= limitValueWithoutExponent) */
            if(expform = value < 1.e-003d || value >= 1.e+007d)
            {
                if(order < 0) order--;
                if((intval = Math.round(pow10(value, sigdig - order - 1))) < minRepresentValue)
                {
                    intval *= 10L;
                    order--;
                }
                if(intval > maxRepresentValue)
                {
                    intval /= 10L;
                    order++;
                }
                dotpos = len + 1;
            }
            else if(value < 1.e+000d)
            {
                intval = Math.round(pow10(value, sigdig - 1));
                dotpos = len + 1;
            }
            else if(value < limitValueWithFractialPart)
            {
                if((intval = Math.round(pow10(value, sigdig - order - 1))) < minRepresentValue)
                {
                    intval *= 10L;
                    order--;
                }
                if(intval > maxRepresentValue)
                {
                    intval /= 10L;
                    order++;
                }
                dotpos = len + order + 1;
            }
            else
            {
                if((intval = Math.round(value)) > (tmp = maxRepresentValue)) intval = tmp;
                dotpos = len + sigdig;
            }
            for(buf[dotpos] = '.', i = len + sigdig; i-- > len; )
            {
                buf[i >= dotpos ? i + 1 : i] = (char) ((int) (intval % 10L) + '0');
                intval /= 10L;
            }
            for(len += sigdig + 2; (c = buf[len - 1]) == '0' || c == '.'; )
            {
                len--;
                if(c == '.')
                {
                    len += 2;
                    break;
                }
            }
            if(expform)
            {
                buf[len++] = 'E';
                if(order < 0)
                {
                    buf[len++] = '-';
                    order = -order;
                } else
                {
                    buf[len++] = '+';
                }
                for(i = j = len + orderDigits; i-- > len; )
                {
                    buf[i] = (char) ((order % 10) + '0');
                    order /= 10;
                }
                len = j;
            }
        } else
        {
            len += 3;
            buf[len - 2] = '.';
        }
        return new String(buf, 0, len);
    }

    protected double parse(String string) throws NumberFormatException {
        boolean negative;
        char c;
        double result;
        int order;
        int frac;
        int len;
        int i;
        if(string == null || (len = string.length()) <= 0)
        {
            throw new NumberFormatException("RealValueRepresenter.parse: недопустимый формат числа.");
        }
        negative = false;
        result = 0.d;
        order = 0;
        frac = 0;
        switch(string.charAt(i = 0))
        {
        default:
            break;
        case '-':
            negative = true;
            /* fall through */
        case '+':
        case ' ':
            i++;
            break;
        }
        label0:
        {
            if(i < len && ((c = string.charAt(i)) >= '0' && c <= '9' || c == '.'))
            {
                if(c == '.') break label0;
                result = (double) (c - '0');
            } else
            {
                throw new NumberFormatException("RealValueRepresenter.parse: недопустимый формат числа.");
            }
            for(i++; i < len && (c = string.charAt(i)) >= '0' && c <= '9'; i++) result = (10.d * result) + (double) (c - '0');
        }
        if(i < len && string.charAt(i) == '.')
        {
            for(i++; i < len && (c = string.charAt(i)) >= '0' && c <= '9'; frac++, i++) result = (10.d * result) + (double) (c - '0');
        }
        if(negative)
        {
            result = -result;
            negative = false;
        }
        if(i < len && ((c = string.charAt(i)) == 'E' || c == 'e'))
        {
            if(++i == len)
            {
                throw new NumberFormatException("RealValueRepresenter.parse: недопустимый формат числа.");
            }
            switch(string.charAt(i))
            {
            default:
                break;
            case '-':
                negative = true;
                /* fall through */
            case '+':
                i++;
                break;
            }
            if(i == len)
            {
                throw new NumberFormatException("RealValueRepresenter.parse: недопустимый формат числа.");
            }
            for(; i < len && (c = string.charAt(i)) >= '0' && c <= '9'; i++) if((order = (10 * order) + (int) (c - '0')) > 9999)
            {
                throw new NumberFormatException("RealValueRepresenter.parse: недопустимый формат числа.");
            }
            if(negative) order = -order;
        }
        if(i < len || Math.abs(result = pow10(result, order - frac)) > Double.MAX_VALUE)
        {
            throw new NumberFormatException("RealValueRepresenter.parse: недопустимый формат числа.");
        }
        return result;
    }
}
