区块链钱包开发,第三周总结 (货币精度计算 超大数运算)

这周主要做了 ETH钱包:(1)钱包列表展示钱包价值(2)在钱包内发起一个Transation交易 (3)获取交易详情

当前热钱包部分, 对于以上三个功能最大的需求功能,最大阻碍是精度问题和超大数的的基本运算

一.钱包价值展示

参考imToken, imToken主做ETH钱包三年多,相当专业,有太多的地方值得我们学习。

首页部分头部是钱包内主币和代币换算成实体货币数量的总价值,列表部分是当前钱包内主币和选中代币集合,展示了币的图标、名称、数量、兑换成实体货币价值。

关键是数量这里:
ETH 有很多单位,基本使用统计如下:

单位Unit 以最小单位Wei为基准的进制(Wei  数量) 使用频率
Wei 1           wei 经常
KWei 10 * 3    wei  
MWei 10 * 6    wei  
GWei 10 * 9    wei 经常
microether 10 * 12  wei  
milliether 10 * 15  wei  
ether 10 * 18  wei 经常

 

 

 

 

 

 

二 需要运算的地方:

发起一笔转账时的进制转换,和gas 费用。

  Gas limit 是用户愿意为执行某个操作或确认交易支付的最大Gas量(最少21,000) 单位个(即对应的是ETH的个数)

  计算 花费应该   gas fee = Gas Limit * Gas Price

  这个是给矿工的佣金

  第一步就是把gas price 从Gwei 转为 ETH 再 * limit

  Gas Limit*Gas Price

 eg:  1Gwei≈0.00000002 ETH,所以佣金最少为0.00000002*21000=0.00042ETH

 

 

(1)用户输入转账ETH个数 单位:ether    

和服务端约定上传一律用最小单位,这里需要做一次进制转换  即:1 ether = 10 * 18 wei

如果交易稍大就会超过 iOS 中精度了,更别提运算了。因为有Web3 ,提供 BigDecimal方案处理超大数的精度处理,iOS 这边,完全可以自己写超大数的加减乘除,也可以选择一些成熟的第三 ,这里我使用了一个框架web3swift

       web3swift,完全支持在iOS 客户端上实施冷钱包,并发起交易的整个过程 通过  Infura 节点(测试/主网)。具体参考(1)上介绍

      所以,上一篇文章,我使用trust keystore 能完成创建钱包,导入钱包,校验钱包的操作。今天换成web3swift,主要是符合我的需求和持续化的迭代设计。

     至此,超大数,精度浮点数运算 进制转换使用swift 框架 web3swift来解决

    使用举例:

//
//  FIREnterUnitManager.swift
//  AkeyWallet
//
//  Created by HF on 2018/7/14.
//  Copyright © 2018年 Fir.im. All rights reserved.
//

import Foundation
import BigInt
import web3swift

@objc public class FIREnterUnitManager:NSObject {
 
    //从ETH 转换为 Wei
    public static func getWeiBigStringFrom(ethString:String) throws -> String {
        let eth = Web3.Utils.parseToBigUInt(ethString, units: .eth);//当前是eth
        if (nil == eth) {
            return ""
        } else {
            let balString =  Web3.Utils.formatToEthereumUnits(eth!, toUnits: .wei, decimals: 0)
            return balString!
        }
    }
    //从wei 到 eth
    public static func getETHFrom(weiString:String) throws -> String {
        let weiBig = BigUInt(weiString);
        let balString =  Web3.Utils.formatToEthereumUnits(weiBig!, toUnits: .eth, decimals: 8)
        return balString!;
    }//从当前进制到wei
    public static func getWeiFromUnit(unitString:String,decimal:Int) throws -> String {
        let balance = Web3.Utils.parseToBigUInt(unitString, decimals: decimal)
        let balString =  Web3.Utils.formatToEthereumUnits(balance!, toUnits: .wei, decimals: 8)
        return balString!;
    }//wei乘法
    public static func getMultiWei(wei1:String,wei2:String) throws -> String {
        let weiBig1 = BigUInt(wei1);
        let weiBig2 = BigUInt(wei2);
        let wei = weiBig1! * weiBig2!;
        let balString =  Web3.Utils.formatToEthereumUnits(wei, toUnits: .wei, decimals: 8)
        return balString!;
    }
    //wei加法
    public func getSumWei(wei1:String,wei2:String) throws -> String {
        let weiBig1 = BigUInt(wei1);
        let weiBig2 = BigUInt(wei2);
        let wei = weiBig1! + weiBig2!;
        let balString =  Web3.Utils.formatToEthereumUnits(wei, toUnits: .wei, decimals: 8)
        return balString!;
    }}

