All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.progrm_jarvis.javacommons.primitive.NumberUtil Maven / Gradle / Ivy

package ru.progrm_jarvis.javacommons.primitive;

import lombok.NonNull;
import lombok.experimental.UtilityClass;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import ru.progrm_jarvis.javacommons.object.Result;
import ru.progrm_jarvis.javacommons.primitive.error.IntegerParseError;

import java.util.OptionalInt;
import java.util.OptionalLong;

/**
 * Utilities related to numbers.
 */
@UtilityClass
public class NumberUtil {

    /**
     * Default radix of a number.
     */
    public static final int DEFAULT_RADIX = 10;

    /**
     * 

Parses an {@code int} number.

*

This method is based on OpenJDK {@link Integer#parseInt(String, int)} * but it returns {@link OptionalInt} to handle invalid input instead of throwing {@link NumberFormatException}. * * @param possibleInteger string which is expected to contain an {@code int} number but may not * @param radix radix of the possible number * @return optional containing the parsed number or an empty one if it could not be parsed * * @throws IllegalArgumentException if the radix * is out of bound [{@value Character#MIN_RADIX}; {@value Character#MAX_RADIX}]. */ public @NotNull OptionalInt parseInt(final @NonNull CharSequence possibleInteger, final int radix) { // check radix bounds if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) throw new IllegalArgumentException( "Radix " + radix + " is out of bounds [" + Character.MIN_RADIX + "; " + Character.MAX_RADIX + "]" ); // check string length final int length; if ((length = possibleInteger.length()) == 0) return OptionalInt.empty(); final boolean negative; final int limit; int currentIndex = 0, digit; { // check sign by first char (may be implicit '+') val firstChar = possibleInteger.charAt(0); if (firstChar < '0') { // first char is definitely not a digit switch (firstChar) { case '-': { negative = true; limit = Integer.MIN_VALUE; break; } case '+': { negative = false; limit = -Integer.MAX_VALUE; break; } default: return OptionalInt.empty(); } /* sign found */ // it is not a number if there is only a sign without digits if (length == 1) return OptionalInt.empty(); currentIndex = 1; digit = Character.digit(possibleInteger.charAt(1), radix); } else { // first char is a digit negative = false; limit = -Integer.MAX_VALUE; digit = Character.digit(firstChar, radix); } } final int bound = limit / radix, lastIndex = length - 1; int negativeResult = 0; while (true) { // check if it really is a digit if (digit < 0) return OptionalInt.empty(); // check if corresponds to bound if (negativeResult < bound) return OptionalInt.empty(); // move the pre-number to the left leaving a last zero for a new sign negativeResult *= radix; // check if the number is OK with the limit if (negativeResult < limit + digit) return OptionalInt.empty(); // update the result negativeResult -= digit; // check it was the last character if (currentIndex == lastIndex) break; // if it was not the last character then read the next one digit = Character.digit(possibleInteger.charAt(++currentIndex), radix); } return OptionalInt.of(negative ? negativeResult : -negativeResult); } /** *

Parses a decimal {@code int} number.

*

This method is based on OpenJDK {@link Integer#parseInt(String, int)} * but it returns {@link OptionalInt} to handle invalid input instead of throwing {@link NumberFormatException}. * * @param possibleInteger string which is expected to contain an {@code int} number but may not * @return optional containing the parsed number or an empty one if it could not be parsed * * @throws IllegalArgumentException if the radix * is out of bound [{@value Character#MIN_RADIX}; {@value Character#MAX_RADIX}]. */ public @NotNull OptionalInt parseInt(final @NonNull CharSequence possibleInteger) { return parseInt(possibleInteger, 10); } /** *

Parses an {@code int} number.

*

This method is based on OpenJDK {@link Integer#parseInt(String, int)} * but it returns {@link Result} to handle invalid input instead of throwing {@link NumberFormatException}. * * @param possibleInteger string which is expected to contain an {@code int} number but may not * @param radix radix of the possible number * @return result containing the parsed number or an error one if it could not be parsed * * @throws IllegalArgumentException if the radix * is out of bound [{@value Character#MIN_RADIX}; {@value Character#MAX_RADIX}]. */ public @NotNull Result<@NotNull Integer, @NotNull IntegerParseError> parseIntResult( final @NonNull CharSequence possibleInteger, final int radix ) { // check radix bounds if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) throw new IllegalArgumentException( "Radix " + radix + " is out of bounds [" + Character.MIN_RADIX + "; " + Character.MAX_RADIX + "]" ); final int length; // check string length if ((length = possibleInteger.length()) == 0) return Result.error(IntegerParseError.EMPTY); final boolean negative; final int limit; int currentIndex = 0, digit; { // check sign by first char (may be implicit '+') val firstChar = possibleInteger.charAt(0); if (firstChar < '0') { // first char is definitely not a digit switch (firstChar) { case '-': { negative = true; limit = Integer.MIN_VALUE; break; } case '+': { negative = false; limit = -Integer.MAX_VALUE; break; } default: return Result.error(IntegerParseError.INVALID_CHARACTER); } /* sign found */ // it is not a number if there is only a sign without digits if (length == 1) return Result.error(IntegerParseError.EMPTY); currentIndex = 1; digit = Character.digit(possibleInteger.charAt(1), radix); } else { // first char is a digit negative = false; limit = -Integer.MAX_VALUE; digit = Character.digit(firstChar, radix); } } final int bound = limit / radix, lastIndex = length - 1; int negativeResult = 0; while (true) { // check if it really is a digit if (digit < 0) return Result.error(IntegerParseError.INVALID_CHARACTER); // check if corresponds to bound if (negativeResult < bound) return Result.error(IntegerParseError.OUT_OF_BOUNDS); // move the pre-number to the left leaving a last zero for a new sign negativeResult *= radix; // check if the number is OK with the limit if (negativeResult < limit + digit) Result.error(IntegerParseError.INVALID_CHARACTER); // update the result negativeResult -= digit; // check it was the last character if (currentIndex == lastIndex) break; // if it was not the last character then read the next one digit = Character.digit(possibleInteger.charAt(++currentIndex), radix); } return Result.success(negative ? negativeResult : -negativeResult); } /** *

Parses a decimal {@code int} number.

*

This method is based on OpenJDK {@link Integer#parseInt(String, int)} * but it returns {@link Result} to handle invalid input instead of throwing {@link NumberFormatException}. * * @param possibleInteger string which is expected to contain an {@code int} number but may not * @return result containing the parsed number or an error one if it could not be parsed * * @throws IllegalArgumentException if the radix * is out of bound [{@value Character#MIN_RADIX}; {@value Character#MAX_RADIX}]. */ public @NotNull Result<@NotNull Integer, @NotNull IntegerParseError> parseIntResult( final @NonNull CharSequence possibleInteger) { return parseIntResult(possibleInteger, DEFAULT_RADIX); } /** *

Parses a {@code long} number.

*

This method is based on OpenJDK {@link Long#parseLong(String, int)} * but it returns {@link OptionalLong} to handle invalid input instead of throwing {@link NumberFormatException}. * * @param possibleLong string which is expected to contain a {@code long} number but may not * @param radix radix of the possible number * @return optional containing the parsed number or an empty one if it could not be parsed * * @throws IllegalArgumentException if the radix * is out of bound [{@value Character#MIN_RADIX}; {@value Character#MAX_RADIX}]. */ public @NotNull OptionalLong parseLong(final @NonNull CharSequence possibleLong, final int radix) { // check radix bounds if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) throw new IllegalArgumentException( "Radix " + radix + " is out of bounds [" + Character.MIN_RADIX + "; " + Character.MAX_RADIX + "]" ); // check string length final int length; if ((length = possibleLong.length()) == 0) return OptionalLong.empty(); final boolean negative; final long limit; int currentIndex = 0, digit; { // check sign by first char (may be implicit '+') val firstChar = possibleLong.charAt(0); if (firstChar < '0') { // first char is definitely not a digit switch (firstChar) { case '-': { negative = true; limit = Long.MIN_VALUE; break; } case '+': { negative = false; limit = -Long.MAX_VALUE; break; } default: return OptionalLong.empty(); } /* sign found */ // it is not a number if there is only a sign without digits if (length == 1) return OptionalLong.empty(); currentIndex = 1; digit = Character.digit(possibleLong.charAt(1), radix); } else { // first char is a digit negative = false; limit = -Long.MAX_VALUE; digit = Character.digit(firstChar, radix); } } val bound = limit / radix; val lastIndex = length - 1; long negativeResult = 0; while (true) { // check if it really is a digit if (digit < 0) return OptionalLong.empty(); // check if corresponds to bound if (negativeResult < bound) return OptionalLong.empty(); // move the pre-number to the left leaving a last zero for a new sign negativeResult *= radix; // check if the number is OK with the limit if (negativeResult < limit + digit) return OptionalLong.empty(); // update the result negativeResult -= digit; // check it was the last character if (currentIndex == lastIndex) break; // if it was not the last character then read the next one digit = Character.digit(possibleLong.charAt(++currentIndex), radix); } return OptionalLong.of(negative ? negativeResult : -negativeResult); } /** *

Parses a decimal {@code long} number.

*

This method is based on OpenJDK {@link Long#parseLong(String, int)} * but it returns {@link OptionalLong} to handle invalid input instead of throwing {@link NumberFormatException}. * * @param possibleLong string which is expected to contain a {@code long} number but may not * @return optional containing the parsed number or an empty one if it could not be parsed * * @throws IllegalArgumentException if the radix * is out of bound [{@value Character#MIN_RADIX}; {@value Character#MAX_RADIX}]. */ public @NotNull OptionalLong parseLong(final @NonNull CharSequence possibleLong) { return parseLong(possibleLong, DEFAULT_RADIX); } /** *

Parses a {@code long} number.

*

This method is based on OpenJDK {@link Long#parseLong(String, int)} * but it returns {@link Result} to handle invalid input instead of throwing {@link NumberFormatException}. * * @param possibleLong string which is expected to contain a {@code long} number but may not * @param radix radix of the possible number * @return result containing the parsed number or an error one if it could not be parsed * * @throws IllegalArgumentException if the radix * is out of bound [{@value Character#MIN_RADIX}; {@value Character#MAX_RADIX}]. */ public @NotNull Result<@NotNull Long, @NotNull IntegerParseError> parseLongResult( final @NonNull CharSequence possibleLong, final int radix ) { // check radix bounds if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) throw new IllegalArgumentException( "Radix " + radix + " is out of bounds [" + Character.MIN_RADIX + "; " + Character.MAX_RADIX + "]" ); // check string length final int length; if ((length = possibleLong.length()) == 0) return Result.error(IntegerParseError.EMPTY); final boolean negative; final long limit; int currentIndex = 0, digit; { // check sign by first char (may be implicit '+') val firstChar = possibleLong.charAt(0); if (firstChar < '0') { // first char is definitely not a digit switch (firstChar) { case '-': { negative = true; limit = Long.MIN_VALUE; break; } case '+': { negative = false; limit = -Long.MAX_VALUE; break; } default: return Result.error(IntegerParseError.INVALID_CHARACTER); } /* sign found */ // it is not a number if there is only a sign without digits if (length == 1) return Result.error(IntegerParseError.EMPTY); currentIndex = 1; digit = Character.digit(possibleLong.charAt(1), radix); } else { // first char is a digit negative = false; limit = -Long.MAX_VALUE; digit = Character.digit(firstChar, radix); } } val bound = limit / radix; val lastIndex = length - 1; long negativeResult = 0; while (true) { // check if it really is a digit if (digit < 0) return Result.error(IntegerParseError.INVALID_CHARACTER); // check if corresponds to bound if (negativeResult < bound) return Result.error(IntegerParseError.OUT_OF_BOUNDS); // move the pre-number to the left leaving a last zero for a new sign negativeResult *= radix; // check if the number is OK with the limit if (negativeResult < limit + digit) return Result.error(IntegerParseError.OUT_OF_BOUNDS); // update the result negativeResult -= digit; // check it was the last character if (currentIndex == lastIndex) break; // if it was not the last character then read the next one digit = Character.digit(possibleLong.charAt(++currentIndex), radix); } return Result.success(negative ? negativeResult : -negativeResult); } /** *

Parses a decimal {@code long} number.

*

This method is based on OpenJDK {@link Long#parseLong(String, int)} * but it returns {@link Result} to handle invalid input instead of throwing {@link NumberFormatException}. * * @param possibleLong string which is expected to contain a {@code long} number but may not * @return result containing the parsed number or an error one if it could not be parsed * * @throws IllegalArgumentException if the radix * is out of bound [{@value Character#MIN_RADIX}; {@value Character#MAX_RADIX}]. */ public @NotNull Result<@NotNull Long, @NotNull IntegerParseError> parseLongResult( final @NonNull CharSequence possibleLong ) { return parseLongResult(possibleLong, DEFAULT_RADIX); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy