HarmonyOS 鸿蒙Next数学类上架项目解析8-小数转分数算法

HarmonyOS 鸿蒙Next数学类上架项目解析8-小数转分数算法 在开发鸿蒙数学计算应用时,需要将计算结果从小数形式转换为分数形式显示。要求:

  1. 能够将有限小数转换为最简分数
  2. 支持设置精度容差
  3. 处理特殊情况(整数、负数、无限循环小数近似)

如何用ArkTS实现一个小数转分数的算法?

4 回复

小数转分数的核心算法是连分数展开法(Continued Fraction),它能够找到最接近给定小数的最简分数。

算法原理:

1. 取小数的整数部分

2. 取倒数后重复步骤1

3. 根据展开序列构造分数

1. 定义数据模型

// 分数接口
interface Fraction {
  numerator: number      // 分子
  denominator: number    // 分母
  isNegative: boolean    // 是否为负数
}
// 转换结果接口
interface FractionResult {
  fraction: Fraction
  display: string        // 显示字符串
  decimal: number        // 原始小数
  error: number          // 误差
  isValid: boolean
  errorMsg?: string
}

2. 核心转换函数

/ 计算最大公约数(欧几里得算法) @param a 整数a @param b 整数b @returns 最大公约数 /

function gcd(a: number, b: number): number {
  a = Math.abs(Math.round(a))
  b = Math.abs(Math.round(b))

  while (b !== 0) {
    const temp: number = b
    b = a % b
    a = temp
  }
  return a
}

/ 约分(化简分数) @param numerator 分子 @param denominator 分母 @returns 最简分数 /

function simplifyFraction(numerator: number, denominator: number): Fraction {
  const isNegative: boolean = (numerator < 0) !== (denominator < 0)
  numerator = Math.abs(numerator)
  denominator = Math.abs(denominator)

  const divisor: number = gcd(numerator, denominator)

  return {
    numerator: numerator / divisor,
    denominator: denominator / divisor,
    isNegative: isNegative
  }
}

/ 使用连分数算法将小数转换为分数 @param decimal 小数 @param tolerance 精度容差(默认1e-10) @param maxIterations 最大迭代次数(默认20) @returns 转换结果 /

function decimalToFraction(
  decimal: number,
  tolerance: number = 1e-10,
  maxIterations: number = 20
): FractionResult {
  // 验证输入
  if (Number.isNaN(decimal) || !Number.isFinite(decimal)) {
    return {
      fraction: { numerator: 0, denominator: 1, isNegative: false },
      display: 'NaN',
      decimal: decimal,
      error: 0,
      isValid: false,
      errorMsg: '请输入有效数字'
    }
  }

  // 处理负数
  const isNegative: boolean = decimal < 0
  let value: number = Math.abs(decimal)

  // 处理整数
  if (Math.abs(value - Math.round(value)) < tolerance) {
    const intValue: number = Math.round(value)
    return {
      fraction: { numerator: intValue, denominator: 1, isNegative: isNegative },
      display: isNegative ? `-${intValue}` : `${intValue}`,
      decimal: decimal,
      error: 0,
      isValid: true
    }
  }

  // 连分数展开
  let numerator: number = 1
  let denominator: number = 0
  let prevNumerator: number = 0
  let prevDenominator: number = 1
  let remainder: number = value

  for (let i = 0; i < maxIterations; i++) {
    const intPart: number = Math.floor(remainder)

    // 更新分子分母
    const tempNumerator: number = numerator
    numerator = intPart * numerator + prevNumerator
    prevNumerator = tempNumerator

    const tempDenominator: number = denominator
    denominator = intPart * denominator + prevDenominator
    prevDenominator = tempDenominator

    // 检查精度
    const currentValue: number = numerator / denominator
    if (Math.abs(currentValue - value) < tolerance) {
      break
    }

    // 计算下一个余数
    const fractionalPart: number = remainder - intPart
    if (fractionalPart < tolerance) {
      break
    }
    remainder = 1 / fractionalPart

    // 防止溢出
    if (!Number.isFinite(remainder)) {
      break
    }
  }

  // 约分
  const fraction: Fraction = simplifyFraction(numerator, denominator)
  fraction.isNegative = isNegative

  // 生成显示字符串
  let display: string
  if (fraction.denominator === 1) {
    display = isNegative ? `-${fraction.numerator}` : `${fraction.numerator}`
  } else {
    display = isNegative
      ? `-${fraction.numerator}/${fraction.denominator}`
      : `${fraction.numerator}/${fraction.denominator}`
  }

  // 计算误差
  const resultValue: number = fraction.numerator / fraction.denominator
  const error: number = Math.abs(resultValue - value)

  return {
    fraction: fraction,
    display: display,
    decimal: decimal,
    error: error,
    isValid: true
  }
}

