Java / Kotlin: Calculate the quotient by specifying the number of significant digits when it is not divisible by the division (division) of BigDecimal.

If it is divisible, please calculate up to the point where it is divisible. But if it is not divisible, always find the quotient with the same number of significant digits. I think it will be necessary for an application that displays the calculation result of division like a calculator. I couldn't find a good way on the net, so I thought about it myself (`・ ω ・ ´)

(Added on 2018/07/16) To clarify what I want to achieve, I added a usage example as a GIF below. By the way, this image was taken with a calculator app called Calc Leaf that I developed.

In this way, even if the answer is an infinite decimal, the purpose is to display it properly within the displayable range.

For Java

Here are some output examples. I will omit it here, but if you format the obtained answer with Decimal Format, the display will look good.

Division.java


import java.math.BigDecimal;
import java.math.RoundingMode;

public class Division {
	
	public static final int SIGNIFICANT_DIGITS = 3; //Number of significant digits
	
	public static void main(String[] args) {
		try {
			System.out.println(divided("10000", "3")); // 3330
			System.out.println(divided("1000", "3"));  // 333
			System.out.println(divided("100", "3"));   // 33.3
			System.out.println(divided("10", "3"));    // 3.33
			System.out.println(divided("1", "3"));     // 0.333
			System.out.println(divided("1", "30"));    // 0.0333
			System.out.println(divided("1", "300"));   // 0.00333
			System.out.println(divided("1", "3000"));  // 0.000333
			System.out.println(divided("1", "30000")); // 0.0000333
		} catch(NumberFormatException nfe) {
			System.out.println("Error: Not a number.");
		} catch(ArithmeticException ae) {
			System.out.println("Error: Impossible to calculate.");
		} catch(Exception e) {
			System.out.println("Error: Unexpected.");
		}
	}

	public static String divided(String dividend, String divisor) throws Exception {
		BigDecimal bdDividend = new BigDecimal(dividend);
		BigDecimal bdDivisor = new BigDecimal(divisor);
		BigDecimal answer = null;
		
		try {
			//If it is divisible, divide it as it is
			answer = bdDividend.divide(bdDivisor);
		} catch(ArithmeticException arithmeticException) {
			//If it is not divisible, calculate a valid scale (in this example, rounding is rounded)
			answer = bdDividend.divide(bdDivisor, getSignificantScale(bdDividend, bdDivisor), RoundingMode.HALF_UP);
		}
		return answer.stripTrailingZeros().toPlainString();
	}
	
	public static int getSignificantScale(BigDecimal dividend, BigDecimal divisor) {
		//Absolute value excluding trailing zeros after the decimal point
		BigDecimal bd1 = dividend.abs().stripTrailingZeros();
		BigDecimal bd2 = divisor.abs().stripTrailingZeros();
		//Convert to plain character string according to natural numbers
		int decimalDigits = bd1.scale() > bd2.scale() ? bd1.scale() : bd2.scale();
		if (decimalDigits < 0) decimalDigits = 0;
		String s1 = bd1.scaleByPowerOfTen(decimalDigits).toPlainString();
		String s2 = bd2.scaleByPowerOfTen(decimalDigits).toPlainString();
		//Calculate and return a valid scale
		return SIGNIFICANT_DIGITS - (s1.length() - s2.length()) - (s1.compareTo(s2) >= 0 ? 1 : 0);
	}
}

For Kotlin

You can copy and paste it to Try kotlin as it is. You can use let to make val on one line where you want decimalDigits, but I stopped it because it's subtle.

Division.kt


import java.math.BigDecimal
import java.math.RoundingMode

const val SIGNIFICANT_DIGITS: Int = 3 //Number of significant digits

fun main(args: Array<String>) {
    try {
    	println(divided("10000", "3")) // 3330
    	println(divided("1000", "3"))  // 333
    	println(divided("100", "3"))   // 33.3
    	println(divided("10", "3"))    // 3.33
    	println(divided("1", "3"))     // 0.333
    	println(divided("1", "30"))    // 0.0333
    	println(divided("1", "300"))   // 0.00333
    	println(divided("1", "3000"))  // 0.000333
    	println(divided("1", "30000")) // 0.0000333
    } catch(nfe: NumberFormatException) {
        println("Error: Not a number.")
    } catch(ae: ArithmeticException) {
        println("Error: Impossible to calculate.")
    } catch(e: Exception) {
        println("Error: Unexpected.")
    }
}

fun divided(dividend: String, divisor: String): String {
    val bdDividend = BigDecimal(dividend)
    val bdDivisor = BigDecimal(divisor)
    val answer = try {
        //If it is divisible, divide it as it is
        bdDividend.divide(bdDivisor)
    } catch(ae: ArithmeticException) {
        //If it is not divisible, calculate a valid scale (in this example, rounding is rounded)
        bdDividend.divide(bdDivisor, getSignificantScale(bdDividend, bdDivisor), RoundingMode.HALF_UP)
    }
    return answer.stripTrailingZeros().toPlainString()
}

fun getSignificantScale(dividend: BigDecimal, divisor: BigDecimal): Int {
    //Absolute value excluding trailing zeros after the decimal point
    val bd1 = dividend.abs().stripTrailingZeros()
    val bd2 = divisor.abs().stripTrailingZeros()
    //Convert to plain character string according to natural numbers
    var decimalDigits = if (bd1.scale() > bd2.scale()) bd1.scale() else bd2.scale()
    if (decimalDigits < 0) decimalDigits = 0
    val s1 = bd1.scaleByPowerOfTen(decimalDigits).toPlainString()
    val s2 = bd2.scaleByPowerOfTen(decimalDigits).toPlainString()
    //Calculate and return a valid scale
    return SIGNIFICANT_DIGITS - (s1.length - s2.length) - (if (s1 >= s2) 1 else 0)
}

Please use at your own risk

You can test the above code yourself and use it as you like. The test code I conducted has been uploaded to github (released in response to comments from kaizen_nagoya), but We verified about 50 patterns based on the relationship between the number to be divided and the number to be divided, the position of the decimal point, positive / negative, and exponential notation, and cleared the requirements. However, it is undeniable that it lacks objectivity because I was doing it alone (´ ・ ω ・ `) So, those who review and re-verify are welcome! Also, if you have information on libraries or reference sites that are doing the same thing, I would be very happy if you could let me know! (I thought about it now, but it may be listed in the math dictionary. I don't have it.)

You may adjust it considering the display of the calculation result.

getSignificantScale is actually used in the calculator app CalcLeaf that I developed, but it's actually a little There is a difference in the implementation. The point is that "0 is returned if the calculation result of a valid scale is negative" as shown below.

Sample.java


int significantScale = SIGNIFICANT_DIGITS - (s1.length() - s2.length()) - (s1.compareTo(s2) >= 0 ? 1 : 0);
return significantScale < 0 ? 0 : significantScale;

In the case of a calculator, for example, if the answer of 1,000,000,000,000 ÷ 3 becomes 333,333,333,330, it feels unpleasant, so Calc Leaf adjusts it as a specification.

Impressions

I wish I could do it as standard with Java API. I wonder if people in Java will consider it if they see it.

Recommended Posts

Java / Kotlin: Calculate the quotient by specifying the number of significant digits when it is not divisible by the division (division) of BigDecimal.
[Java] Calculate the day of the week from the date (Calendar class is not used)
Please note the division (division) of java kotlin Int and Int
The idea of cutting off when the error is not resolved
Count the number of digits after the decimal point in Java
[Rails] When the layout change of devise is not reflected
Switch the version of java installed by SDKMAN when moving directories
Investigation method when the CPU of the server running java is heavy