10

I get the following error from the GPS:

Fatal Exception: java.lang.NumberFormatException
Invalid double: "-٣٣٫٩٣٨٧٤"

Now, this is from an error that I got from a user via Fabric. It looks like Arabic so I'm guessing it only happens if you have the language set to that, or your sim card?

Is it possible to force the GPS to send characters in the 0-9 range? Or can I somehow fix this?

Mahozad
  • 18,032
  • 13
  • 118
  • 133
rosu alin
  • 5,674
  • 11
  • 69
  • 150
  • For difference between numerals see [this Wikipedia article](https://en.wikipedia.org/wiki/Eastern_Arabic_numerals#Numerals). – Mahozad Nov 04 '21 at 15:09
  • Upvote this YouTrack issue for Kotlin so you can simply call `String.toDouble()`: https://youtrack.jetbrains.com/issue/KT-58122/StringtoDouble-does-not-parse-Eastern-Arabic-and-Persian-Farsi-valid-numbers – Mahozad Apr 28 '23 at 06:29

9 Answers9

20

Try this:

String number = arabicToDecimal("۴۲"); // number = 42;

private static final String arabic = "\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9";
private static String arabicToDecimal(String number) {
    char[] chars = new char[number.length()];
    for(int i=0;i<number.length();i++) {
        char ch = number.charAt(i);
        if (ch >= 0x0660 && ch <= 0x0669)
           ch -= 0x0660 - '0';
        else if (ch >= 0x06f0 && ch <= 0x06F9)
           ch -= 0x06f0 - '0';
        chars[i] = ch;
    }
    return new String(chars);
}
Amy
  • 4,034
  • 1
  • 20
  • 34
5

Kotlin developers:

fun ArabicToEnglish(str: String):String {
        var result = ""
        var en = '0'
        for (ch in str) {
            en = ch
            when (ch) {
                '۰' -> en = '0'
                '۱' -> en = '1'
                '۲' -> en = '2'
                '۳' -> en = '3'
                '۴' -> en = '4'
                '۵' -> en = '5'
                '۶' -> en = '6'
                '۷' -> en = '7'
                '۸' -> en = '8'
                '۹' -> en = '9'
            }
            result = "${result}$en"
        }
        return result
    }
Parisa Baastani
  • 1,713
  • 14
  • 31
2

More generic solution using Character.getNumericValue(char)

static String replaceNonstandardDigits(String input) {
    if (input == null || input.isEmpty()) {
        return input;
    }

    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < input.length(); i++) {
        char ch = input.charAt(i);
        if (isNonstandardDigit(ch)) {
            int numericValue = Character.getNumericValue(ch);
            if (numericValue >= 0) {
                builder.append(numericValue);
            }
        } else {
            builder.append(ch);
        }
    }
    return builder.toString();
}

private static boolean isNonstandardDigit(char ch) {
    return Character.isDigit(ch) && !(ch >= '0' && ch <= '9');
}
Flovettee
  • 895
  • 1
  • 8
  • 9
2

A modified generic solution


    fun convertArabic(arabicStr: String): String? {
        val chArr = arabicStr.toCharArray()
        val sb = StringBuilder()
        for (ch in chArr) {
            if (Character.isDigit(ch)) {
                sb.append(Character.getNumericValue(ch))
            }else if (ch == '٫'){
                sb.append(".")
            }

            else {
                sb.append(ch)
            }
        }
        return sb.toString()
    }

The second branch is necessary as double numbers has this character as dot separator '٫'

Mahmoud Mabrok
  • 1,362
  • 16
  • 24
2

Simple answer

NumberFormat.getInstance(Locale.getDefault()).format(123)

If you want to set for a particular language

NumberFormat.getInstance(Locale.forLanguageTag("ar"))
NumberFormat.getInstance(Locale.ENGLISH)
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Tejas Soni
  • 404
  • 3
  • 4
  • You will get `java.lang.IllegalArgumentException: Cannot format given Object as a Number` Exception if you try to convert no English string. – philoopher97 Feb 15 '22 at 09:13
2

Only 6 lines in Kotlin

val ar = "-٣٣٫٩٣٨٧٤"

val en = ar.map {
    if (it.code in 1632..1641) (it.code - 1632 + 48).toChar()
    else it
}.joinToString("")

Here, 1632 & 1641 are the values of 0(zero) & 9(nine) respectively in Arabic.

