1

Edit: This has to do with how computers handle floating point operations, a fact that every programmer faces once in a lifetime. I didn't understand this correctly when I asked the question.

I know the simplest way to start dealing with this would be:

val floatNumber: Float = 123.456f
val decimalPart = floatNumber - floatNumber.toInt() //This would be 0.456 (I don't care about precision as this is not the main objective of my question)

Now in a real world with a pen and a piece of paper, if I want to "convert" the decimal part 0.456 to integer, I just need to multiply 0.456 * 1000, and I get the desired result, which is 456 (an integer number).

Many proposed solutions suggest splitting the number as string and extracting the decimal part this way, but I need the solution to be obtained mathematically, not using strings.

Given a number, with an unknown number of decimals (convert to string and counting chars after . or , is not acceptable), I need to "extract" it's decimal part as an integer using only math.

Read questions like this with no luck:

How to get the decimal part of a float?

How to extract fractional digits of double/BigDecimal

If someone knows a kotlin language solution, it would be great. I will post this question also on the math platform just in case. How do I get whole and fractional parts from double in JSP/Java?

Update: Is there a "mathematical" way to "calculate" how many decimals a number has? (It is obvious when you convert to string and count the chars, but I need to avoid using strings) It would be great cause calculating: decimal (0.456) * 10 * number of decimals(3) will produce the desired result.

Update 2 This is not my use-case, but I guess it will clarify the idea: Suppose you want to calculate a constant(such as PI), and want to return an integer with at most 50 digits of the decimal part of the constant. The constant doesn't have to be necessarily infinite (can be for example 0.5, in which case "5" will be returned)

Raymond Arteaga
  • 4,355
  • 19
  • 35
  • multiply the number by 10 as long as it is not equal to its int part or as long as the fractional part is non-zero (recalculated after each multiplication) but be aware of rounding/floating point errors – user85421 Jul 21 '18 at 18:27
  • 1
    Before you embark on this task, you should understand what a floating-point number is. Kotlin’s [`Float`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-float/index.html) or [`Double`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/index.html) types use IEEE-754 binary floating-point. In these formats, numbers are represented as an integer multiplied or divided by a power of two. The number 123.456 **does not exist** in `Float`. The source code `123.456f` is converted to 16181625 / 131072, which is 123.45600128173828125.… – Eric Postpischil Jul 21 '18 at 23:17
  • Given the number `123.456f`, the digits can be extracted mathematically, but you will get “45600128173828125”, not “456”. An alternative is to work with numbers in a decimal floating-point format instead of `Float` or `Double`, but some quick searching does not show Kotlin has support for that. One can also extract the desired digits if the number is known to be associated with a number with a limited number of decimal digits (where the limit depends on the format, `Float` or `Double`), but this sort of method ought to be done with a proper understanding of floating-point arithmetic. – Eric Postpischil Jul 21 '18 at 23:20

4 Answers4

3

I would just multiply the fractional number by 10 (or move the decimal point to the right) until it has no fractional part left:

public static long fractionalDigitsLong(BigDecimal value) {
    BigDecimal fractional = value.remainder(BigDecimal.ONE);
    long digits;
    do {
        fractional = fractional.movePointRight(1);  // or multiply(BigDecimal.TEN)
        digits = fractional.longValue();
    } while (fractional.compareTo(BigDecimal.valueOf(digits)) != 0);
    return digits;
}

Note 1: using BigDecimal to avoid floating point precision problems

Note 2: using compareTo since equals also compares the scale ("0.0" not equals "0.00")

(sure the BigDecimal already knows the size of the fractional part, just the value returned by scale())


Complement:

If using BigDecimal the whole problem can be compressed to:

public static BigInteger fractionalDigits(BigDecimal value) {
    return value.remainder(BigDecimal.ONE).stripTrailingZeros().unscaledValue();
}

stripping zeros can be suppressed if desired

user85421
  • 28,957
  • 10
  • 64
  • 87
  • Tried it with `3.14159` it returns `14158999999999988`. Rounding problem –  Jul 21 '18 at 19:06
  • 1
    see Note 3 - you actually passed the later number – user85421 Jul 21 '18 at 19:07
  • I know about precision problems with doubles and floats, this is why I said you must consider rounding, because the way this works will produce the mentioned results –  Jul 21 '18 at 19:14
  • I also know about that problems - that is why I had added the 3rd note... changed the code to 'integrate' that note – user85421 Jul 21 '18 at 19:28
  • please note that this code was neither implemented for ,nor tested with negative numbers - it is just intended as a showcase – user85421 Jul 21 '18 at 19:38
  • Note: BigDecimal is the solution, however one should start with `new BigDecimal("3.120")` (precision 3) - not `new BigDecimal(3.120)` which copies the floating point approximation error). And one can use `scale()` to receive -3. – Joop Eggen Jul 22 '18 at 01:29
  • @JoopEggen not sure HOW the OP is getting his number, at least double would not work with 50 digits (his example) - maybe hes calculations are aleady using `BigDecimal`. About the `scale()`: read the last line I posted.... and, again, the whole algorithm is just as demonstration, actually it is enough to get the unscaled value of the fractional part, no loop needed at all (but to eliminate right hand zeros) – user85421 Jul 22 '18 at 06:54
  • @CarlosHeuberger Edit `fractionalDigits()` and replace `number` with `fractional`. Both `nonfrac()` and `fractionalDigits()` keep 1 digit from the integer part, so `93.14` returns `314` –  Jul 22 '18 at 08:33
1

I am not sure if it counts against you on this specific problem if you use some String converters with a method(). That is one way to get the proper answer. I know that you stated you couldn't use String, but would you be able to use Strings within a Custom made method? That could get you the answer that you need with precision. Here is the class that could help us convert the number:

