2023-11-28 flutter库 number_precision中的NP.times实现原理

本文主要关注NP.times(0.362, 100);这个函数的实现原理:

number_precision的功能列举如下:

import 'package:number_precision/number_precision.dart';

NP.strip(0.09999999999999998); // = 0.1
NP.plus(0.1, 0.2);             // = 0.3, not 0.30000000000000004
NP.plus(2.3, 2.4);             // = 4.7, not 4.699999999999999
NP.minus(1.0, 0.9);            // = 0.1, not 0.09999999999999998
NP.times(3, 0.3);              // = 0.9, not 0.8999999999999999
NP.times(0.362, 100);          // = 36.2, not 36.199999999999996
NP.divide(1.21, 1.1);          // = 1.1, not 1.0999999999999999
NP.round(0.105, 2);            // = 0.11, not 0.1

下面是整个源码实现:

import 'dart:math';

/// The smallest possible value of an int within 64 bits.
const int intMinValue = -9007199254740991;

/// The biggest possible value of an int within 64 bits.
const int inMaxValue = 9007199254740991;

class NP {
  static bool _boundaryCheckingState = true;

  /// 字符串转为[num]类型
  /// [number] 数据
  static num parseNum(dynamic number) {
    if (number is num) {
      return number;
    } else if (number is String) {
      return num.parse(number);
    } else {
      throw FormatException('$number is not of type num and String');
    }
  }

  /// 把错误的数据转正
  /// strip(0.09999999999999998)=0.1
  /// [number] 数据 [precision] 截取小数位
  static num strip(dynamic number, {int precision = 14}) {
    return num.parse(parseNum(number).toStringAsFixed(precision));
  }

  /// 返回小数的位数
  /// [number] 数据
  static num digitLength(dynamic number) {
    final eSplit = parseNum(number).toString().toLowerCase().split('e');
    final digit = eSplit[0].split('.');
    final len = (digit.length == 2 ? digit[1].length : 0) -
        (eSplit.length == 2 ? int.parse(eSplit[1]) : 0);
    return len > 0 ? len : 0;
  }

  /// 把小数转成整数,支持科学计数法。如果是小数则放大成整数
  /// [number] 数据
  static num float2Fixed(dynamic number) {
    final dLen = digitLength(number);
    if (dLen <= 20) {
      if (number is String) {
        if (number.toLowerCase().indexOf('e') == -1) {
          return num.parse(number.replaceAll('.', ''));
        }
        return num.parse(num.parse(number)
            .toStringAsFixed(dLen as int)
            .replaceAll(dLen == 0 ? '' : '.', ''));
      } else if (number is num) {
        return num.parse(number
            .toStringAsFixed(dLen as int)
            .replaceAll(dLen == 0 ? '' : '.', ''));
      }

      throw FormatException('$number is not of type num and String');
    }
    throw Exception(
        '$number is beyond boundary when transfer to integer, the results may not be accurate');
  }

  /// 检测数字是否越界,如果越界给出提示
  /// [number] 数据
  static void checkBoundary(dynamic number) {
    if (_boundaryCheckingState) {
      if (number > inMaxValue || number < intMinValue) {
        throw Exception(
            '$number is beyond boundary when transfer to integer, the results may not be accurate');
      }
    }
  }

  /// 精确乘法
  /// [num1] 左操作数 [num2]右操作数
  /// [others] 更多操作数使用数组传递
  ///
  /// 譬如 times(1, 2, [22,33])
  static num times(dynamic num1, dynamic num2, [List<dynamic>? others]) {
    if (others != null) {
      return times(times(num1, num2), others[0],
          others.length >= 2 ? others.sublist(1) : null);
    }
    num num1Changed = float2Fixed(num1);
    num num2Changed = float2Fixed(num2);
    num baseNum = digitLength(num1) + digitLength(num2);
    dynamic leftValue = num1Changed * num2Changed;

    checkBoundary(leftValue);

    return NP.strip(leftValue / pow(10, baseNum));
  }

  /// 精确加法
  static num plus(dynamic num1, dynamic num2, [List<dynamic>? others]) {
    if (others != null) {
      return plus(plus(num1, num2), others[0],
          others.length >= 2 ? others.sublist(1) : null);
    }
    num baseNum = pow(10, max(digitLength(num1), digitLength(num2)));
    return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
  }

  /// 精确减法
  static num minus(dynamic num1, dynamic num2, [List<dynamic>? others]) {
    if (others != null) {
      return minus(minus(num1, num2), others[0],
          others.length >= 2 ? others.sublist(1) : null);
    }
    num baseNum = pow(10, max(digitLength(num1), digitLength(num2)));

    return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;
  }

  /// 精确除法
  static num divide(dynamic num1, dynamic num2, [List<dynamic>? others]) {
    if (others != null) {
      return divide(divide(num1, num2), others[0],
          others.length >= 2 ? others.sublist(1) : null);
    }
    num num1Changed = float2Fixed(num1);
    num num2Changed = float2Fixed(num2);
    checkBoundary(num1Changed);
    checkBoundary(num2Changed);
    return times(num1Changed / num2Changed,
        (pow(10, digitLength(num2) - digitLength(num1))));
  }

  /// 四舍五入
  static num round(dynamic number, int ratio) {
    num base = pow(10, ratio);
    return divide((times(number, base).round()), base);
  }

  /// 是否进行边界检查,默认开启
  /// [flag] 标记开关,true 为开启,false 为关闭,默认为 true
  static void enableBoundaryChecking([flag = true]) {
    _boundaryCheckingState = flag;
  }
}

在Flutter中,pow函数是Dart数学库中的一个函数,用于计算一个数字的指数幂。具体而言,pow函数的定义如下:

double pow(num x, num exponent)

其中,x是底数,exponent是指数。这函数返回x的exponent次方的结果。

例如,如果你想计算2的3次方,可以这样使用:

import 'dart:math';

void main() {
  double result = pow(2, 3);
  print(result);  // 输出 8.0
}

这段代码中,pow(2, 3)的结果是2的3次方,即8.0。需要注意的是,pow函数返回的结果是double类型。

回到:

  static num times(dynamic num1, dynamic num2, [List<dynamic>? others]) {
    if (others != null) {
      return times(times(num1, num2), others[0],
          others.length >= 2 ? others.sublist(1) : null);
    }
    num num1Changed = float2Fixed(num1); //小数转化为整数,比如1.234567890 则为1234567890
    num num2Changed = float2Fixed(num2);//跟上相同
    num baseNum = digitLength(num1) + digitLength(num2);//小数点后面的位数,1.234567890 位数为9
    dynamic leftValue = num1Changed * num2Changed; //整数想乘

    checkBoundary(leftValue);

    return NP.strip(leftValue / pow(10, baseNum));//结果除以 10的baseNum次幂。即还原之前放大的倍数。然后转化为num.parse输出为num类型
  }

分析逻辑步骤:

  • 如果others不为null,递归调用times函数,将结果与others中的元素相乘,直到处理完所有元素。
  • 将输入的num1和num2转换为整数形式,使用float2Fixed函数。
  • 计算乘积的基数baseNum,即两个操作数小数位数之和,用于后续处理。
  • 计算整数形式的乘积leftValue。
  • 调用checkBoundary函数检查乘积是否越界。
  • 将乘积还原成小数形式,使用NP.strip函数,并返回结果。

总体而言,times函数的主要逻辑是将两个数转换为整数形式,进行整数乘法,然后将结果还原成小数形式,同时处理了越界的情况。

好的,到此分析完毕。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容