(2)有一些非超大数,比如一些显示,虚拟币、实体币,求和,汇率计算什么的,也可以选择上面的方法算,但是我的风格是按需处理,不是超大数,就是正常的金融数据运算,自己写了一个工具类。

      正常的浮点经度进行金融数据运算,会丢精度。苹果针对浮点型计算时存在精度计算误差的问题,提供NSDecimalNumber的计算类

      参考(2),自己完善一下边界处理

      NSDecimalNumber 有 NSRoundingMode 类型,我使用四舍五入类型 NSRoundPlain ,请参考者按需处理

//
//  NSDecimalNumber+FIRDeimalTool.h
//  AkeyWallet
//
//  Created by HF on 2018/7/15.
//  Copyright © 2018年 Fir.im. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, calculationType) {
    Add,
    Subtract,
    Multiply,
    Divide
};

@interface NSDecimalNumber (FIRDeimalTool)

//自定义加减乘除
+(NSDecimalNumber *)aDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber1 type:(calculationType)type anotherDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber2 andDecimalNumberHandler:(NSDecimalNumberHandler *)handler;

//小数比较
+(NSComparisonResult)aDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber1 compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber2;

/**
 小数位数保留

 @param str1 小数
 @param scale 保留位数
 @return 结果
 */
+(NSString *)stringWithDecimalNumber:(NSDecimalNumber *)str1 scale:(NSInteger)scale;

extern NSComparisonResult StrNumCompare(id str1,id str2);

extern NSDecimalNumber *handlerDecimalNumber(id strOrNum,NSRoundingMode mode,int scale);


extern NSComparisonResult FIRCompare(id strOrNum1,id strOrNum2);

//加减乘除
extern NSDecimalNumber *FIRAdd(id strOrNum1,id strOrNum2);
extern NSDecimalNumber *FIRSub(id strOrNum1,id strOrNum2);
extern NSDecimalNumber *FIRMul(id strOrNum1,id strOrNum2);
extern NSDecimalNumber *FIRDiv(id strOrNum1,id strOrNum2);
//比大小
extern NSDecimalNumber *FIRMin(id strOrNum1,id strOrNum2);
extern NSDecimalNumber *FIRMax(id strOrNum1,id strOrNum2);
//定义运算结果小数取舍模态
extern NSDecimalNumber *FIRAdd_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale);
extern NSDecimalNumber *FIRSub_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale);
extern NSDecimalNumber *FIRMul_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale);
extern NSDecimalNumber *FIRDiv_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale);
extern NSDecimalNumber *FIRMin_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale);
extern NSDecimalNumber *FIRMax_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale);
/**
 字符串小数舍去末尾0
 
 @param stringOrNumber 字符串小数/Number/decimalNumer
 @return 舍去末尾0
 */
+(NSString *)getStringDecimalWithoutEndZero:(id)stringOrNumber;

@end
//
//  NSDecimalNumber+FIRDeimalTool.m
//  AkeyWallet
//
//  Created by HF on 2018/7/15.
//  Copyright © 2018年 Fir.im. All rights reserved.
//

#import "NSDecimalNumber+FIRDeimalTool.h"

@implementation NSDecimalNumber (FIRDeimalTool)

