背景
大数计算在金融行业比较普遍。计算机领域对浮点数的表达方式基本遵循如下机制。
IEEE 754
基于IEEE 754的浮点数表示方法如下:
因此总有极限,在超过极限之后,需要引入第三方库协助处理。大体如下
当浮点数精度超过20位,需要大数计算
当参与运算的数无法使用常规变量类型来表示时,例如超出如下范围时:
C的基础类型
Java的基础类型
Rust的基础类型
可以看到在大约20位精度后,需要家诶住第扩展类型或第三方库。Java可以使用BigInteger,BIgDecimal来表示;C可以借助GMP库来表示或运算;Rust可以基于自身的库或对照C来;那么性能差异到底如何?
最间实践方案
环境
单线程计算100次30位以上大数的加减乘除。所在的机器配置如下:
机器环境
Java
Java标准库中的 java.math.BigInteger 和 java.math.BigDecimal 可以用来处理大数运算。例如
import java.math.BigInteger;import java.math.BigDecimal;// BigInteger 示例BigInteger bigInt1 = new BigInteger("123456789012345678901234567890");BigInteger bigInt2 = new BigInteger("987654321098765432109876543210");BigInteger sum = bigInt1.add(bigInt2); // 加法:cite[2]:cite[6]// BigDecimal 示例(务必使用String构造器避免初始精度损失:cite[8])BigDecimal bd1 = new BigDecimal("0.09");BigDecimal bd2 = new BigDecimal("0.01");BigDecimal result = bd1.add(bd2); // 结果为精确的0.10:cite[8]GMP
基于C语言有一个知名的大数运算库gmp,也可以对正相互和浮点数进行处理,如下:
#include "gmp.h"void gmp_float_test(){ mpf_t x, y, result; // 声明mpf_t浮点变量 mpf_init2(x, 100); // 初始化x,设置100位有效数字 mpf_init2(y, 100); // 初始化y,100位有效数字 mpf_init2(result, 100); // 初始化结果变量,100位有效数字 // 赋值:将数值(或字符串)赋值给mpf_t变量 mpf_set_d(x, 3.14159265358979323846); // 从double赋值 mpf_set_str(y, "2.71828182845904523536", 10); // 从字符串赋值(避免double精度损失) // 浮点运算:加法 mpf_add(result, x, y); // result = x + y // 输出结果(指定以10进制打印,保留50位小数) gmp_printf("x + y = %.50Ff\n", result); // 释放内存(必须手动释放,否则内存泄漏) mpf_clear(x); mpf_clear(y); mpf_clear(result);}void gmp_int_test() { mpz_t a, b, result; // 初始化变量 mpz_init(a); mpz_init(b); mpz_init(result); // 设置大整数值 mpz_set_str(a, "12345678901234567890", 10); mpz_set_str(b, "98765432109876543210", 10); // 加法 mpz_add(result, a, b); gmp_printf("算式: %Zd + %Zd = %Zd\n", a, b, result); // 乘法 mpz_mul(result, a, b); gmp_printf("算式: %Zd * %Zd = %Zd\n", a, b, result); // 除法 mpz_set_str(a, "123456789012345678901234567890123456789012345678901234567890", 10); mpz_tdiv_q(result, a, b); gmp_printf("算式: %Zd / %Zd = %Zd\n", a, b, result); // 平方 mpz_pow_ui(result, a, 2); gmp_printf("算式: %Zd^2 = %Zd\n", a, result); // 清理内存 mpz_clear(a); mpz_clear(b); mpz_clear(result);}Java+JNI+so(C+GMP)
初步测试,看到直接使用GMP的C代码运行效率大约比Java BigDecimal高出6倍以上。考虑将这部分运算使用C实现,看能否达到提高效率的目的。
Java BigDecimal计算100万次 +-*/
Rust、Java、C
Rust基于num、Java基于BigDecimal、C基于GMP,都提供相同的命令行参数指定计算的次数、参数,输出ns耗时。结果如下:
C最快,Rust相当不俗
相关代码如下:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <time.h>#include <gmp.h>void factorial(mpz_t result, unsigned long n) { mpz_set_ui(result, 1); for (unsigned long i = 2; i <= n; i++) { mpz_mul_ui(result, result, i); }}void power(mpz_t result, const mpz_t base, unsigned long exp) { mpz_set_ui(result, 1); for (unsigned long i = 0; i < exp; i++) { mpz_mul(result, result, base); }}void fibonacci(mpz_t result, unsigned long n) { if (n == 0) { mpz_set_ui(result, 0); return; } mpz_t a, b, temp; mpz_init(a); mpz_init(b); mpz_init(temp); mpz_set_ui(a, 0); mpz_set_ui(b, 1); for (unsigned long i = 2; i <= n; i++) { mpz_add(temp, a, b); mpz_set(a, b); mpz_set(b, temp); } mpz_set(result, b); mpz_clear(a); mpz_clear(b); mpz_clear(temp);}int main(int argc, char *argv[]) { if (argc < 4) { fprintf(stderr, "用法: %s <操作> <数字> <次数>\n", argv[0]); fprintf(stderr, "操作选项:\n"); fprintf(stderr, " factorial - 计算阶乘\n"); fprintf(stderr, " power - 计算幂运算(数字^次数)\n"); fprintf(stderr, " fibonacci - 计算斐波那契数列\n"); fprintf(stderr, "\n示例:\n"); fprintf(stderr, " %s factorial 100 1000\n", argv[0]); fprintf(stderr, " %s power 123456789 1000\n", argv[0]); fprintf(stderr, " %s fibonacci 1000 1000\n", argv[0]); return 1; } char *operation = argv[1]; unsigned long number = strtoul(argv[2], NULL, 10); unsigned long iterations = strtoul(argv[3], NULL, 10); printf("C (GMP) 大数运算测试\n"); printf("操作: %s, 数字: %lu, 次数: %lu\n", operation, number, iterations); struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); mpz_t result; mpz_init(result); if (strcmp(operation, "factorial") == 0) { for (unsigned long i = 0; i < iterations; i++) { factorial(result, number); } clock_gettime(CLOCK_MONOTONIC, &end); unsigned long long duration = (end.tv_sec - start.tv_sec) * 1000000000ULL + (end.tv_nsec - start.tv_nsec); gmp_printf("%lu! = %Zd\n", number, result); printf("结果位数: %d\n", mpz_sizeinbase(result, 10)); printf("耗时: %llu ns\n", duration); } else if (strcmp(operation, "power") == 0) { mpz_t base; mpz_init(base); mpz_set_ui(base, number); for (unsigned long i = 0; i < iterations; i++) { power(result, base, iterations); } clock_gettime(CLOCK_MONOTONIC, &end); unsigned long long duration = (end.tv_sec - start.tv_sec) * 1000000000ULL + (end.tv_nsec - start.tv_nsec); gmp_printf("%lu^%lu = %Zd\n", number, iterations, result); printf("结果位数: %d\n", mpz_sizeinbase(result, 10)); printf("耗时: %llu ns\n", duration); mpz_clear(base); } else if (strcmp(operation, "fibonacci") == 0) { for (unsigned long i = 0; i < iterations; i++) { fibonacci(result, number); } clock_gettime(CLOCK_MONOTONIC, &end); unsigned long long duration = (end.tv_sec - start.tv_sec) * 1000000000ULL + (end.tv_nsec - start.tv_nsec); gmp_printf("fibonacci(%lu) = %Zd\n", number, result); printf("结果位数: %d\n", mpz_sizeinbase(result, 10)); printf("耗时: %llu ns\n", duration); } else { fprintf(stderr, "错误:未知的操作 '%s'\n", operation); mpz_clear(result); return 1; } mpz_clear(result); return 0;}import java.math.BigDecimal;import java.math.BigInteger;import java.math.MathContext;import java.time.Duration;import java.time.Instant;public class BigNumber { public static BigDecimal factorial(int n) { BigDecimal result = BigDecimal.ONE; for (int i = 2; i <= n; i++) { result = result.multiply(BigDecimal.valueOf(i)); } return result; } public static BigDecimal power(BigDecimal base, long exp) { BigDecimal result = BigDecimal.ONE; for (long i = 0; i < exp; i++) { result = result.multiply(base); } return result; } public static BigInteger fibonacci(int n) { if (n == 0) { return BigInteger.ZERO; } BigInteger a = BigInteger.ZERO; BigInteger b = BigInteger.ONE; for (int i = 2; i <= n; i++) { BigInteger temp = a.add(b); a = b; b = temp; } return b; } public static void main(String[] args) { if (args.length < 3) { System.err.println("用法: java BigNumber <操作> <数字> <次数>"); System.err.println("操作选项:"); System.err.println(" factorial - 计算阶乘"); System.err.println(" power - 计算幂运算(数字^次数)"); System.err.println(" fibonacci - 计算斐波那契数列"); System.err.println("\n示例:"); System.err.println(" java BigNumber factorial 100 1000"); System.err.println(" java BigNumber power 123456789 1000"); System.err.println(" java BigNumber fibonacci 1000 1000"); System.exit(1); } String operation = args[0]; int number; long iterations; try { number = Integer.parseInt(args[1]); iterations = Long.parseLong(args[2]); } catch (NumberFormatException e) { System.err.println("错误:无效的数字或次数"); System.exit(1); return; } System.out.println("Java 大数运算测试"); System.out.println("操作: " + operation + ", 数字: " + number + ", 次数: " + iterations); Instant start = Instant.now(); switch (operation) { case "factorial": BigDecimal factorialResult = BigDecimal.ZERO; for (long i = 0; i < iterations; i++) { factorialResult = factorial(number); } Duration factorialDuration = Duration.between(start, Instant.now()); System.out.println(number + "! = " + factorialResult); System.out.println("结果位数: " + factorialResult.toPlainString().length()); System.out.println("耗时: " + factorialDuration.tonanos() + " ns"); break; case "power": BigDecimal base = new BigDecimal(number); BigDecimal powerResult = BigDecimal.ZERO; for (long i = 0; i < iterations; i++) { powerResult = power(base, iterations); } Duration powerDuration = Duration.between(start, Instant.now()); System.out.println(number + "^" + iterations + " = " + powerResult); System.out.println("结果位数: " + powerResult.toPlainString().length()); System.out.println("耗时: " + powerDuration.tonanos() + " ns"); break; case "fibonacci": BigInteger fibResult = BigInteger.ZERO; for (long i = 0; i < iterations; i++) { fibResult = fibonacci(number); } Duration fibDuration = Duration.between(start, Instant.now()); System.out.println("fibonacci(" + number + ") = " + fibResult); System.out.println("结果位数: " + fibResult.toString().length()); System.out.println("耗时: " + fibDuration.tonanos() + " ns"); break; default: System.err.println("错误:未知的操作 '" + operation + "'"); System.exit(1); } }}use std::time::Instant;use num::BigInt;use std::env;fn factorial(n: u64) -> BigInt { let mut result = BigInt::from(1); for i in 2..=n { result *= BigInt::from(i); } result}fn power(base: &BigInt, exp: u64) -> BigInt { let mut result = BigInt::from(1); for _ in 0..exp { result *= base; } result}fn fibonacci(n: u64) -> BigInt { if n == 0 { return BigInt::from(0); } let mut a = BigInt::from(0); let mut b = BigInt::from(1); for _ in 2..=n { let temp = &a + &b; a = b; b = temp; } b}fn main() { let args: Vec<String> = env::args().collect(); if args.len() < 4 { eprintln!("用法: {} <操作> <数字> <次数>", args[0]); eprintln!("操作选项:"); eprintln!(" factorial - 计算阶乘"); eprintln!(" power - 计算幂运算(数字^次数)"); eprintln!(" fibonacci - 计算斐波那契数列"); eprintln!("\n示例:"); eprintln!(" {} factorial 100 1000", args[0]); eprintln!(" {} power 123456789 1000", args[0]); eprintln!(" {} fibonacci 1000 1000", args[0]); std::process::exit(1); } let operation = &args[1]; let number: u64 = match args[2].parse() { Ok(n) => n, Err(_) => { eprintln!("错误:无效的数字 '{}'", args[2]); std::process::exit(1); } }; let iterations: u64 = match args[3].parse() { Ok(n) => n, Err(_) => { eprintln!("错误:无效的次数 '{}'", args[3]); std::process::exit(1); } }; println!("Rust 大数运算测试"); println!("操作: {}, 数字: {}, 次数: {}", operation, number, iterations); let start = Instant::now(); match operation.as_str() { "factorial" => { let mut result = BigInt::from(0); for _ in 0..iterations { result = factorial(number); } let duration = start.elapsed(); println!("{}! = {}", number, result); println!("结果位数: {}", result.to_string().len()); println!("耗时: {} ns", duration.as_nanos()); } "power" => { let base = BigInt::from(number); let mut result = BigInt::from(0); for _ in 0..iterations { result = power(&base, iterations); } let duration = start.elapsed(); println!("{}^{} = {}", number, iterations, result); println!("结果位数: {}", result.to_string().len()); println!("耗时: {} ns", duration.as_nanos()); } "fibonacci" => { let mut result = BigInt::from(0); for _ in 0..iterations { result = fibonacci(number); } let duration = start.elapsed(); println!("fibonacci({}) = {}", number, result); println!("结果位数: {}", result.to_string().len()); println!("耗时: {} ns", duration.as_nanos()); } _ => { eprintln!("错误:未知的操作 '{}'", operation); std::process::exit(1); } }}参考资料
- GMP官网 : https://gmplib.org/
- Java的BigDicimal BigInteger源码
