How to format numbers with two decimal places?
Asked Answered
M

1

5

This is what I am doing to display the amount with two decimal places. It is working fine, but I would like to know if this is right or is there any drawback in doing so and what is the better way to do it?

I want to format the amount for 'Your Price' and 'MRP'. How can I do that?

        holder.itemView.tv_dashboard_item_title.text = model.title
        holder.itemView.tv_dashboard_item_price.text = "Your Price ₹${model.price}"
        holder.itemView.tv_dashboard_item_mrp.text = "MRP ₹${model.mrp}"
        val mrp:Double? = model.mrp
        val price: Double? = model.price
        val save=DecimalFormat("#,##0.00")
        val save2: Double = mrp!! - price!!
        val saves=save.format(save2)

        holder.itemView.tv_dashboard_item_you_save.text = "You Save ₹  $saves"

Thank you.

EDIT

Revised code.

    val decFormat = DecimalFormat("#,##0.00")
    holder.itemView.tv_dashboard_item_title.text = model.title
    val mrp: BigDecimal = model.mrp.toBigDecimal()
    val price: BigDecimal = model.price.toBigDecimal()

    val save: BigDecimal = mrp - price
    val saveAmount = decFormat.format(save)

    holder.itemView.tv_dashboard_item_price.text = decFormat.format(price)
    holder.itemView.tv_dashboard_item_mrp.text = decFormat.format(mrp)
    holder.itemView.tv_dashboard_item_you_save.text = "You Save ₹  $saveAmount"

EDIT 2

Following is the model class Product.kt

@Parcelize
data class Product(
    val user_id: String = "",
    val user_name: String = "",
    val title: String = "",
    val mrp: BigDecimal= "0.00".toBigDecimal(),
    val price: BigDecimal = "0.00".toBigDecimal(),
    val description: String = "",
    val stock_quantity: String = "",
    val image: String = "",
    var brand_name:String="",
    var manufacturer:String="",
    var main_category:String="",
    var sub_category:String="",
    var product_id: String = "",
) : Parcelable

Function in AddProductActivity.kt to upload product details

   private fun uploadProductDetails() {
        val username =
            this.getSharedPreferences(Constants.TRAD_PREFERENCES, Context.MODE_PRIVATE)
                .getString(Constants.LOGGED_IN_USERNAME, "")!!

        val product = Product(
            FirestoreClass().getCurrentUserID(),
            username,
            et_product_title.text.toString().trim { it <= ' ' },
            et_product_mrp.text.toString().toBigDecimal(),
            et_product_price.text.toString().toBigDecimal(),
            et_product_description.text.toString().trim { it <= ' ' },
            et_product_quantity.text.toString().trim { it <= ' ' },
            mProductImageURL,
            et_product_brand_name.text.toString().trim { it <= ' ' },
            et_product_manufacturer.text.toString().trim { it <= ' ' },
            et_product_main_category.text.toString().trim { it <= ' ' },
            et_product_sub_category.text.toString().trim { it <= ' ' },
        )

        FirestoreClass().uploadProductDetails(this@AddProductActivity, product)
    }

Logcat

   --------- beginning of crash
2021-06-19 18:36:11.605 6674-6674/com.trad E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.trad, PID: 6674
    java.lang.IllegalArgumentException: Could not serialize object. Numbers of type BigDecimal are not supported, please use an int, long, float or double (found in field 'mrp')
        at com.google.firebase.firestore.util.CustomClassMapper.serializeError(CustomClassMapper.java:555)
        at com.google.firebase.firestore.util.CustomClassMapper.serialize(CustomClassMapper.java:122)
        at com.google.firebase.firestore.util.CustomClassMapper.access$400(CustomClassMapper.java:54)
        at com.google.firebase.firestore.util.CustomClassMapper$BeanMapper.serialize(CustomClassMapper.java:902)
        at com.google.firebase.firestore.util.CustomClassMapper.serialize(CustomClassMapper.java:178)
        at com.google.firebase.firestore.util.CustomClassMapper.serialize(CustomClassMapper.java:104)
        at com.google.firebase.firestore.util.CustomClassMapper.convertToPlainJavaTypes(CustomClassMapper.java:78)
        at com.google.firebase.firestore.UserDataReader.convertAndParseDocumentData(UserDataReader.java:231)
        at com.google.firebase.firestore.UserDataReader.parseMergeData(UserDataReader.java:87)
        at com.google.firebase.firestore.DocumentReference.set(DocumentReference.java:166)
        at com.trad.firestore.FirestoreClass.uploadProductDetails(FirestoreClass.kt:246)
        at com.trad.ui.activities.AddProductActivity.uploadProductDetails(AddProductActivity.kt:280)
        at com.trad.ui.activities.AddProductActivity.imageUploadSuccess(AddProductActivity.kt:254)
        at com.trad.firestore.FirestoreClass$uploadImageToCloudStorage$1$1.onSuccess(FirestoreClass.kt:212)
        at com.trad.firestore.FirestoreClass$uploadImageToCloudStorage$1$1.onSuccess(FirestoreClass.kt:27)
        at com.google.android.gms.tasks.zzn.run(com.google.android.gms:play-services-tasks@@17.2.0:4)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:246)
        at android.app.ActivityThread.main(ActivityThread.java:8512)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Mormon answered 19/6, 2021 at 7:36 Comment(1)