+(NSDecimalNumber *)aDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber1 type:(calculationType)type anotherDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber2 andDecimalNumberHandler:(NSDecimalNumberHandler *)handler{
    if (!stringOrNumber2 || !stringOrNumber1) {
        NSAssert(NO, @"输入正确类型");
        return nil;
    }
    NSDecimalNumber *one;
    NSDecimalNumber *another;
    NSDecimalNumber *returnNum;
    if ([stringOrNumber1 isKindOfClass:[NSString class]]) {
        one = [NSDecimalNumber decimalNumberWithString:stringOrNumber1];
    }else if([stringOrNumber1 isKindOfClass:[NSDecimalNumber class]]){
        one = stringOrNumber1;
    }else if ([stringOrNumber1 isKindOfClass:[NSNumber class]]){
        one = [NSDecimalNumber decimalNumberWithDecimal:[stringOrNumber1 decimalValue]];
    }else{
        NSAssert(NO, @"输入正确类型");
        return nil;
    }
    
    if ([stringOrNumber2 isKindOfClass:[NSString class]]) {
        another = [NSDecimalNumber decimalNumberWithString:stringOrNumber2];
    }else if([stringOrNumber2 isKindOfClass:[NSDecimalNumber class]]){
        another = stringOrNumber2;
    }else if ([stringOrNumber2 isKindOfClass:[NSNumber class]]){
        another = [NSDecimalNumber decimalNumberWithDecimal:[stringOrNumber2 decimalValue]];
    }else{
        NSAssert(NO, @"输入正确类型");
        return nil;
    }
    //排除NaN
    if (isnan(one.doubleValue) || isinf(one.doubleValue)) one = [NSDecimalNumber decimalNumberWithString:@"0"];
    if (isnan(another.doubleValue) || isinf(another.doubleValue)) another = [NSDecimalNumber decimalNumberWithString:@"0"];
    //
    if (type == Add) {
        returnNum = [one decimalNumberByAdding:another];
    }else if (type == Subtract){
        returnNum  = [one decimalNumberBySubtracting:another];
    }else if (type == Multiply){
        returnNum = [one decimalNumberByMultiplyingBy:another];
    }else if (type == Divide){
        
        if ([NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:another compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:@(0)] == 0) {
            returnNum = nil;
        }else
            returnNum = [one decimalNumberByDividingBy:another];
    }else{
        returnNum = nil;
    }
    if (returnNum) {
        if (handler) {
            return [returnNum decimalNumberByRoundingAccordingToBehavior:handler];
        }else{
            return returnNum;
        }
    }else{
        NSAssert(NO, @"输入正确类型");
        return nil;
    }
}

+(NSComparisonResult)aDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber1 compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:(id)stringOrNumber2{
    if (!stringOrNumber2 || !stringOrNumber1) {
        NSAssert(NO, @"输入正确类型");
        return -404;
    }
    NSDecimalNumber *one;
    NSDecimalNumber *another;
    if ([stringOrNumber1 isKindOfClass:[NSString class]]) {
        one = [NSDecimalNumber decimalNumberWithString:stringOrNumber1];
    }else if([stringOrNumber1 isKindOfClass:[NSDecimalNumber class]]){
        one = stringOrNumber1;
    }else if ([stringOrNumber1 isKindOfClass:[NSNumber class]]){
        one = [NSDecimalNumber decimalNumberWithDecimal:[stringOrNumber1 decimalValue]];
    }else{
        NSAssert(NO, @"输入正确类型");
        return -404;
    }
    
    if ([stringOrNumber2 isKindOfClass:[NSString class]]) {
        another = [NSDecimalNumber decimalNumberWithString:stringOrNumber2];
    }else if([stringOrNumber2 isKindOfClass:[NSDecimalNumber class]]){
        another = stringOrNumber2;
    }else if ([stringOrNumber2 isKindOfClass:[NSNumber class]]){
        another = [NSDecimalNumber decimalNumberWithDecimal:[stringOrNumber2 decimalValue]];
    }else{
        NSAssert(NO, @"输入正确类型");
        return -404;
    }
    //排除NaN
    if (isnan(one.doubleValue) || isinf(one.doubleValue)) one = [NSDecimalNumber decimalNumberWithString:@"0"];
    if (isnan(another.doubleValue) || isinf(another.doubleValue)) another = [NSDecimalNumber decimalNumberWithString:@"0"];
    //
    return [one compare:another];
}

+(NSString *)stringWithDecimalNumber:(NSDecimalNumber *)str1 scale:(NSInteger)scale{
    if (!str1) {
        return @"";
    }
    NSString *str = [NSString stringWithFormat:@"%@",str1];
    if (str && str.length) {
        if ([str rangeOfString:@"."].length == 1) {//有小数点
            NSArray *arr = [str componentsSeparatedByString:@"."];
            if (scale > 0) {
                NSInteger count = [arr[1] length];
                for (NSInteger i = count; i<scale; i++) {
                    str = [str stringByAppendingString:@"0"];
                }
                return str;
            }else{
                return arr[0];
            }
        }else{//没有小数点
            if ([str rangeOfString:@"."].length) {
                return @"";
            }
            if (scale > 0) {
                str = [str stringByAppendingString:@"."];
                for (int i = 0; i<scale; i++) {
                    str = [str stringByAppendingString:@"0"];
                }
                return str;
            }else{
                return str;
            }
        }
    }else{
        return @"";
    }
}

