Home > Blockchain >  Precision issue when computing weight range given BMI range in Swift
Precision issue when computing weight range given BMI range in Swift

Time:03-23

I am making a BMI app. I have this code:

enum MathHelper {
  static func computeBMI(kg: Decimal, cm: Decimal) -> Decimal {
    return kg / cm / cm * 10_000
  }

  static func computeWeight(cm: Decimal, bmi: Decimal) -> Decimal {
    return bmi * cm * cm / 10_000
  }
  
  static func computeWeightRange(
    cm: Decimal,
    bmiRange: (from: Decimal, to: Decimal))
    -> (from: Decimal, to: Decimal)
  {
    let fromWeight = computeWeight(cm: cm, bmi: bmiRange.from)
    let toWeight = computeWeight(cm: cm, bmi: bmiRange.to)
    return (from: fromWeight, to: toWeight)
  }
}

Now I print the from/to pair, with 1 fraction length.

print(Decimal.FormatStyle.number.precision(.fractionLength(1)).format(from))
print(Decimal.FormatStyle.number.precision(.fractionLength(1)).format(to))

When I run this code with height 180 cm, BMI range from 18.5 to 24.9 (which is the BMI range for healthy weight), I got 59.9 - 80.7 weight range.

But this is not correct. The weight range should be 59.7-80.8. Because if I put in 59.7 and 80.8 into the computeBMI function and print result in 1 fraction length, it's still within range 18.5-24.9.

Feel free to try it out. This is minimal reproducible code:


enum MathHelper {
  static func computeBMI(kg: Decimal, cm: Decimal) -> Decimal {
    return kg / cm / cm * 10_000
  }

  static func computeWeight(cm: Decimal, bmi: Decimal) -> Decimal {
    return bmi * cm * cm / 10_000
  }
  
  static func computeWeightRange(
    cm: Decimal,
    bmiRange: (from: Decimal, to: Decimal))
    -> (from: Decimal, to: Decimal)
  {
    let fromWeight = computeWeight(cm: cm, bmi: bmiRange.from)
    let toWeight = computeWeight(cm: cm, bmi: bmiRange.to)
    return (from: fromWeight, to: toWeight)
  }
}


func format(_ decimal: Decimal) -> String {
  return Decimal.FormatStyle.number.precision(.fractionLength(1)).format(decimal)
}


let height: Decimal = 180
let upperBMI: Decimal = 24.9
let computedUpperWeight = format(MathHelper.computeWeight(cm: height, bmi: upperBMI))
// this prints out 80.7, which is incorrect, because 80.8 is the correct answer (see below)
print("computed upper weight: \(computedUpperWeight)")

let correctUpperWeight: Decimal = 80.8
let bmiFromCorrectUpperWeight = format(MathHelper.computeBMI(kg: correctUpperWeight, cm: height))
// This prints out 24.9, which is still within the uppoer bound
print("BMI from correct upper weight \(bmiFromCorrectUpperWeight)")

CodePudding user response:

While flanker's argument is valid, BMI values are typically represented with 1 precision in real world application.

My recommendation is to adjust your BMI chart range by 0.05.

For example, with these 2 ranges (i quote from wikipedia).

  • Normal: 18.5-24.9
  • Overweight 25-29.9

You can represent your range in your code like this:

  • Normal: 18.45-24.95
  • Overweight 24.95-29.95

This should solve your problem.

CodePudding user response:

The problem is in the accuracy of the value you are displaying, not the calculation. The difference in BMI between 80.7 and 80.8 kg is lost in the rounding.

The upper BMI limit for 180cm is 80.676 kg, which then rounded do 1dp is 80.7 kg.

Working it back the other way to calculate the BMI from 180cm with a weight of 80.8 kg provides a BMI of 24.938... At this degree of accuracy this is obviously greater than the healthy BMI range's upper bound of 24.9, but when rounded down to 1 dp it appears to be within the range due to the degree of precision lost by the rounding.

The maths here is correct. The issue becomes how you represent this in the UI so the user understands such things. This is a question of presentation logic, not maths, which is a different one to the one asked.

  • Related