3. 常见分数识别

/ 识别常见分数(如 1/2, 1/3, 1/4 等) @param decimal 小数 @param tolerance 容差 @returns 识别结果或null /

function recognizeCommonFraction(decimal: number, tolerance: number = 1e-6): string | null {
  const isNegative: boolean = decimal < 0
  const value: number = Math.abs(decimal)

  // 常见分数表
  const commonFractions: Array<[number, string]> = [
    [0.5, '1/2'],
    [0.333333, '1/3'],
    [0.666667, '2/3'],
    [0.25, '1/4'],
    [0.75, '3/4'],
    [0.2, '1/5'],
    [0.4, '2/5'],
    [0.6, '3/5'],
    [0.8, '4/5'],
    [0.166667, '1/6'],
    [0.833333, '5/6'],
    [0.142857, '1/7'],
    [0.125, '1/8'],
    [0.375, '3/8'],
    [0.625, '5/8'],
    [0.875, '7/8'],
    [0.1, '1/10'],
    [0.3, '3/10'],
    [0.7, '7/10'],
    [0.9, '9/10']
  ]

  for (const [fracValue, fracStr] of commonFractions) {
    if (Math.abs(value - fracValue) < tolerance) {
      return isNegative ? `-${fracStr}` : fracStr
    }
  }

  return null
}

/
  智能转换(优先识别常见分数)
/
function smartDecimalToFraction(decimal: number): FractionResult {
  // 先尝试识别常见分数
  const common = recognizeCommonFraction(decimal)
  if (common) {
    const result = decimalToFraction(decimal)
    result.display = common
    return result
  }

  return decimalToFraction(decimal)
}

4. 完整UI组件实现

@Entry
@Component
struct FractionConverter {
  @State decimalInput: string = '0.75'
  @State toleranceInput: string = '0.0000001'
  @State result: FractionResult | null = null

  build() {
    Column() {
      Text('小数转分数')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 10 })

      Text('连分数展开算法')
        .fontSize(14)
        .fontColor('#666666')
        .margin({ bottom: 20 })

      // 小数输入
      Row() {
        Text('小数:')
        TextInput({ text: this.decimalInput })
          .width(150)
          .type(InputType.Number)
          .onChange((value: string) => { this.decimalInput = value })
      }
      .margin({ bottom: 10 })

      // 精度输入
      Row() {
        Text('精度:')
        TextInput({ text: this.toleranceInput })
          .width(150)
          .onChange((value: string) => { this.toleranceInput = value })
      }
      .margin({ bottom: 20 })

      // 转换按钮
      Button('转换为分数')
        .onClick(() => this.handleConvert())
        .width(200)
        .margin({ bottom: 20 })

      // 结果显示
      if (this.result) {
        Column() {
          if (this.result.isValid) {
            Text(`${this.result.decimal}`)
              .fontSize(18)
              .margin({ bottom: 8 })
            Text('=')
              .fontSize(24)
              .margin({ bottom: 8 })
            Text(this.result.display)
              .fontSize(32)
              .fontWeight(FontWeight.Bold)
              .fontColor('#007AFF')
              .margin({ bottom: 12 })
            
            Text(`分子:${this.result.fraction.numerator}`)
              .fontSize(14)
              .fontColor('#666666')
            Text(`分母:${this.result.fraction.denominator}`)
              .fontSize(14)
              .fontColor('#666666')
            Text(`误差:${this.result.error.toExponential(4)}`)
              .fontSize(12)
              .fontColor('#999999')
              .margin({ top: 8 })
          } else {
            Text(this.result.errorMsg || '转换失败')
              .fontColor(Color.Red)
          }
        }
        .padding(20)
        .backgroundColor('#f5f5f5')
        .borderRadius(8)
      }

      // 常见分数参考
      Column() {
        Text('常见分数参考:')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 8 })
        Text('1/2 = 0.5     1/3 ≈ 0.333     1/4 = 0.25')
          .fontSize(12)
          .fontColor('#666666')
        Text('1/5 = 0.2     1/6 ≈ 0.167     1/8 = 0.125')
          .fontSize(12)
          .fontColor('#666666')
      }
      .margin({ top: 30 })
      .padding(16)
      .backgroundColor('#f0f0f0')
      .borderRadius(8)
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }

  handleConvert(): void {
    const decimal: number = parseFloat(this.decimalInput)
    const tolerance: number = parseFloat(this.toleranceInput)
    this.result = decimalToFraction(decimal, tolerance)
  }
}

5. 使用示例

// 示例1:简单分数 const result1 = decimalToFraction(0.5) // { display: ‘1/2’, fraction: { numerator: 1, denominator: 2 } }

// 示例2:循环小数 const result2 = decimalToFraction(0.333333) // { display: ‘1/3’, fraction: { numerator: 1, denominator: 3 } }

