001// --------------------------------------------------------------------------------
002// Copyright 2002-2026 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.util.common.string;
018
019public class DecimalUtils {
020
021    private DecimalUtils() {
022        super();
023    }
024
025    private static class DecimalUtilsHolder {
026        static DecimalUtils instance = new DecimalUtils();
027    }
028
029    public static DecimalUtils getInstance() {
030        return DecimalUtilsHolder.instance;
031    }
032
033    /**
034     * Converts a decimal value to a scaled integer string, preserving MAX_VALUE and MIN_VALUE markers.
035     *
036     * @param minusSign Character to use when looking for a minus sign in the value, may be null if amount is positive-only
037     * @param fractionSeparator Character that separates the whole part of the value from the decimal portion
038     * @param fractionDigits Number of characters to look for in the decimal portion of the value
039     * @param value Value being parsed
040     */
041    public String parse(String minusSign, String fractionSeparator, Integer fractionDigits, String value) {
042        String result;
043        
044        if(value.equalsIgnoreCase("MAX_VALUE") || value.equalsIgnoreCase("MIN_VALUE")) {
045            result = value;
046        } else {
047            var rawValue = value.toCharArray();
048            var size = rawValue.length;
049            var cleanWhole = new StringBuilder(size);
050            var checkMinusSign = minusSign != null;
051            var charMinusSign = checkMinusSign? minusSign.charAt(0): 0;
052            var minusSignFound = false;
053            var charFractionSeparator = fractionSeparator == null? '\u0000': fractionSeparator.charAt(0);
054            var intFractionDigits = fractionDigits == null? 0: fractionDigits;
055            var cleanFraction = new StringBuilder(intFractionDigits);
056            var separatorFound = false;
057            var parsedValue = new StringBuilder(size);
058
059            for(var i = 0; i < size; i++) {
060                var testChar = rawValue[i];
061
062                if(testChar >= '0' && testChar <= '9') {
063                    if(separatorFound) {
064                        cleanFraction.append(testChar);
065                        if(cleanFraction.length() == intFractionDigits)
066                            break;
067                    } else {
068                        cleanWhole.append(testChar);
069                    }
070                } else if(testChar == charFractionSeparator) {
071                    separatorFound = true;
072                } else if(checkMinusSign && testChar == charMinusSign) {
073                    minusSignFound = true;
074                }
075            }
076
077            for(var i = cleanFraction.length(); i < intFractionDigits; i++) {
078                cleanFraction.append('0');
079            }
080
081            if(minusSignFound) {
082                parsedValue.append('-');
083            }
084
085            result = parsedValue.append(cleanWhole).append(cleanFraction).toString();
086        }
087        
088        return result;
089    }
090    
091}