easaca在2011-02-22在博客中发表了用Ada语言编写的“超长数字减法”,本人在评论中说明该算法有可改进之处,无乃评论字数不能超过1000字,因此将改进算法贴于此。新算法的特点重复如下:更加Ada-like,采用fixed_string完成,使用character'pos和character'val进行字符与数值之间的转换;使用a'first和a'length来保证串操作的通用性;小数点保留在串中,完全按人工计算的逻辑设计,包括小数点对齐、正负号判别、从后往前运算和借位的处理等等。经对比运算,改进算法的使用时间仅是原算法的1/3。对超长数字加法和乘法也进行了改进,其中乘法的速度提高更明显。
function long_minus (a, b : in String) return String is
use Ada.Strings.Fixed;
dot_pos_a, dot_pos_b : Integer := 0;
--dot postion in string a and b;
before_dot_a, before_dot_b : Integer := 0;
--the number of characters before dot in string a and b;
after_dot_a, after_dot_b : Integer := 0;
--the nubmer of chareacters after dot in string a and b;
len : Integer;
--the length of the result string;
before_max, after_max : Integer;
--before_max: the max number of characters before dot , which is the
--number of
--characters before dot in result string;
--after_max: the max number of characters after dot , which is the
--number of
--characters after dot in result string;
negative_result : Boolean := False;
--the resut string is negative or not
r_a : String := to_real (a);
r_b : String := to_real (b);
begin
--determine dot position in string a;
dot_pos_a := Index (r_a, ".");
before_dot_a := dot_pos_a - r_a'First;
after_dot_a := r_a'Length - dot_pos_a;
--determine dot position in string b;
dot_pos_b := Index (r_b, ".");
before_dot_b := dot_pos_b - r_b'First;
after_dot_b := r_b'Length - dot_pos_b;
--determine the sign.
if before_dot_a < before_dot_b then
negative_result := True;
elsif before_dot_a = before_dot_b then
declare
after_min : int := int'Min (after_dot_a, after_dot_b);
begin
for i in
r_a'First .. r_a'First + before_dot_a + 1 + after_min - 1
loop
if Character'Pos (r_a (i)) < Character'Pos (r_b (i)) then
negative_result := True;
exit;
end if;
end loop;
end;
end if;
before_max := int'Max (before_dot_a, before_dot_b);
after_max := int'Max (after_dot_a, after_dot_b);
len := before_max + 1 + after_max;
declare
result, aa, bb : String (1 .. len) := (others => '0');
--result: store the result;
--aa,bb:temp string, dot position aligned
--aa: if value(r_a)>value(r_b) then aa=r_a else aa=r_b
--bb: if value(r_a)>value(r_b) then bb=r_b else bb=r_a
borrowed : Boolean := False;
--borrow state
indented_a_pos : int := before_max - before_dot_a + 1;
--r_a is copied to aa or bb with indention so that the dot position
--are aligned;
indented_b_pos : int := before_max - before_dot_b + 1;
--r_b is copied to aa or bb with indention so that the dot position
--are aligned;
zero_pos : int := Character'Pos ('0');
--The position of character '0' in character set. It is used to
--convert character to int and vice versa.
begin
--swap a and b if value(a) < value(b)
if negative_result then
bb (indented_a_pos .. indented_a_pos + r_a'Length - 1) := r_a;
aa (indented_b_pos .. indented_b_pos + r_b'Length - 1) := r_b;
else
aa (indented_a_pos .. indented_a_pos + r_a'Length - 1) := r_a;
bb (indented_b_pos .. indented_b_pos + r_b'Length - 1) := r_b;
end if;
borrowed := False;
-- subtraction digit by digit in reverse order
for i in reverse result'Range loop
if aa (i) = '.' then
result (i) := '.';
else
declare
aa_digit : int := int'Value ("" & aa (i));
bb_digit : int := int'Value ("" & bb (i));
begin
if borrowed then
aa_digit := aa_digit - 1;
end if;
if aa_digit >= bb_digit then
result (i) :=
Character'Val (aa_digit - bb_digit + zero_pos);
borrowed := False;
else
result (i) :=
Character'Val (10 + aa_digit - bb_digit + zero_pos);
borrowed := True;
end if;
end;
end if; --if aa(i)='.'
end loop;
if negative_result then
return "-" & result;
else
return result;
end if;
end;
end long_minus;
function long_addition (a, b : in String) return String is
use Ada.Strings.Fixed;
dot_pos_a, dot_pos_b : Integer := 0;
--dot postion in string a and b;
before_dot_a, before_dot_b : Integer := 0;
--the number of characters before dot in string a and b;
after_dot_a, after_dot_b : Integer := 0;
--the nubmer of chareacters after dot in string a and b;
len : Integer;
--the length of the result string;
before_max, after_max : Integer;
--before_max: the max number of characters before dot , which is the
--number of
--characters before dot in result string;
--after_max: the max number of characters after dot , which is the
--number of
--characters after dot in result string;
r_a : String := to_real (a);
r_b : String := to_real (b);
begin
--determine dot position in string a;
dot_pos_a := Index (r_a, ".");
before_dot_a := dot_pos_a - r_a'First;
after_dot_a := r_a'Length - dot_pos_a;
--determine dot position in string b;
dot_pos_b := Index (r_b, ".");
before_dot_b := dot_pos_b - r_b'First;
after_dot_b := r_b'Length - dot_pos_b;
before_max := int'Max (before_dot_a, before_dot_b);
after_max := int'Max (after_dot_a, after_dot_b);
len := before_max + 1 + after_max;
declare
result, aa, bb : String (1 .. len) := (others => '0');
--result: store the result;
--aa,bb:temp string, dot position aligned
--aa: if value(r_a)>value(r_b) then aa=r_a else aa=r_b
--bb: if value(r_a)>value(r_b) then bb=r_b else bb=r_a
carry : Integer := 0;
--carry state
indented_a_pos : int := before_max - before_dot_a + 1;
--a is copied to aa or bb with indention so that the dot position
--are aligned;
indented_b_pos : int := before_max - before_dot_b + 1;
--b is copied to aa or bb with indention so that the dot position
--are aligned;
zero_pos : int := Character'Pos ('0');
--The position of character '0' in character set. It is used to
--convert character to int and vice versa.
begin
aa (indented_a_pos .. indented_a_pos + r_a'Length - 1) := r_a;
bb (indented_b_pos .. indented_b_pos + r_b'Length - 1) := r_b;
carry := 0;
-- addition digit by digit in reverse order
for i in reverse result'Range loop
if aa (i) = '.' then
result (i) := '.';
else
declare
aa_digit : int := int'Value ("" & aa (i));
bb_digit : int := int'Value ("" & bb (i));
sum_digit : int := aa_digit + bb_digit + carry;
sum_digit_img : String := int'Image (sum_digit);
begin
result (i) := sum_digit_img (sum_digit_img'Last);
if sum_digit >= 10 then
carry :=
Character'Pos
(sum_digit_img (sum_digit_img'Last - 1)) -
zero_pos;
else
carry := 0;
end if;
end;
end if;
end loop;
if carry > 0 then
return Character'Val (carry + zero_pos) & result;
else
return result;
end if;
end;
end long_addition;
function long_multiply (a, b : in String) return String is
use Ada.Strings.Fixed;
dot_pos_a, dot_pos_b : Integer := 0;
--dot postion in string a and b;
before_dot_a, before_dot_b : Integer := 0;
--the number of characters before dot in string a and b;
after_dot_a, after_dot_b : Integer := 0;
--the nubmer of chareacters after dot in string a and b;
len : Integer;
--the length of the result string;
before_max, after_max : Integer;
--before_max: the max number of characters before dot , which is the
--number of
--characters before dot in result string;
--after_max: the max number of characters after dot , which is the
--number of
--characters after dot in result string;
r_a : String := to_real (a);
r_b : String := to_real (b);
begin
--determine dot position in string a;
dot_pos_a := Index (r_a, ".");
before_dot_a := dot_pos_a - r_a'First;
after_dot_a := r_a'Length - dot_pos_a;
--determine dot position in string b;
dot_pos_b := Index (r_b, ".");
before_dot_b := dot_pos_b - r_b'First;
after_dot_b := r_b'Length - dot_pos_b;
before_max := int'Max (before_dot_a, before_dot_b);
after_max := int'Max (after_dot_a, after_dot_b);
len := r_b'Length + r_a'Length + 1;
declare
result, resultx : String (1 .. len) := (others => '0');
--result: store the result;
carry : Integer := 0;
--carry state
zero_pos : int := Character'Pos ('0');
--The position of character '0' in character set. It is used to
--convert character to int and vice versa.
right_aligned_pos : int := 0;
result_pos : Natural := 1;
aa_digit : int;
bb_digit : int;
result_digit : Character;
begin
carry := 0;
right_aligned_pos := 0;
-- multiplication digit by digit in reverse order
for b_index in reverse r_b'Range loop
if r_b (b_index) /= '.' then
bb_digit := int'Value ("" & r_b (b_index));
result_pos := resultx'Last;
resultx := (others => '0');
carry := 0;
for a_index in reverse r_a'Range loop
if r_a (a_index) /= '.' then
aa_digit := int'Value ("" & r_a (a_index));
declare
product : int := aa_digit * bb_digit + carry;
product_img : String := int'Image (product);
begin
result_digit := product_img (product_img'Last);
if product >= 10 then
carry :=
Character'Pos
(product_img (product_img'Last - 1)) -
zero_pos;
else
carry := 0;
end if;
end;
resultx (result_pos + right_aligned_pos) := result_digit;
result_pos := result_pos -
1;
end if;
end loop;
if carry > 0 then
resultx (result_pos + right_aligned_pos) :=
Character'Val (carry + zero_pos);
carry := 0;
end if;
declare
tmp_result : String := int_long_addition (result, resultx);
len_dif : Natural := tmp_result'Length - result'Length;
begin
result :=
tmp_result (tmp_result'First + len_dif .. tmp_result'Last);
end;
right_aligned_pos := right_aligned_pos - 1;
end if;
end loop;
declare
dot_pos : int := after_dot_b + after_dot_a;
first_non_zero_pos : int := 0;
begin
for i in result'Range loop
if result (i) /= '0' then
first_non_zero_pos := i;
exit;
end if;
end loop;
return result (first_non_zero_pos .. result'Length - dot_pos) &
"." &
result (result'Length - dot_pos + 1 .. result'Length);
end;
end;
end long_multiply;
function to_real (a : String) return String is
use Ada.Strings.Fixed;
pos : Integer := Index (a, ".");
begin
if pos = 0 then
return a & ".0";
else
return a;
end if;
end to_real;
function int_long_addition (a, b : String) return String is
a_len : Natural := a'Length;
b_len : Natural := b'Length;
max_len : Natural := int'Max (a_len, b_len);
a_len_diff : Natural := max_len - a_len;
b_len_diff : Natural := max_len - b_len;
aa, bb, result : String (1 .. max_len) := (others => '0');
carry : Natural := 0;
a_digit, b_digit : int;
zero_pos : Natural := Character'Pos ('0');
sum : Natural := 0;
begin
aa (aa'First + a_len_diff .. aa'Last) := a;
bb (bb'First + b_len_diff .. bb'Last) := b;
for i in reverse result'Range loop
a_digit := Character'Pos (aa (i)) - zero_pos;
b_digit := Character'Pos (bb (i)) - zero_pos;
sum := a_digit + b_digit + carry;
declare
sum_img : String := int'Image (sum);
begin
result (i) := sum_img (sum_img'Last);
if sum >= 10 then
carry := Character'Pos (sum_img (sum_img'Last - 1)) -
zero_pos;
else
carry := 0;
end if;
end;
end loop;
if carry > 0 then
return Character'Val (carry + zero_pos) & result;
else
return result;
end if;
end int_long_addition;
其中to_real和int_long_addition是两个辅助函数,分别用于将整数转换为实数和超长整数加法。
easaca的超长乘法尚有语法错误,必须修改才能通过编译。
下面是测试程序及运行结果
with long_algebra; use long_algebra;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;
with Ada.Exceptions; use Ada.Exceptions;
with ada.Long_Float_Text_IO;
use ada.Long_Float_Text_IO;
procedure main is
a1 : String := "8230";
a2 : String := "82211";
a3 : String := a1 - a2;
a4 : String := long_minus (a1, a2);
star_product:string:=a1*a2;
long_product:string:=long_multiply(a1,a2);
start_t, end_t : Time;
begin
start_t := Clock;
for i in 1 .. 1000 loop
a3 := a1 - a2;
end loop;
end_t := Clock;
Put_Line
(" " & """" & "-" & """" & "(" & a1 & "," & a2 &
")=" & a3 & " time span:" & Duration'Image (end_t - start_t));
start_t := Clock;
for i in 1 .. 1000 loop
a4 := long_minus (a1, a2);
end loop;
end_t := Clock;
Put_Line
("long_minus(" & a1 & "," & a2 & ")=" &
a4 & " time span:" & Duration'Image (end_t - start_t));
declare
a6 : String := a1 + a2;
begin
Put_Line ("a1 + a2=" & a6);
exception
when E : others =>
Put_Line ("a1 + a2 exception.");
Put_Line (Exception_Message (E));
end;
declare
a7 : String := long_addition(a1 , a2);
begin
Put_Line ("long_addition(a1,a2)=" & a7);
exception
when E : others =>
Put_Line ("a1 + a2 exception.");
Put_Line (Exception_Message (E));
end;
declare
a5 : String := a1 * a2;
a1_r:long_float:=long_float'value(a1);
a2_r:long_float:=long_float'value(a2);
a5_r:long_float:=a1_r*a2_r;
begin
Put_Line ("a1 * a2=" & a5);
put("a1_r*a2_r=");
put(a5_r,10,10,0);
new_line;
exception
when E : others =>
Put_Line ("a1 * a2 exception.");
Put_Line (Exception_Message (E));
end;
put_line("int_long_add(000133+9999)="&int_long_addition("000133","9999"));
put_line("long_multiply(a1,a2)="&long_multiply(a1,a2));
start_t := Clock;
for i in 1 .. 1000 loop
star_product := a1* a2;
end loop;
end_t := Clock;
Put_Line
("star_product(" & a1 & "," & a2 & ")=" &
star_product & " time span:" &
Duration'Image (end_t - start_t));
start_t := Clock;
for i in 1 .. 1000 loop
long_product := long_multiply(a1,a2);
end loop;
end_t := Clock;
Put_Line
("long_product(" & a1 & "," & a2 &
")=" & long_product &
" time span:" & Duration'Image (end_t - start_t));
exception
when E:others=>
Put_Line ("in main.");
Put_Line (Exception_Message (E));
end main;
D:\zhenggen\src\long_algebra\main
"-"(8230,82211)=-73981 time span: 0.010781325 (easaca减法)
long_minus(8230,82211)=-73981.0 time span: 0.003780305
a1 + a2=90441
long_addition(a1,a2)=90441.0
a1 * a2=676596530
a1_r*a2_r= 676596530.0000000000
int_long_add(000133+9999)=010132
long_multiply(a1,a2)=676596530.00
star_product(8230,82211)=676596530 time span: 0.049297256 (easaca乘法)
long_product(8230,82211)=676596530.00 time span: 0.011648885
[2012-02-07 16:28:10] process terminated successfully (elapsed time: 00.33s)
编译环境:
GPS 4.4.1 (20091215) hosted on i686-pc-mingw32
GNAT GPL 2010 (20100603)
the GNAT Programming Studio