Why not use Double or Float to represent currency?Christianize
A
6

Using a DecimalFormat is a perfectly good way to format a number.

However, you have a deeper issue here, which is that you should never use a Float or Double to store a value that needs to be exact, such as money.

That's because Floats and Doubles are stored as binary fractions, not decimal fractions.  And just as you can't represent 1/3 exactly as a decimal fraction of any finite length (0.33333333333…), so you can't represent 1/10 exactly as a binary fraction (0.0001100110011…).  So most of the decimal numbers you want to store will get approximated and rounded to the nearest binary fraction that can be stored.  This isn't always obvious — when printing them out, they get rounded again to the nearest decimal fraction, and in many cases that ‘recovers’ the number you want — but there are many cases where it's noticeable, especially as the result of calculations.

You can see the effect in the Kotlin REPL:

>>> 0.1 + 0.2
res0: kotlin.Double = 0.30000000000000004

In this case, the binary fractions nearest to 0.1 and 0.2 sum to give a binary fraction that's nearer to 0.30000000000000004 than it is to 0.3.

(There are many existing questions on StackOverflow discussing this, such as here.)

So if you need your money values to be accurate (and you almost always do!), then you should store them some other way.  For example, if you only ever need two decimal places (i.e. the number of paise), then simply store the number of paise as an integer.  Or if you don't need to do any calculations, you could store the number as a string (which is otherwise a bad idea…).

However, the most general and flexible way in Kotlin (and Java) is to use BigDecimal.  That uses decimal fractions internally to represent any decimal number exactly, to any precision you need, and you can easily do calculations and other manipulations.

In Java, using it is awkward and long-winded, but Kotlin's operator overloading makes it very natural, e.g.:

>>> val p1 = "0.1".toBigDecimal()
>>> val p2 = "0.2".toBigDecimal()
>>> p1 + p2
res3: java.math.BigDecimal = 0.3

DecimalFormat supports it too:

>>> java.text.DecimalFormat("#,##0.00").format(p1 + p2)
res4: kotlin.String! = 0.30

(Note: do not create a BigDecimal from a Float or Double, as the damage will already have been done.  If you have an integer value, then start from an integer type such as Int or Long; otherwise, you'll need to start from a String to get an exact value.)

Angadresma answered 19/6, 2021 at 9:2 Comment(6)
I asked you for a flower but you gave me a garden full of flowers. thanks. But, being a beginner I still have some more issues that I couldn't sort out myself. I changed 'Double' to 'BigDecmal' in the above code but when I make the same changes in the model class, I get an error as 'The floating-point literal does not conform to the expected type BigDecimal'. These are the two lines of code in my data class. 'val mrp: BigDecimal= 0.00, val price: BigDecimal = 0.00,Mormon
How I sorted it out now is, I kept my (model class) data class variable type as Double and the codes in my question is changed as follow. It works and there is no error. But I do not know if this is how it's supposed to be done. val mrp: BigDecimal = model.mrp.toBigDecimal() val price: BigDecimal = model.price.toBigDecimal(). The revised code is updated in the question. Please check if that is OK.Mormon
As per my last paragraph, if you're storing the values as Double in your model, then the damage is already done.  You can initialise a BigDecimal with e.g. "0.00".toBigDecimal(), or BigDecimal("0.00"), or (if you don't need 2DP precision at that point) 0.toBigDecimal() or BigDecimal.ZERO.Angadresma
Yes, you have mentioned that in advance but I couldn't do it. After going through your last comment I made the changes in my model class. Unfortunately, I am getting errors when I upload items to Firestore. I have uploaded the latest logcat and code in my question. I have been trying to fix it on my own but I couldn't .Mormon
I don't know Firestore, I'm afraid.  Perhaps that could be a separate question?Angadresma
I fixed all errors and it's working fine now. Thank you.Mormon

© 2022 - 2024 — McMap. All rights reserved.