class NumConvert{
     String theNum;

     public NumConvert(String theNum) {
           this.theNum = theNum;
}
     public int convert() {
            String a = String.valueOf(theNum);
            String[] b = a.split("\\.");
            String b2 = b[1];
            int zeros = b2.length();
            String num = "1";
            for(int x = 0; x < zeros; x++) {
                num += "0";
    }
            float c = Float.parseFloat(theNum);
            int multiply = Integer.parseInt(num);
            float answer = c - (int)c;
            int integerForm = (int)(answer * multiply);
            return integerForm;
     }
 }

Then within your main class:

public class ChapterOneBasics {
    public static void main(String[] args) throws java.io.IOException{
          NumConvert n = new NumConvert("123.456");
          NumConvert q = new NumConvert("123.45600128");
          System.out.println(q.convert());
          System.out.println(n.convert());
    }
}

output:

 45600128
 456
Simeon Ikudabo
  • 2,152
  • 1
  • 10
  • 27
  • 1
    It will only work for numbers with 3 decimals. The number of decimals I have for input is unknown. – Raymond Arteaga Jul 21 '18 at 17:49
  • Could you convert it to a String IF it involves creating your own class to do that manually, or would that not work either? @RaymondArteaga I'd assume that you wouldn't be allowed to just create a class to handle it. – Simeon Ikudabo Jul 21 '18 at 18:27
  • 2
    If your number is currently stored as a float, how do you know "how many decimals" it really has? For example, 123.456 when printed to more digits than the default is really 123.45600128, since 123.456 cannot be exactly represented as a float. – FredK Jul 21 '18 at 18:34
  • @FredK We don't need to know. The method() would simply need to know when the float is passed to it. The issue as the op mentioned is that he isn't allowed to use Strings to find the amount of number after the decimal point. But in theory, only the method() needs to know what the float is. – Simeon Ikudabo Jul 21 '18 at 18:43
  • @Simeon I disagree. If you pass `f=123.456f` to the function, ands later pass `g=123.45600128f` to the function, I believe the OP hopes to get "456" from the first and "45600128" from the second. But that is impossible, since both of those values are identical as far as the computer is concerned. – FredK Jul 21 '18 at 18:47
  • 1
    The OP really needs to clarify what he wants. If I say `f=123.50`, does he want the decimal part to be '5' or '50'? The point is that is impossible to know exactly how many digits there are after the decimal point in a float value. – FredK Jul 21 '18 at 18:53
  • @FredK I just tested the method() and it works properly. The issue is that we can't convert this to a String though. But if you create a method that takes the float as a String argument, and splits that String on the "." it should work. I would post my code, but it's irrelevant because the OP can't use Strings. If you have a number as a String such as "123.45600128" the method will do something similar to String b[] = float.split("\\."). You then take the length of b[1] and that will show you how many digits you have after the decimal within the method(). But again, the OP can't use Strings. – Simeon Ikudabo Jul 21 '18 at 18:54
  • @FredK I think you are right Fred in terms of the OP wanting to be able to work with whatever the float number ends up being. I really don't know how to do that though without making this a String though. – Simeon Ikudabo Jul 21 '18 at 18:55
  • I've posted an update... but I think I will need to convert to string hopelessly :( – Raymond Arteaga Jul 21 '18 at 18:56
  • @RaymondArteaga There's no such internal representation because floats aren't represented in decimal. – Alexey Romanov Jul 21 '18 at 19:13
1

Float or Double are imprecise, just an approximation - without precision. Hence 12.345 is somewhere between 12.3449... and 12.3450... .

This means that 12.340 cannot be distinghuished from 12.34. The "decimal part" would be 34 divided by 100. Also 12.01 would have a "decimal part" 1 divided by 100, and too 12.1 would have 1 divided by 10.

So a complete algorith would be (using java):

int[] decimalsAndDivider(double x) {
    int decimalPart = 0;
    int divider = 1;
    final double EPS = 0.001;
    for (;;) {
        double error = x - (int)x;
        if (-EPS < error && error < EPS) {
            break;
        }
        x *= 10;
        decimalPart = 10 * decimalPart + ((int)(x + EPS) % 10);
        divider *= 10;
    }
    return new int[] { decimalPart, divider };
}
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • `12.34` -> `[2464, 100]`; `3.14159` -> `[1566409, 100000]` ?? – user85421 Jul 21 '18 at 21:23
  • only fractional part: `0.34` -> `[64, 100]` (adding `(int) x` to decimal part is correct only on first interaction; probably wanted to add last digit of it = modulo?) – user85421 Jul 21 '18 at 21:30
  • @CarlosHeuberger sorry forgot the modulo 10 to receive a next digit; you are right. Corrected. – Joop Eggen Jul 22 '18 at 01:25
0

I posted the below solution yesterday after testing it for a while, and later found that it does not always work due to problems regarding precision of floats, doubles and bigdecimals.
My conclusion is that this problem is unsolvable if you want infinite precision:
So I re-post the code just for reference:

fun getDecimalCounter(d: Double): Int {
    var temp = d
    var tempInt = Math.floor(d)

    var counter = 0

    while ((temp - tempInt)  > 0.0 ) {
        temp *= 10
        tempInt = Math.floor(temp)
        counter++
    }

    return counter
}


fun main(args: Array <String> ) {
    var d = 3.14159
    if (d < 0) d = -d
    val decimalCounter = getDecimalCounter(d)
    val decimalPart = (d - Math.floor(d))
    var decimalPartInt = Math.round(decimalPart * 10.0.pow(decimalCounter))
    while (decimalPartInt % 10 == 0L) {
        decimalPartInt /= 10
    }
    println(decimalPartInt)
}

I dropped floats because of lesser precision and used doubles.
The final rounding is also necessary due to precision.