package com.kickstarter.libs.utils; import android.support.annotation.NonNull; import com.kickstarter.libs.NumberOptions; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.Locale; public final class NumberUtils { private NumberUtils() {} public static @NonNull String flooredPercentage(final float value) { return flooredPercentage(value, Locale.getDefault()); } public static @NonNull String flooredPercentage(final float value, final @NonNull Locale locale) { final NumberFormat numberFormat = NumberFormat.getPercentInstance(locale); numberFormat.setRoundingMode(RoundingMode.DOWN); return numberFormat.format(value / 100); } /** * Returns a formatted number for the user's locale. */ public static @NonNull String format(final int value) { return format(value, Locale.getDefault()); } /** * Returns a formatted number for the specified locale. */ public static @NonNull String format(final int value, final @NonNull Locale locale) { return NumberFormat.getIntegerInstance(locale).format(value); } /** * Returns a formatted number for the user's locale. Defaults to 0 precision with no bucketing. */ public static @NonNull String format(final float value) { return format(value, NumberOptions.builder().build()); } /** * Returns a formatted number for the user's locale. {@link NumberOptions} can control whether the number is * used as a currency, if it is bucketed, and the precision. */ public static @NonNull String format(final float value, final @NonNull NumberOptions options) { return format(value, options, Locale.getDefault()); } /** * Returns a formatted number for a given locale. {@link NumberOptions} can control whether the number is * used as a currency, if it is bucketed, and the precision. */ public static @NonNull String format(final float value, final @NonNull NumberOptions options, final @NonNull Locale locale) { final NumberFormat numberFormat = numberFormat(options, locale); if (numberFormat instanceof DecimalFormat) { numberFormat.setRoundingMode(ObjectUtils.coalesce(options.roundingMode(), RoundingMode.HALF_DOWN)); } int precision = ObjectUtils.coalesce(options.precision(), 0); float divisor = 1.0f; String suffix = ""; // TODO: The bucketing logic works, but the suffix should be translated. final float bucketAbove = ObjectUtils.coalesce(options.bucketAbove(), 0.0f); if (bucketAbove >= 1000.0f && value >= bucketAbove) { if (bucketAbove > 0.0f && bucketAbove < 1_000_000.0f) { divisor = 1000.0f; suffix = "K"; } else if (bucketAbove >= 1_000_000.0f) { divisor = 1_000_000.0f; suffix = "M"; } if (options.bucketAbove() != null) { precision = ObjectUtils.coalesce(options.bucketPrecision(), 0); } } if (options.currencyCode() != null) { suffix = String.format("%s %s", suffix, options.currencyCode()); } numberFormat.setMinimumFractionDigits(precision); numberFormat.setMaximumFractionDigits(precision); float bucketedValue = value; if (value >= bucketAbove) { bucketedValue = value / divisor; } return String.format("%s%s", numberFormat.format(bucketedValue), suffix).trim(); } /** * Return a formatter that can output an appropriate number based on the input currency and locale. */ private static @NonNull NumberFormat numberFormat(final @NonNull NumberOptions options, final @NonNull Locale locale) { final NumberFormat numberFormat; if (options.isCurrency()) { final DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale); final DecimalFormatSymbols symbols = decimalFormat.getDecimalFormatSymbols(); symbols.setCurrencySymbol(options.currencySymbol()); decimalFormat.setDecimalFormatSymbols(symbols); numberFormat = decimalFormat; } else { numberFormat = NumberFormat.getInstance(locale); } return numberFormat; } }