2

I am not sure that Java has tools for it but if there is a solution in Java it would be awesome. For example, I need to increment fifth significant digit in Double / Decimal / BigDecimal or similar variable.

// Left is what I have. Right is what I need to get
12345 -> 12346
1234567 -> 1234667
123 -> 123.01
0.12345 -> 0.12346
0.1 -> 0.10001
0.0012345 -> 0.0012346
123.5 -> 123.51
12.5 -> 12.501
0.000123456789 -> 0.000123466789

Is there any solution to increment / decrement fixed significant digits?

EDIT:
Inaccurate arithmetic is OK. I.e. if we turn 12.5 to 12.50100000000041 its OK.
Min input value is 0.00001. Max input value is 100000.0.
Max significant digit to increment/decrement is 5.
I.e. min output value after increment is 0.000010001
Length from highest significant digit to least significant digit is no more than 10

P.S. Please, test your solution at least with all example numbers listed in this question before posting an answer. I will check all answers to find the most effective solution. The fastest method will be marked as a correct one.

Oleksandr
  • 3,574
  • 8
  • 41
  • 78
  • No, and floating point arithmetic is inaccurate anyway. Why do you think you need to do something like this? – Kayaman Sep 20 '17 at 10:35
  • It's doable but would be very, very tricky in IEEE754 binary floating point. My suggestion would be to go via the BigDecimal route. – Bathsheba Sep 20 '17 at 10:36
  • I have a special calculation which requires to increment / decrement significant digits in numbers. – Oleksandr Sep 20 '17 at 10:38
  • @Bathsheba could you show an example how to increment / decrement fixed significant digits with BigDecimal? – Oleksandr Sep 20 '17 at 10:39
  • @Kayaman Inaccurate arithmetic is OK in my calculations. I.e. if we turn 12.5 to 12.50100000000041 its OK. I will round it anyway. – Oleksandr Sep 20 '17 at 10:43
  • 2
    Expected 'digits' are decimal, double implementation is binary. FP computation has not only accuracy problem (like @kayaman say), but representation problem too: not every value exist in double domain. This cannot be ROUNDED, or cannot be reached by rounding. – Jacek Cz Sep 20 '17 at 10:47
  • You could probably dick about with log10 to get the magnitude of a number, round that cleverly, subtract 5, raise 10 to that power, and add that back to the original number. But I'm too old to test all the edge cases. Plus it will puke for 0 or less. I'll upvote any successful implementation. – Bathsheba Sep 20 '17 at 10:49
  • @JacekCz: That said, increasing the 5th significant figure, neglecting the effect on anything beyond the 15th significant figure, is a well-defined problem in double precision binary floating point. – Bathsheba Sep 20 '17 at 10:55
  • Do you have to work with doubles in the first place? With a decimal fixed point number this would be easier (except when the 5th sigdig would fall off the edge) – harold Sep 20 '17 at 11:11
  • @Bathsheba I dont know, I correctly understand You. I think 0.699999999987757 + 0.00000696999696 =? hard to say what the 5th digit is (numbers artifical, not from real life) – Jacek Cz Sep 20 '17 at 11:13
  • @JacekCz: But the OP is only adding something of the form 1q or .q1q where q is any number of zeros, subject to the constraint of 1 being in the 5th significant position of the other number. – Bathsheba Sep 20 '17 at 11:15
  • I have updated the question. Inaccurate arithmetic is OK. It's not required to use double type to store numbers. Any convenient type is accepted. There will be no input numbers like 1234500000.0000000012345. Only normal numbers which is listed in the question. – Oleksandr Sep 20 '17 at 11:18
  • what about 123 what should the result? – Youcef LAIDANI Sep 20 '17 at 12:09
  • @YCF_L I've updated a question. 123 -> 123.01 – Oleksandr Sep 20 '17 at 12:39
  • Do you want carries? e.g. if incrementing .01 digit, with input approximately 3.99, what should the result be? I can make a case for any of 3.99, 3.9, and 4.0. – Patricia Shanahan Sep 20 '17 at 13:24
  • @Bathsheba can you please check my answer hope i don't miss any case ;) – Youcef LAIDANI Sep 20 '17 at 13:38
  • 2
    @YCF_L: It's nice aside from, IMHO, the use of a regular expression to evaluate the order of magnitude. – Bathsheba Sep 20 '17 at 13:57
  • @YCF_L Please, check your output with output from the question. It doesn't work as expected. Only some cases work as expected – Oleksandr Sep 20 '17 at 14:16

3 Answers3

0

Hope this can help you :

int position;
BigDecimal[] numbers = {new BigDecimal("12345.0"), new BigDecimal("1234567.0"),
    new BigDecimal("0.12345"), new BigDecimal("0.1"),
    new BigDecimal("0.0012345"), new BigDecimal("123.5"), new BigDecimal("12.5"),
    new BigDecimal("1234500000.0000000012345")};