NSComparisonResult StrNumCompare(id str1,id str2){
    return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:str1 compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:str2];
}

NSDecimalNumber *handlerDecimalNumber(id strOrNum,NSRoundingMode mode,int scale){
    if (!strOrNum || strOrNum == nil) {
        NSLog(@"输入正确类型");
        return nil;
    }else{
        NSDecimalNumber *one;
        if ([strOrNum isKindOfClass:[NSString class]]) {
            one = [NSDecimalNumber decimalNumberWithString:strOrNum];
        }else if([strOrNum isKindOfClass:[NSDecimalNumber class]]){
            one = strOrNum;
        }else if ([strOrNum isKindOfClass:[NSNumber class]]){
            one = [NSDecimalNumber decimalNumberWithDecimal:[strOrNum decimalValue]];
        }else{
            NSLog(@"输入正确的类型");
            return nil;
        }
        //排除NaN
        if (isnan(one.doubleValue) || isinf(one.doubleValue)) one = [NSDecimalNumber decimalNumberWithString:@"0"];
        //
        NSDecimalNumberHandler *handler = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:mode scale:scale raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:NO];
        return  [one decimalNumberByRoundingAccordingToBehavior:handler];
    }
}


NSDecimalNumber *FIRAdd(id strOrNum1,id strOrNum2){
    return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 type:Add anotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2 andDecimalNumberHandler:nil];
}

NSDecimalNumber *FIRSub(id strOrNum1,id strOrNum2){
    return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 type:Subtract anotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2 andDecimalNumberHandler:nil];
}
NSDecimalNumber *FIRMul(id strOrNum1,id strOrNum2){
    return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 type:Multiply anotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2 andDecimalNumberHandler:nil];
}

NSDecimalNumber *FIRDiv(id strOrNum1,id strOrNum2){
    return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 type:Divide anotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2 andDecimalNumberHandler:nil];
}

NSComparisonResult FIRCompare(id strOrNum1,id strOrNum2){
    return [NSDecimalNumber aDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum1 compareAnotherDecimalNumberWithStringOrNumberOrDecimalNumber:strOrNum2];
}

NSDecimalNumber *FIRMin(id strOrNum1,id strOrNum2){
    return FIRCompare(strOrNum1, strOrNum2) > 0 ? strOrNum2 : strOrNum1;
}
NSDecimalNumber *FIRMax(id strOrNum1,id strOrNum2){
    return FIRCompare(strOrNum1, strOrNum2) > 0 ? strOrNum1 : strOrNum2;
}
NSDecimalNumber *FIRAdd_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){
    return handlerDecimalNumber(FIRAdd(strOrNum1, strOrNum2), mode, scale);
}
NSDecimalNumber *FIRSub_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){
    return handlerDecimalNumber(FIRSub(strOrNum1, strOrNum2), mode, scale);
}
NSDecimalNumber *FIRMul_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){
    return handlerDecimalNumber(FIRMul(strOrNum1, strOrNum2), mode, scale);
}
NSDecimalNumber *FIRDiv_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){
    return handlerDecimalNumber(FIRDiv(strOrNum1, strOrNum2), mode, scale);
}


NSDecimalNumber *FIRMin_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){
    return handlerDecimalNumber(FIRMin(strOrNum1, strOrNum2), mode, scale);
}
NSDecimalNumber *FIRMax_handler(id strOrNum1,id strOrNum2,NSRoundingMode mode,int scale){
    return handlerDecimalNumber(FIRMax(strOrNum1, strOrNum2), mode, scale);
}

+(NSString *)getStringDecimalWithoutEndZero:(id)stringOrNumber
{
    NSDecimalNumber *num = FIRAdd(stringOrNumber, @"0");
    return [NSString stringWithFormat:@"%@",num];
}

@end

 

 

参考:

(1) https://github.com/BANKEX/web3swift

(2)https://github.com/CodeJCSON/NSDecimalNumber 

 

 

 

待续

posted on 2018-07-17 23:20  ACM_Someone like you  阅读(845)  评论(0编辑  收藏  举报

导航