區塊鏈錢包開發,第三周總結 (貨幣精度計算 超大數運算)
這周主要做了 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 閱讀(880) 評論(0) 收藏 舉報
浙公網安備 33010602011771號