จะทราบได้อย่างไรว่า BigDecimal สามารถแปลงให้ลอยหรือเป็นสองเท่าได้อย่างแน่นอน


10

Class BigDecimalมีวิธีการที่เป็นประโยชน์ในการรับประกันการแปลงแบบไม่สูญเสีย

  • byteValueExact()
  • shortValueExact()
  • intValueExact()
  • longValueExact()

อย่างไรก็ตามวิธีการfloatValueExact()และdoubleValueExact()ไม่ได้อยู่

ผมอ่านรหัสที่มา OpenJDK สำหรับวิธีการและfloatValue() doubleValue()ทั้งคู่ดูเหมือนจะย้อนกลับไปที่Float.parseFloat()และDouble.parseDouble()ตามลำดับซึ่งอาจส่งกลับอินฟินิตี้บวกหรือลบ ตัวอย่างเช่นการแยกสตริง 10,000 9s จะส่งกลับค่าบวกอนันต์ อย่างที่ฉันเข้าใจBigDecimalไม่มีแนวคิดเกี่ยวกับอินฟินิตี้ภายใน นอกจากนี้การแยกสตริงของ 100 9s ตามที่doubleให้1.0E100ซึ่งไม่ได้ไม่มีที่สิ้นสุด แต่สูญเสียความแม่นยำ

การใช้งานที่เหมาะสมคืออะไรfloatValueExact()และdoubleValueExact()?

ฉันคิดว่าเกี่ยวกับdoubleการแก้ปัญหาโดยการรวมBigDecimal.doubleValue(), BigDecial.toString(), Double.parseDouble(String)และDouble.toString(double)แต่มันดูยุ่ง ฉันต้องการถามที่นี่เพราะอาจมี (ต้อง!) เป็นวิธีที่ง่ายกว่า

เพื่อความชัดเจนฉันไม่ต้องการโซลูชันประสิทธิภาพสูง


7
ฉันเดาว่าคุณสามารถแปลงเป็นสองเท่าแล้วเปลี่ยนกลับเป็น BigDecimal และดูว่าคุณได้รับค่าเดียวกันหรือไม่ ไม่แน่ใจว่ากรณีการใช้งานคืออะไร หากคุณใช้ doubles คุณยอมรับแล้วว่ามีค่าที่ไม่แน่นอน หากคุณต้องการค่าที่แน่นอนให้อยู่กับ BigDecimal
JB Nizet

คำตอบ:


6

จากการอ่าน เอกสารทั้งหมดก็ไม่กับสายพันธุ์คือการตรวจสอบสำหรับการดำรงอยู่ของส่วนหนึ่งส่วนหรือถ้าค่ามีขนาดใหญ่เกินไปสำหรับชนิดที่เป็นตัวเลขและโยนข้อยกเว้น numTypeValueExact

สำหรับfloatValue()และdoubleValue()มีการตรวจสอบโอเวอร์โฟลที่คล้ายกัน แต่แทนที่จะโยนข้อยกเว้นแทนที่จะส่งคืนDouble.POSITIVE_INFINITYหรือDouble.NEGATIVE_INFINITYเป็นสองเท่าและFloat.POSITIVE_INFINITYหรือFloat.NEGATIVE_INFINITYลอย

ดังนั้นที่เหมาะสมที่สุด (และง่าย) การดำเนินการตามexactวิธีการในการลอยและคู่ก็ควรตรวจสอบว่าผลตอบแทนการแปลงหรือPOSITIVE_INFINITYNEGATIVE_INFINITY


นอกจากนี้โปรดจำไว้ว่าBigDecimalได้รับการออกแบบมาเพื่อจัดการกับการขาดความแม่นยำที่มาจากการใช้floatหรือdoubleสำหรับ irrationals ขนาดใหญ่ดังนั้นเมื่อ@JB Nizet แสดงความคิดเห็นการตรวจสอบอื่นที่คุณสามารถเพิ่มไปด้านบนจะเป็นการแปลงdoubleหรือfloatย้อนกลับBigDecimalเพื่อดูว่าคุณยัง ค่าเดียวกัน นี่ควรพิสูจน์ว่าการแปลงนั้นถูกต้อง

นี่คือสิ่งที่วิธีการดังกล่าวจะมองหาfloatValueExact():

public static float floatValueExact(BigDecimal decimal) {
    float result = decimal.floatValue();
    if (!Float.isInfinite(result)) {
        if (new BigDecimal(String.valueOf(result)).compareTo(decimal) == 0) {
            return result;
        }
    }
    throw new ArithmeticException(String.format("%s: Cannot be represented as float", decimal));
}

การใช้งานcompareToแทนการใช้equalsด้านบนเป็นไปโดยเจตนาเพื่อไม่ให้เข้มงวดเกินไปกับการตรวจสอบ equalsจะประเมินค่าเป็นจริงเมื่อBigDecimalวัตถุทั้งสองมีค่าและขนาดเท่ากัน (ขนาดของส่วนของทศนิยม) ในขณะที่compareToจะมองข้ามความแตกต่างนี้เมื่อมันไม่สำคัญ ยกตัวอย่างเช่นVS2.02.00


เจ้าเล่ห์มาก สิ่งที่ฉันต้องการ! หมายเหตุ: คุณอาจต้องการใช้Float.isFinite()หรือFloat.isInfinite()สิ่งนี้เป็นทางเลือก :)
kevinarpe

3
คุณควรจะเลือกเปรียบเทียบกับมากกว่าเพราะเพราะ equals () ใน BigDecimal จะพิจารณา 2.0 และ 2.00 เป็นค่าที่ต่างกัน
JB Nizet

ค่าที่ไม่สามารถแสดงได้อย่างแม่นยำเนื่องจากการลอยจะไม่ทำงาน ตัวอย่าง: 123.456f. ฉันเดาว่านี่เป็นเพราะขนาดที่แตกต่างกันของซิกนิแคนด์ (แมนทิสซา) ระหว่าง 32- บิตลอยและ 64- บิตสองเท่า if (new BigDecimal(result, MathContext.DECIMAL32).equals(decimal)) {ในโค้ดข้างต้นของคุณฉันจะได้รับผลลัพธ์ที่ดีกว่าด้วย: นี่เป็นการเปลี่ยนแปลงที่สมเหตุสมผล ... หรือฉันไม่มีตัวพิมพ์เล็กอีกค่าหนึ่งของค่าทศนิยม
kevinarpe

1
@kevinarpe - 123.456fเป็นแนวคิดเดียวกันกับตัวอย่าง 1.0E100 ของคุณ มันกระทบฉันว่าถ้าคุณต้องการตรวจสอบว่าการแปลงจาก BigDecimal -> จุดลอยตัวไบนารี่นั้นแน่นอนแล้วความต้องการนั้นก็เป็นปัญหา เช่นการแปลงไม่ควรไตร่ตรอง
สตีเฟนซี
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.