首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何将JavaScript中的数字划分为任意精度(例如小数点28位)

如何将JavaScript中的数字划分为任意精度(例如小数点28位)
EN

Stack Overflow用户
提问于 2022-09-23 20:00:16
回答 1查看 62关注 0票数 2

我知道https://floating-point-gui.de/,也知道有许多库可以帮助处理大数,但令人惊讶的是,在除法运算的结果中,我没有找到任何处理小数点以上19位的东西。

我花了几个小时尝试一些库,如精确数学decimal.jsbignumber.js和其他库。

您将如何处理下面用⭐标记的情况?

代码语言:javascript
复制
// https://jestjs.io/docs/getting-started#using-typescript

import exactMath from 'exact-math'; // https://www.npmjs.com/package/exact-math
import { Decimal } from 'decimal.js'; // https://github.com/MikeMcl/decimal.js

const testCases = [
  // The following cases work in exact-math but fail in Decimal.js:
  '9999513263875304671192000009',
  '4513263875304671192000009',
  '530467119.530467119',
  // The following cases fail in both Decimal.js and exact-math:
  '1.1998679030467029262556391239', // ⭐ exact-math rounds these 28 decimal places to 17: "1.1998679030467029263000000000"
];

describe('decimals.js', () => {
  testCases.forEach((testCase) => {
    test(testCase, () => {
      expect(new Decimal(testCase).div(new Decimal(1)).toFixed(28)).toBe(testCase); // Dividing by 1 (very simple!)
    });
  });
});

describe('exact-math', () => {
  testCases.forEach((testCase) => {
    test(testCase, () => {
      expect(exactMath.div(testCase, 1, { returnString: true })).toBe(testCase); // Dividing by 1 (very simple!)
    });
  });
});
EN

回答 1

Stack Overflow用户

发布于 2022-09-23 20:46:52

上面的一些测试用例没有意义(因为我不应该期望输出与输入相等,因为我使用的是.toFixed()。

然后,真正的答案是@James:使用建议选项:https://www.npmjs.com/package/exact-math#the-config-maxdecimal-property-usage或decimal.js中的https://mikemcl.github.io/decimal.js/#precision

请参见下面使用⭐的行。

代码语言:javascript
复制
import exactMath from 'exact-math'; // https://www.npmjs.com/package/exact-math
import { Decimal } from 'decimal.js'; // https://github.com/MikeMcl/decimal.js

/**
 *
 * @param amount {string}
 * @param decimals {number} e.g. 6 would return 6 decimal places like 0.000000
 * @param locale {string} e.g. 'en-US' or 'de-DE'
 * @returns {string} e.g. 1,000.000000
 */
export function getLocaleStringToDecimals(amount: string, decimals: any, locale?: string): string {
  // Thanks to https://stackoverflow.com/a/68906367/ because https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toLocaleString would not work for huge numbers or numbers with many decimal places.

  const decimalFormat = new Intl.NumberFormat(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1 });
  const decimalFullString = '1.1';
  const decimalFullNumber = Number.parseFloat(decimalFullString);
  const decimalChar = decimalFormat.format(decimalFullNumber).charAt(1); // e.g. '.' or ','
  const fixed = new Decimal(amount).toFixed(decimals);
  const [mainString, decimalString] = fixed.split('.'); // ['321321321321321321', '357' | '998']
  const mainFormat = new Intl.NumberFormat(locale, { minimumFractionDigits: 0 });
  let mainBigInt = BigInt(mainString); // 321321321321321321n
  const mainFinal = mainFormat.format(mainBigInt); // '321.321.321.321.321.321' | '321.321.321.321.321.322'
  const decimalFinal = typeof decimalString !== 'undefined' ? `${decimalChar}${decimalString}` : ''; // '.357' | '.998'
  const amountFinal = `${mainFinal}${decimalFinal}`; // '321.321.321.321.321.321,36' | '321.321.321.321.321.322,00'
  // console.log({
  //   amount,
  //   fixed,
  //   mainString,
  //   decimalString,
  //   'decimalString.length': decimalString ? decimalString.length : undefined,
  //   decimalFormat,
  //   decimalFinal,
  //   mainFormat,
  //   mainBigInt,
  //   mainFinal,
  //   amountFinal,
  // });
  return amountFinal;
}

/**
 *
 * @param amount {string}
 * @param decimals {number} e.g. 6 would return 6 decimal places like 0.000000
 * @param divisorPower {number} e.g. 0 for yocto, 24 for [base], 27 for kilo, etc 
 * @param locale {string} e.g. 'en-US' or 'de-DE'
 * @returns {string} e.g. 1,000.000000
 */
export function round(amount: string, decimals = 0, divisorPower = 0, locale?: string): string {
  if (divisorPower < 0) {
    throw new Error('divisorPower must be >= 0');
  }
  const amountCleaned = amount.replaceAll('_', '');
  const divisor = Math.pow(10, divisorPower);
  const value: string = exactMath.div(amountCleaned, divisor, { returnString: true, maxDecimal: amount.length + decimals }); // ⭐ https://www.npmjs.com/package/exact-math#the-config-maxdecimal-property-usage
  // console.log(`round(${amount}, decimals = ${decimals}, divisorPower = ${divisorPower}) = ${value}`, divisor);
  const localeString = getLocaleStringToDecimals(value, decimals, locale);
  return localeString;
}

我的测试用例现在通过了。谢谢!

请参阅https://stackoverflow.com/a/68906367/,了解是什么激发了我的代码。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73832451

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档