// 示例3:复杂小数 const result3 = decimalToFraction(0.142857) // { display: ‘1/7’, fraction: { numerator: 1, denominator: 7 } }

// 示例4:负数 const result4 = decimalToFraction(-0.75) // { display: ‘-3/4’, fraction: { numerator: 3, denominator: 4, isNegative: true } }

// 示例5:无理数近似(√2 ≈ 1.41421356) const result5 = decimalToFraction(1.41421356, 1e-6) // { display: ‘1393/985’, … } 这是√2的一个有理逼近

总结

使用ArkTS实现小数转分数的关键点:

  1. 连分数算法是最优的小数转分数方法,能找到最简分数
  2. 需要设置合适的精度容差,太小可能导致分母过大
  3. 对于常见分数(1/2, 1/3等)可以直接识别,提升用户体验
  4. 必须处理负数、整数、无穷大等特殊情况
  5. 约分使用欧几里得算法求最大公约数
  6. 项目链接:https://gitee.com/solgull/math-fbox

更多关于HarmonyOS 鸿蒙Next数学类上架项目解析8-小数转分数算法的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


能不能把所有问题发一个帖子啊 发了好几个了

鸿蒙Next数学类库中,小数转分数算法主要基于有理数逼近原理。核心函数通过计算分子分母的最大公约数实现约分,将有限小数或循环小数转换为最简分数形式。算法内部处理了浮点数精度问题,确保转换准确性。开发者调用相关API时,需传入目标小数,库函数将返回对应的分数对象。该功能封装在系统数学模块中,无需额外实现底层逻辑。

在HarmonyOS Next中,使用ArkTS实现小数转分数算法,可以结合数学原理和ArkTS的数值处理能力。以下是一个核心实现方案:

1. 核心算法原理 对于有限小数,转换基于:小数位数n确定分母为10ⁿ,分子为整数部分×10ⁿ+小数部分整数化,最后约分。对于需要精度容差的无限循环小数近似,通常采用连分数法或基于容差的最大分母限制法。

2. ArkTS实现示例

// 最大迭代次数和精度容差控制
const MAX_ITERATIONS = 20;
const DEFAULT_TOLERANCE = 1.0e-6;

function decimalToFraction(decimal: number, tolerance: number = DEFAULT_TOLERANCE): [number, number] {
  // 处理整数和负数
  if (Number.isInteger(decimal)) {
    return [decimal, 1];
  }
  const isNegative = decimal < 0;
  let num = Math.abs(decimal);

  // 处理精度容差下的近似
  let numerator = 1;
  let denominator = 0;
  let prevNumerator = 0;
  let prevDenominator = 1;

  for (let i = 0; i < MAX_ITERATIONS; i++) {
    const integerPart = Math.floor(num);
    const tempNumerator = numerator;
    const tempDenominator = denominator;

    numerator = integerPart * numerator + prevNumerator;
    denominator = integerPart * denominator + prevDenominator;

    prevNumerator = tempNumerator;
    prevDenominator = tempDenominator;

    const approx = numerator / denominator;
    if (Math.abs(approx - Math.abs(decimal)) <= tolerance) {
      break;
    }

    if (num - integerPart <= tolerance) {
      break;
    }
    num = 1 / (num - integerPart);
  }

  // 约分到最简
  const gcdValue = gcd(numerator, denominator);
  let resultNumerator = numerator / gcdValue;
  let resultDenominator = denominator / gcdValue;

  // 恢复负数
  if (isNegative) {
    resultNumerator = -resultNumerator;
  }

  return [resultNumerator, resultDenominator];
}

// 辅助函数:计算最大公约数用于约分
function gcd(a: number, b: number): number {
  a = Math.floor(Math.abs(a));
  b = Math.floor(Math.abs(b));
  while (b !== 0) {
    const temp = b;
    b = a % b;
    a = temp;
  }
  return a;
}

3. 关键点解析

  • 有限小数:直接通过小数位数确定分母,结合gcd函数约分。
  • 精度容差:通过tolerance参数控制近似程度,循环中判断当前近似值与原始值的误差。
  • 特殊情况
    • 整数直接返回分母为1。
    • 负数通过标志位处理,最终结果恢复符号。
    • 循环小数通过迭代连分数逼近,在容差范围内停止。

4. 使用示例

// 有限小数
console.log(decimalToFraction(0.75)); // 输出: [3, 4]

// 设置容差
console.log(decimalToFraction(0.333333, 1e-4)); // 近似为 1/3

// 负数
console.log(decimalToFraction(-2.5)); // 输出: [-5, 2]

此实现平衡了精确度和性能,通过容差控制适合多数数学计算场景。对于极高精度需求,可调整MAX_ITERATIONStolerance参数。

回到顶部