0

I'm trying to convert the user's height in cm to ft and inc values, and also to convert them to cm by selecting ft and inc values. I am sharing the parts I did, please help, I couldn't get out of the work.

For example, I enter 100 cm in the cm field and when I press the ft button, it needs to convert 100 cm to ft and it converts it to 3 ft 3 inches, in the same way, when I press the cm button again without changing anything, it should convert 3 ft 3 inches to cm, but 3 ft. 3 inches translates as 99 cm, in the same way, when I press ft again without changing anything, this time it is 3 ft 2 inches because it calculates over 99 cm and continues like this. I couldn't solve this problem, where could I be doing wrong, it shouldn't be like this?

view

   Button(
                        modifier = Modifier.padding(5.dp),
                        colors = ButtonDefaults.buttonColors(backgroundColor = toggleButtonBackgroundColor1),
                        shape = RoundedCornerShape(25.dp),
                        elevation = ButtonDefaults.elevation(0.dp, 0.dp),
                        onClick = {
                            if(!btnHeightIsSelected){
                                btnHeightIsSelected = true
                                btnFtIsSelected = false
                                isClickIn = false
                                isClickFt = false
                             
                                onFtToCmChange(state.ft.toInt(),state.inc.toInt())
                            }
    
                        }) {
                        Text(
                            text = stringResource(id = R.string.cm),
                            color = toggleButtonTextColor1
                        )
                    }


  Button(
                        modifier = Modifier
                            .padding(5.dp),
                        colors = ButtonDefaults.buttonColors(backgroundColor = toggleButtonBackgroundColor2),
                        shape = RoundedCornerShape(25.dp),
                        elevation = ButtonDefaults.elevation(0.dp, 0.dp),
                        onClick = {
                            if(!btnFtIsSelected){
                                btnFtIsSelected = true
                                btnHeightIsSelected = false
                                onCmToFtChange(state._height)
                            }
    
                        }) {
                        Text(
                            text = stringResource(id = R.string.ft_in),
                            color = toggleButtonTextColor2
                        )
                    }



if (btnFtIsSelected) {
            CustomHeightScreenIncInputField(
                ftValue = state.ft,
                incValue = state.inc,
                ftPlaceholder = "ft",
                inPlaceholder = "in",
                onClickFt = {
                    onPickerStateChanged(HeightPickerState.FT)
                    isClickIn = false
                    isClickFt = true
                },
                onClickIn = {
                    onPickerStateChanged(HeightPickerState.IN)
                    isClickFt = false
                    isClickIn = true
                }
            )
        } else {
            CustomInputField(
                isFocused = inputFocused,
                placeholder = "your height (cm)",
                currentValue = state._height.toString(),
                onClick = {
                    onPickerStateChanged(HeightPickerState.HEIGHT)
                })
        }

viewmodel

fun onFtToCmChange(ft: Int,inc:Int) {

        //val cmHeight = (ft*12 + inc) * 2.54
        val totalInches = ft * 12 + inc
        val cmHeight = totalInches * 2.54

        _viewState.update {
            it.copy(
                _height = cmHeight.toInt(),
                ft = ft.toString(),
                inc = inc.toString(),
                height = "${cmHeight.roundToInt()}"
            )
        }
    }








fun onCmToFtChange(_height: Int) {

        val totalInches = _height / 2.54
        val ft = (totalInches / 12).toInt()
        val inc = (totalInches % 12).toInt()

     
        _viewState.update {
            it.copy(
                ftHeight = "$ft.$inc".toDouble(),
                height ="$_height",
                ft = ft.toString(),
               
                inc = inc.toString()
            )
        }
    }
NewPartizal
  • 604
  • 4
  • 18
  • Does this answer your question? [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – m0skit0 Mar 15 '23 at 18:16

2 Answers2

1

I think what may be wrong with your code is that you are not using rounding functions at some points.

For instance line val totalInches = _height / 2.54 should be modified to val totalInches = Math.round(_height / 2.54) and val totalInches = ft * 12 + inc to val totalInches = Math.round(ft * 12 + inc)

It is also important to use the math operators consistently, if you are using floor() in one function and ceil() in another you will also get inconsistent conversions.

Luis Pascual
  • 256
  • 1
  • 3
  • 13
1

.toInt() always rounds down. You're getting rounding error when you go back and forth.

Calculating the conversion is so trivial, I suggest you keep it always stored in a single Double property and calculate the value for the current units on demand when the Composable wants it.

So, in your view state class, I suggest removing the properties for _height, ft, inc, ftHeight, etc. Just replace all of that with two properties, and you can add the other properties you need as ones that are calculated on demand:

enum class ViewStateHeightAppearance {
    Cm, Imperial
}

data class ImperialHeight(val feet: Int, val inches: Int) {
  companion object {
    fun fromRawCm(rawCm: Double): ImperialHeight {
      val wholeInches = (rawCmHeight / 2.54).roundToInt()
      return ImperialHeight(wholeInches / 12, wholeInches % 12)
    }
  }
}

data class ViewState(
  //...,
  val rawCmHeight: Double,
  val heightAppearance: ViewStateHeightAppearance
) {

  val cmHeight: Int 
    get() = rawCmHeight.rountToInt()

  val imperialHeight: ImperialHeight 
    get() = ImperialHeight.fromRawCm(rawCmHeight)

  val heightText: String
    get() = when(heightAppearance) {
        ViewStateHeightAppearance.Cm -> "$cmHeight cm"
        ViewStateHeightAppearance.Imperial -> with(imperialHeight) { "$feet feet, $inches inches" }
    }
}

I use an enum for the type of units so you can easily add other formats later without increasing code complexity.

With the above, you only have to change the heightAppearance when the user clicks a button. You only have to change rawCmHeight when the user enters different values. No extra step of doing a conversion every time the units change, and you never lose accuracy from changing the conversion back and forth because that doesn't change the stored value in rawCmHeight.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154