for (BigDecimal number : numbers) {
    System.out.println(number);
    String leftPart = String.valueOf(number).replaceAll("^(\\d+)\\.\\d+", "$1");
    String rightPart = String.valueOf(number).replaceAll("^\\d+\\.(\\d+)", "$1");
    int left = leftPart.length();
    int right = rightPart.length();
    if (Integer.parseInt(leftPart) == 0) {
        position = 6 + rightPart.replaceAll("^([0]*).*", "$1").length();
    } else {
        position = 5;
    }
    if (left <= position) {
        number = number.add(new BigDecimal(Math.pow(0.1, position - left)));
    } else {
        number = number.add(new BigDecimal(Math.pow(10, left - position)));
    }
    System.out.println(number.setScale(right > 5 ? right : 5, RoundingMode.DOWN) + "\n..................................");
}

Note

You can change add to subtract in case of increment

Outputs

12345.0
12346.00000
..................................
1234567.0
1234667.00000
..................................
0.12345
0.12346
..................................
0.1
0.10001
..................................
0.0012345
0.0012346
..................................
123.5
123.51000
..................................
12.5
12.50100
..................................
1234500000.0000000012345
1234600000.0000000012345
..................................

ideone demo

Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
  • But it shows wrong values... 0.12345 must be 0.12346 and so on. Some values are correct and some are not. Check your output with output from the question – Oleksandr Sep 20 '17 at 14:11
  • ok @Alexandr we are close i will edit my answer soon i forgot this case ;) – Youcef LAIDANI Sep 20 '17 at 14:15
  • @Alexandr so the 0 in cases like 0.123 is not count? – Youcef LAIDANI Sep 20 '17 at 14:17
  • Sorry but it doesn't work still. Check output values from the answer. Cases which don't work with your solution: `0.0012345 -> 0.0012346` `0.000123456789 -> 0.000123466789` – Oleksandr Sep 20 '17 at 15:21
  • @Alexandr check now – Youcef LAIDANI Sep 20 '17 at 17:27
  • Seems that now it works. Unfortunately, it has to be optimized to be marked as a correct answer. I had comparison on 10000000 random double values. Initialization time wasn't counted. Output printing and rounding was removed from your method. On my computer average time to compute all elements with your method takes 36 seconds. My method takes 3.5 seconds on average. If you can optimize your method at least 10x time I will mark your answer as a correct one. – Oleksandr Sep 20 '17 at 18:12
0

I can propose an effective approach:

  1. Count the number of the caraters of your given number, example: 1 -> 1, 152 -> 3, 123.65899 -> 8
  2. You will have two cases now, size is >5 or <5
  3. 1st case: if the size is >5 then remove the "comma" from your number and save the size of the number after the "comma" in a 10**size format, example: 12345.67 = 1234567 x 10**-2 , 0.1234567 = 1234567 x 10**-7
  4. Add 1 to the number after removing the "comma": 12345.67 = (1234567 + 1) x 10**-2 = 12345.68 , 0.99999 = (99999 + 1) x 10**-5 = 1.00000
  5. 2nd case: if the size is <5 add zeros and inc the power of 10 by a negative value, example 5 = 50000 x 10**-4
  6. Same as the 1st case add 1 and apply 10**-x, example: 1234 = 12340 x 10**-1 -> (12340 + 1) x 10**-1 = 1234.1

Note: you can apply this solution to any other number then 5.

ziMtyth
  • 1,008
  • 16
  • 32
  • Sorry, your solution doesn't work. Test your solution with `0.01234567 -> 0.01234667`. Please, check your solution before editing with output values in question. – Oleksandr Sep 20 '17 at 15:58
0

Here is my solution till now. It's not nice as I need to work with a string but it is pretty fast. At least I don't know how to make it faster.

public static final DecimalFormat DF = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ENGLISH);

static {
    DF.setMaximumFractionDigits(16);
    DF.setGroupingUsed(false);
}

public static double fixedSignificantDigitIncrement(double input, int significantDigit){
    return input+fixedSignificantDigitAmount(input, significantDigit);
}

public static double fixedSignificantDigitDecrement(double input, int significantDigit){
    return input-fixedSignificantDigitAmount(input, significantDigit);
}

public static double fixedSignificantDigitAmount(double input, int significantDigit){
    String inputStr = DF.format(input);

    int pointIndex = inputStr.indexOf('.');

    int digitsBeforePoint;
    if(pointIndex==-1){
        pointIndex=inputStr.length();
        digitsBeforePoint=inputStr.length();
    } else {
        digitsBeforePoint = pointIndex;
        if(digitsBeforePoint==1 && inputStr.charAt(0)=='0') digitsBeforePoint=0;
    }

    if(significantDigit<=digitsBeforePoint){
        return Math.pow(10, digitsBeforePoint-significantDigit);
    }  else if(digitsBeforePoint==0){
        ++pointIndex;
        for(;pointIndex<inputStr.length();pointIndex++){
            if(inputStr.charAt(pointIndex)!='0') break;
        }

        return 1/Math.pow(10, significantDigit+(pointIndex-2));
    }

    return 1/Math.pow(10, significantDigit-digitsBeforePoint);
}
Oleksandr
  • 3,574
  • 8
  • 41
  • 78