philoopher97
  • 772
  • 1
  • 6
  • 18
  • This does not work if the number contains Persian/Urdu/Arabic decimal separator (`٫`). In fact, your own `ar` variable contains that character. – Mahozad Apr 19 '23 at 13:56
1

Here is another "manual" solution in Kotlin. It might not be the best solution performance-wise.

val number: Double = "۱٬۲۳۴٫۵۶۷۸".parseToDouble()
/**
 * Parses any valid number string to a [Double].
 * The number can be in Persian/Urdu, Eastern Arabic, or Westerns Arabic numerals.
 * The number can have thousands separators (Persian/Urdu/Eastern Arabic `٬` or English `,` or others).
 * The number can be a mix of the above; for example,
 * it can have Persian numerals, [thin space](https://en.wikipedia.org/wiki/Thin_space) ` ` as thousands separator, and point `.` as decimal separator.
 *
 * Also see [this Wikipedia article](https://en.wikipedia.org/wiki/Arabic_script_in_Unicode)
 */
fun String.parseToDouble() = this
        .normalizeDigits()
        .normalizeDecimalSeparator()
        .removeOptionalCharacters()
        .toDouble()

/**
 * Converts [Persian/Urdu and Eastern Arabic digits](https://en.wikipedia.org/wiki/Eastern_Arabic_numerals#Numerals) to Western Arabic digits
 */
fun String.normalizeDigits() = this
        // Replace Persian/Urdu numerals
        .replace(Regex("[۰-۹]")) { match -> (match.value.single() - '۰').toString() }
        // Replace Eastern Arabic numerals
        .replace(Regex("[٠-٩]")) { match -> (match.value.single() - '٠').toString() }

/**
 * Converts [Persian/Urdu/Eastern Arabic decimal separator](https://en.wikipedia.org/wiki/Decimal_separator#Other_numeral_systems) `٫` or slash `/` (invalid and non-standard) to `.` (decimal separator in English)
 */
fun String.normalizeDecimalSeparator() = this.replace('٫', '.').replace('/', '.')

/**
 * Removes everything except Western Arabic digits and point `.` (for example, thousands separators)
 */
fun String.removeOptionalCharacters() = this.replace(Regex("[^\\d.]"), "")

See this Wikipedia article for difference between numerals.

Mahozad
  • 18,032
  • 13
  • 118
  • 133
0

This is my code to convert an English number to an Arabic one, the solution could be optimized, modify it according to your needs, it works for all numbers.

fun englishNumberToArabicNumber(number: Int): String {
    val arabicNumber = mutableListOf<String>()
    for (element in number.toString()) {
        when (element) {
            '1' -> arabicNumber.add("١")
            '2' -> arabicNumber.add("٢")
            '3' -> arabicNumber.add("٣")
            '4' -> arabicNumber.add("٤")
            '5' -> arabicNumber.add("٥")
            '6' -> arabicNumber.add("٦")
            '7' -> arabicNumber.add("٧")
            '8' -> arabicNumber.add("٨")
            '9' -> arabicNumber.add("٩")
            else -> arabicNumber.add("٠")
        }
    }
    return arabicNumber.toString()
        .replace("[", "")
        .replace("]", "")
        .replace(",", "")
        .replace(" ", "")


}
Younes Belouche
  • 1,183
  • 6
  • 7
0

A more simple solution using Kotlin:

fun String.parseAsDouble(): Double? = this
    .map { if (it == '٫' || it == '/') '.' else it }    // See note 1
    .filter { it.isDigit() || it == '.' || it == '-' } // See note 2
    .map { it.digitToIntOrNull() ?: it }               // See note 3
    .joinToString(separator = "")
    .toDoubleOrNull()
  1. Normalizes (converts) ٫ (Persian/Urdu/Eastern Arabic decimal separator) and slash / (invalid and non-standard) to . (decimal separator in English).
    See https://en.wikipedia.org/wiki/Decimal_separator#Other_numeral_systems

  2. If you want to return null if the string contains any illegal character (like alphabet), comment or remove this line

  3. Normalizes (converts) Persian/Urdu and Eastern Arabic digits to Western Arabic digits.
    See https://en.wikipedia.org/wiki/Eastern_Arabic_numerals#Numerals

Mahozad
  • 18,032
  • 13
  • 118
  • 133