729 lines
25 KiB
Plaintext
729 lines
25 KiB
Plaintext
// This file contains a port of the floating-point printing functionality in stb_sprintf; the original code has been
|
|
// significantly refactored to support this single use case.
|
|
|
|
stb_print_float :: (builder: *String_Builder, format: FormatFloat) -> int {
|
|
copy_repeated_character :: (destination: *u8, count: int, $character: u8) {
|
|
c :: cast,no_check(u32) character;
|
|
characters_wide : u32 : (c << 24) | (c << 16) | (c << 8) | c;
|
|
|
|
i := count;
|
|
d := destination;
|
|
while i {
|
|
if ((cast,no_check(u64) d) & 3) == 0 break;
|
|
d.* = character;
|
|
d += 1;
|
|
i -= 1;
|
|
}
|
|
|
|
while i >= 4 {
|
|
(cast(*u32) d).* = characters_wide;
|
|
d += 4;
|
|
i -= 4;
|
|
}
|
|
|
|
while i {
|
|
d.* = character;
|
|
d += 1;
|
|
i -= 1;
|
|
}
|
|
}
|
|
|
|
copy_integer_part :: (count: int, s: *u8) -> (n: int, s: *u8) #expand {
|
|
// This is just a macro so we can use these variables without passing them...
|
|
n := 0;
|
|
if use_thousand_separators {
|
|
while n < count {
|
|
cs += 1;
|
|
if cs > thousand_group_size {
|
|
cs = 0;
|
|
s.* = thousand_separator_character;
|
|
s += 1;
|
|
} else {
|
|
s.* = (sn + n).*;
|
|
s += 1;
|
|
n += 1;
|
|
}
|
|
}
|
|
} else {
|
|
while n < count {
|
|
s.* = (sn + n).*;
|
|
s += 1;
|
|
n += 1;
|
|
}
|
|
}
|
|
|
|
return n, s;
|
|
}
|
|
|
|
determine_mode_for_shortest :: () -> FormatFloat.Mode #expand {
|
|
// clamp the precision and delete extra zeros after clamp
|
|
n = pr;
|
|
if l > pr l = pr;
|
|
|
|
while l > 1 && pr && sn[l - 1] == #char "0" {
|
|
pr -= 1;
|
|
l -= 1;
|
|
}
|
|
|
|
if dp <= -4 || dp > n {
|
|
if pr > l pr = l - 1;
|
|
else if pr pr -= 1; // when using %e, there is one digit before the decimal
|
|
return .SCIENTIFIC;
|
|
} else {
|
|
// this is the insane action to get the pr to match %g sematics for %f
|
|
if dp > 0 pr = ifx dp < l then l - dp else 0;
|
|
else pr = -dp + ifx pr > l then l else pr;
|
|
return .DECIMAL;
|
|
}
|
|
}
|
|
|
|
num: [512] u8; // Big enough for e308 (with thousand separators) or e-307.
|
|
exponent: [8] u8; // Stores the exponent string at index 1 and the length of the string at index 0.
|
|
fw, pr, tz, dp, l, n, cs: int;
|
|
s, sn: *u8;
|
|
|
|
total_string_length: int;
|
|
trailing_zeroes_in_string: int;
|
|
decimal_digits_in_string: int;
|
|
|
|
// @Cleanup: We can just 'using' format here:
|
|
decimal_separator_character := format.decimal_separator_character;
|
|
thousand_separator_character := format.thousand_separator_character;
|
|
exponent_character := format.exponent_character;
|
|
use_thousand_separators := format.thousand_separators;
|
|
mode_to_use := format.mode;
|
|
|
|
// @Note: These features aren't available through FormatFloat yet.
|
|
use_left_justification :: false;
|
|
left_pad_with_zeroes :: false;
|
|
|
|
fw = format.width;
|
|
pr = format.trailing_width;
|
|
if pr < 0 pr = default_precision;
|
|
|
|
any := format.value;
|
|
assert(any.type != null);
|
|
type := any.type;
|
|
|
|
while type.type == .VARIANT {
|
|
variant := cast(*Type_Info_Variant) type;
|
|
type = variant.variant_of;
|
|
}
|
|
|
|
assert(type.type == .FLOAT);
|
|
info := cast(*Type_Info_Float) type;
|
|
|
|
float_value: float64;
|
|
if info.runtime_size == 8 {
|
|
float_value = (.*)cast(*float64) format.value.value_pointer;
|
|
} else {
|
|
float_value = (.*)cast(*float32) format.value.value_pointer;
|
|
}
|
|
|
|
pr_flag, pr_bias: u32;
|
|
if mode_to_use == {
|
|
case .SHORTEST;
|
|
if pr == 0 pr = 1;
|
|
pr_bias = 1;
|
|
pr_flag = 0x80000000;
|
|
case .SCIENTIFIC; pr_flag = 0x80000000;
|
|
}
|
|
|
|
is_negative := real_to_str(*sn, *l, num.data, *dp, float_value, (cast,no_check(u32)pr - pr_bias) | pr_flag);
|
|
|
|
prefix_character: u8;
|
|
if is_negative {
|
|
prefix_character = #char "-";
|
|
} else if format.positive_number_prefix == {
|
|
case .SPACE; prefix_character = #char " ";
|
|
case .PLUS; prefix_character = #char "+";
|
|
}
|
|
|
|
if dp == INFINITY_OR_NAN {
|
|
s = sn; cs = 0; pr = 0;
|
|
} else {
|
|
if mode_to_use == .SHORTEST mode_to_use = determine_mode_for_shortest();
|
|
|
|
if mode_to_use == .DECIMAL {
|
|
//
|
|
// Decimal float.
|
|
//
|
|
s_start := num.data + 64;
|
|
s = s_start;
|
|
|
|
// handle the three decimal varieties
|
|
if dp <= 0 { // handle 0.000*000xxxx
|
|
s.* = #char "0"; s += 1;
|
|
if pr { s.* = decimal_separator_character; s += 1; }
|
|
|
|
first_trailing_zero_in_string := s;
|
|
|
|
n = -dp;
|
|
if n > pr n = pr;
|
|
|
|
copy_repeated_character(s, n, #char "0"); s += n;
|
|
|
|
if l + n > pr l = pr - n;
|
|
|
|
decimal_digits_in_string = n + l;
|
|
|
|
for 1..l {
|
|
s.* = sn.*;
|
|
if sn.* != #char "0" first_trailing_zero_in_string = s + 1;
|
|
s += 1; sn += 1;
|
|
}
|
|
|
|
tz = pr - (n + l);
|
|
cs = 1 + (thousand_group_size << 24); // how many tens did we write (for commas below)
|
|
trailing_zeroes_in_string = s - first_trailing_zero_in_string;
|
|
} else {
|
|
cs = ifx use_thousand_separators then (600 - dp) % thousand_group_size else 0;
|
|
|
|
if dp >= l { // handle xxxx000*000.0
|
|
n, s = inline copy_integer_part(l, s);
|
|
|
|
if n < dp { // Copy the remaining zeroes of the integer part.
|
|
n = dp - n;
|
|
|
|
if !use_thousand_separators {
|
|
copy_repeated_character(s, n, #char "0"); s += n;
|
|
} else {
|
|
while n {
|
|
cs += 1;
|
|
if cs > thousand_group_size {
|
|
cs = 0;
|
|
s.* = thousand_separator_character; s += 1;
|
|
} else {
|
|
s.* = #char "0"; s += 1;
|
|
n -= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cs = (s - s_start) + (thousand_group_size << 24); // cs is how many tens
|
|
|
|
if pr {
|
|
s.* = decimal_separator_character; s += 1;
|
|
tz = pr;
|
|
}
|
|
} else { // handle xxxxx.xxxx000*000
|
|
n, s = inline copy_integer_part(dp, s);
|
|
|
|
cs = (s - s_start) + (thousand_group_size << 24); // cs is how many tens
|
|
|
|
if pr { s.* = decimal_separator_character; s += 1; }
|
|
|
|
if l - dp > pr l = pr + dp;
|
|
|
|
decimal_digits_in_string = l - n;
|
|
first_trailing_zero_in_string := s;
|
|
|
|
sn = sn + n;
|
|
n = l - n;
|
|
|
|
for 1..n {
|
|
s.* = sn.*;
|
|
if sn.* != #char "0" first_trailing_zero_in_string = s + 1;
|
|
s += 1; sn += 1;
|
|
}
|
|
|
|
tz = pr - (l - dp);
|
|
trailing_zeroes_in_string = s - first_trailing_zero_in_string;
|
|
}
|
|
}
|
|
|
|
pr = 0;
|
|
l = s - s_start; // get the length that we copied
|
|
s = s_start;
|
|
} else {
|
|
//
|
|
// Scientific float.
|
|
//
|
|
s_start := num.data + 64;
|
|
s = s_start;
|
|
|
|
// handle leading chars
|
|
s.* = sn.*; s += 1;
|
|
|
|
if pr { s.* = decimal_separator_character; s += 1; }
|
|
|
|
// handle after decimal
|
|
if (l - 1) > pr l = pr + 1;
|
|
|
|
decimal_digits_in_string = l - 1;
|
|
first_trailing_zero_in_string := s;
|
|
|
|
n = 1;
|
|
while n < l {
|
|
s.* = (sn + n).*;
|
|
if s.* != #char "0" first_trailing_zero_in_string = s + 1;
|
|
s += 1; n += 1;
|
|
}
|
|
|
|
trailing_zeroes_in_string = s - first_trailing_zero_in_string;
|
|
|
|
// trailing zeros
|
|
tz = pr - (l - 1);
|
|
pr = 0;
|
|
|
|
// dump expo
|
|
exponent[1] = exponent_character;
|
|
dp -= 1;
|
|
if dp < 0 {
|
|
exponent[2] = #char "-";
|
|
dp = -dp;
|
|
} else {
|
|
exponent[2] = #char "+";
|
|
}
|
|
|
|
n = ifx dp >= 100 then 5 else 4;
|
|
exponent[0] = cast,no_check(u8)n;
|
|
while true {
|
|
exponent[n] = cast,no_check(u8)(#char "0" + (dp % 10));
|
|
if n <= 3 break;
|
|
n -= 1;
|
|
dp /= 10;
|
|
}
|
|
|
|
cs = 1 + (thousand_group_size << 24); // how many tens
|
|
|
|
l = s - s_start; // get the length that we copied
|
|
s = s_start;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// This used to be finalize_string():
|
|
//
|
|
|
|
append_repeated_character :: (builder: *String_Builder, count: int, $character: u8) -> int {
|
|
result := count;
|
|
|
|
while count {
|
|
available_range := ensure_contiguous_space_and_return_available_range(builder);
|
|
clamped_count := min(count, available_range.count);
|
|
count -= clamped_count;
|
|
copy_repeated_character(available_range.data, clamped_count, character);
|
|
advance_through_ensured_space(builder, clamped_count);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
characters_removed_by_zero_removal: int;
|
|
|
|
if format.zero_removal != .NO && mode_to_use != .SHORTEST {
|
|
//
|
|
// :ZeroRemoval
|
|
//
|
|
// When we remove zeroes (and possibly the decimal separator itself), special behavior occurs when the width
|
|
// and/or trailing width are explicit (i.e., non-negative). When a width is set and a number of characters
|
|
// are removed by zero removal, the same number of spaces will be removed from any potential left padding,
|
|
// so that the printed number does not shift to the right. When a trailing width is set, any removed
|
|
// characters at the end will be replaced by spaces to maintain the width of the string.
|
|
//
|
|
// Note that the .SHORTEST mode already does its own version of zero removal, so using that in combination
|
|
// with a zero removal mode other than .YES might lead to somewhat surprising results.
|
|
//
|
|
|
|
if (decimal_digits_in_string || tz) && dp != INFINITY_OR_NAN {
|
|
if (decimal_digits_in_string > 0 && trailing_zeroes_in_string == decimal_digits_in_string) || (decimal_digits_in_string == 0 && tz > 0) {
|
|
if format.zero_removal == .YES {
|
|
characters_removed_by_zero_removal = trailing_zeroes_in_string + tz + 1;
|
|
l -= trailing_zeroes_in_string + 1;
|
|
tz = 0;
|
|
} else if format.zero_removal == .ONE_ZERO_AFTER_DECIMAL {
|
|
if trailing_zeroes_in_string {
|
|
characters_removed_by_zero_removal = (trailing_zeroes_in_string - 1) + tz;
|
|
l -= (trailing_zeroes_in_string - 1);
|
|
tz = 0;
|
|
} else {
|
|
characters_removed_by_zero_removal = tz - 1;
|
|
tz = 1;
|
|
}
|
|
}
|
|
} else if trailing_zeroes_in_string {
|
|
characters_removed_by_zero_removal = trailing_zeroes_in_string + tz;
|
|
l -= trailing_zeroes_in_string;
|
|
tz = 0;
|
|
} else if tz {
|
|
characters_removed_by_zero_removal = tz;
|
|
tz = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate fw (= leading/trailing space) and pr (= leading zeroes).
|
|
pr = max(pr, l);
|
|
|
|
n = pr + exponent[0] + tz;
|
|
if prefix_character n += 1;
|
|
|
|
fw = max(fw, n);
|
|
|
|
fw -= n;
|
|
pr -= l;
|
|
|
|
// Maintain number alignment by taking the number of characters removed by zero removal and removing that many spaces from the left padding. :ZeroRemoval
|
|
fw -= min(fw, characters_removed_by_zero_removal);
|
|
|
|
if !use_left_justification {
|
|
if left_pad_with_zeroes { // if leading zeros, everything is in pr
|
|
pr = ifx fw > pr then fw else pr;
|
|
fw = 0;
|
|
} else {
|
|
use_thousand_separators = false;
|
|
}
|
|
}
|
|
|
|
// copy the spaces and/or zeros
|
|
if fw + pr {
|
|
// copy leading spaces (or when doing %8.4d stuff)
|
|
if !use_left_justification total_string_length += append_repeated_character(builder, fw, #char " ");
|
|
|
|
// copy leader
|
|
if prefix_character { append(builder, prefix_character); total_string_length += 1; }
|
|
|
|
// copy leading zeros
|
|
c := cs >> 24;
|
|
cs &= 0xffffff;
|
|
cs = ifx use_thousand_separators then c - ((pr + cs) % (c + 1)) else 0;
|
|
|
|
if pr {
|
|
if !use_thousand_separators {
|
|
total_string_length += append_repeated_character(builder, pr, #char "0");
|
|
} else {
|
|
for 1..pr {
|
|
if cs == c {
|
|
cs = 0;
|
|
append(builder, thousand_separator_character); total_string_length += 1;
|
|
} else {
|
|
cs += 1;
|
|
append(builder, #char "0"); total_string_length += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if prefix_character { append(builder, prefix_character); total_string_length += 1; }
|
|
|
|
append(builder, s, l); total_string_length += l; // Copy number string.
|
|
|
|
total_string_length += append_repeated_character(builder, tz, #char "0"); // Copy trailing zeros.
|
|
|
|
append(builder, *exponent[1], exponent[0]); total_string_length += exponent[0]; // Copy exponent (if there is one).
|
|
|
|
// Add spaces back to compensate for removed characters. :ZeroRemoval
|
|
if format.trailing_width > -1 total_string_length += append_repeated_character(builder, characters_removed_by_zero_removal, #char " ");
|
|
|
|
// In case of left justify, put the spaces at the end.
|
|
if use_left_justification && fw > 0 total_string_length += append_repeated_character(builder, fw, #char " ");
|
|
|
|
|
|
return total_string_length;
|
|
}
|
|
|
|
|
|
|
|
#scope_file
|
|
|
|
default_precision :: 6;
|
|
thousand_group_size :: 3;
|
|
|
|
nan_label :: "NaN";
|
|
inf_label :: "Inf";
|
|
|
|
INFINITY_OR_NAN :: 0x7000;
|
|
|
|
|
|
|
|
// =======================================================================
|
|
// low level float utility functions
|
|
|
|
COPYFP :: inline (dest: *void, src: *void) {
|
|
(.*)cast(*u64)dest = (.*)cast(*u64)src;
|
|
}
|
|
|
|
bot :: float64.[0h3ff0000000000000, 0h4024000000000000, 0h4059000000000000, 0h408f400000000000, 0h40c3880000000000, 0h40f86a0000000000, 0h412e848000000000, 0h416312d000000000, 0h4197d78400000000, 0h41cdcd6500000000, 0h4202a05f20000000, 0h42374876e8000000, 0h426d1a94a2000000, 0h42a2309ce5400000, 0h42d6bcc41e900000, 0h430c6bf526340000, 0h4341c37937e08000, 0h4376345785d8a000, 0h43abc16d674ec800, 0h43e158e460913d00, 0h4415af1d78b58c40, 0h444b1ae4d6e2ef50, 0h4480f0cf064dd592];
|
|
negbot :: float64.[0h3fb999999999999a, 0h3f847ae147ae147b, 0h3f50624dd2f1a9fc, 0h3f1a36e2eb1c432d, 0h3ee4f8b588e368f1, 0h3eb0c6f7a0b5ed8d, 0h3e7ad7f29abcaf48, 0h3e45798ee2308c3a, 0h3e112e0be826d695, 0h3ddb7cdfd9d7bdbb, 0h3da5fd7fe1796495, 0h3d719799812dea11, 0h3d3c25c268497682, 0h3d06849b86a12b9b, 0h3cd203af9ee75616, 0h3c9cd2b297d889bc, 0h3c670ef54646d497, 0h3c32725dd1d243ac, 0h3bfd83c94fb6d2ac, 0h3bc79ca10c924223, 0h3b92e3b40a0e9b4f, 0h3b5e392010175ee6];
|
|
negboterr :: float64.[0hbc5999999999999a, 0hbc0eb851eb851eb8, 0hbbd89374bc6a7efa, 0hbbb6a161e4f765fe, 0hbb8ee78183f91e64, 0h3b4b5a63f9a49c2c, 0h3b15e1e99483b023, 0hbad03023df2d4c94, 0hbab34674bfabb83b, 0hba720a5465df8d2c, 0h3a47f7bc7b4d28aa, 0h39f97f27f0f6e886, 0hb9cecd79a5a0df95, 0h394ea70909833de7, 0hb97937831647f5a0, 0h3925b4c2ebe68799, 0hb90db7b2080a3029, 0hb8d7c628066e8cee, 0h388a52b31e9e3d07, 0h38675447a5d8e536, 0h383f769fb7e0b75e, 0hb7fa7566d9cba769];
|
|
top :: float64.[0h44b52d02c7e14af6, 0h497c06a5ec5433c6, 0h4e428bc8abe49f64, 0h53088ba3bf284e24, 0h57d03e29f5c2b18c, 0h5c957f48bb41db7c, 0h615c73892ecbfbf4, 0h6622d3d6f88f0b3d, 0h6ae8eb0138858d0a, 0h6fb07d457124123d, 0h7475d2ce55747a18, 0h793ce2137f743382, 0h7e031cfd3999f7b0];
|
|
negtop :: float64.[0h3b282db34012b251, 0h366244ce242c5561, 0h319b9b6364f30304, 0h2cd4dbf7b3f71cb7, 0h280f8587e7083e30, 0h2347d12a4670c123, 0h1e81fee341fc585d, 0h19bb31bb5dc320d2, 0h14f48c22ca71a1bd, 0h102f0ce4839198db, 0h0b677603725064a8, 0h06a1ba03f5b21000, 0h01dac9a7b3b7302f];
|
|
toperr :: float64.[0h4160000000000000, 0h45ebb542c80deb40, 0hcae83b80b9aab60a, 0hcfa32e22d17a166c, 0hd4523606902e180e, 0hd9296fb782462e87, 0hddf358952c0bd011, 0he2a78c1376a34b6c, 0he7817569fc243adf, 0hec5d9365a897aaa6, 0h7119050c256123a0, 0hf5db1799d76cc7a6, 0hfaa213fe39571a38];
|
|
negtoperr :: float64.[0h37c13badb829e079, 0hb2ee46a98d3d9f64, 0h2e3227c7218a2b65, 0h2951d96999aa01e9, 0ha4acc2229efc3962, 0h9fecd04a2263407a, 0h9b123b80f187a157, 0h965c4e22914ed912, 0h119bc296cdf42f82, 0h8cc9f9e7f4e16fe1, 0h880aeb0a72a8902a, 0h834e228e12c13408, 0h0000000000fa1259];
|
|
|
|
powten :: u64.[1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000, 10000000000000000000];
|
|
tento19th : u64 : 1000000000000000000;
|
|
|
|
ddmult_high :: (oh: float64, ol: float64, xh: float64, yh: float64) -> (oh: float64, ol: float64) {
|
|
ahi: float64 = 0;
|
|
bhi: float64 = 0;
|
|
bt: s64 = ---;
|
|
|
|
new_oh := xh*yh;
|
|
|
|
COPYFP(*bt, *xh);
|
|
bt &= cast,no_check(s64)((~cast,no_check(u64)0) << 27);
|
|
COPYFP(*ahi, *bt);
|
|
alo := xh - ahi;
|
|
|
|
COPYFP(*bt, *yh);
|
|
bt &= cast,no_check(s64)((~cast,no_check(u64)0) << 27);
|
|
COPYFP(*bhi, *bt);
|
|
blo := yh - bhi;
|
|
|
|
new_ol := ((ahi * bhi - new_oh) + ahi * blo + alo * bhi) + alo * blo;
|
|
|
|
return new_oh, new_ol;
|
|
}
|
|
|
|
ddtoS64 :: inline (xh: float64, xl: float64) -> s64 {
|
|
bits := cast,no_check(s64)xh;
|
|
vh := cast,no_check(float64) bits;
|
|
|
|
ahi := xh - vh;
|
|
t := ahi - xh;
|
|
alo := (xh - (ahi - t)) - (vh + t);
|
|
bits += cast,no_check(s64)(ahi + alo + xl);
|
|
|
|
return bits;
|
|
}
|
|
|
|
ddrenorm :: (oh: float64, ol: float64) -> (oh: float64, ol: float64) {
|
|
s := cast,no_check(float64)(oh + ol);
|
|
return s, ol - (s - oh);
|
|
}
|
|
|
|
ddmult_low :: inline (ol: float64, xh: float64, xl: float64, yh: float64, yl: float64) -> (ol: float64) {
|
|
return ol + xh*yl + xl*yh;
|
|
}
|
|
|
|
ddmult_low_small :: inline (ol: float64, xh: float64, yl: float64) -> (ol: float64) {
|
|
return ol + xh*yl;
|
|
}
|
|
|
|
raise_to_power10 :: (ohi: *float64, olo: *float64, d: float64, power: s32 /* power can be -323 to +350 */) {
|
|
ph, pl: float64;
|
|
if (power >= 0) && (power <= 22) {
|
|
ph, pl = ddmult_high(ph, pl, d, bot[power]);
|
|
} else {
|
|
e, et, eb: s32;
|
|
p2h, p2l: float64;
|
|
|
|
e = power;
|
|
if power < 0 e = -e;
|
|
et = (e * 0x2c9) >> 14;/* %23 */
|
|
if et > 13 et = 13;
|
|
eb = e - (et * 23);
|
|
|
|
ph = d; pl = 0;
|
|
if power < 0 {
|
|
if eb {
|
|
eb -= 1;
|
|
ph, pl = ddmult_high(ph, pl, d, negbot[eb]);
|
|
pl = ddmult_low_small(pl, d, negboterr[eb]);
|
|
}
|
|
|
|
if et {
|
|
ph, pl = ddrenorm(ph, pl);
|
|
et -= 1;
|
|
p2h, p2l = ddmult_high(p2h, p2l, ph, negtop[et]);
|
|
p2l = ddmult_low(p2l, ph, pl, negtop[et], negtoperr[et]);
|
|
ph = p2h;
|
|
pl = p2l;
|
|
}
|
|
} else {
|
|
if eb {
|
|
e = eb;
|
|
if eb > 22 eb = 22;
|
|
e -= eb;
|
|
ph, pl = ddmult_high(ph, pl, d, bot[eb]);
|
|
if e {
|
|
ph, pl = ddrenorm(ph, pl);
|
|
p2h, p2l = ddmult_high(p2h, p2l, ph, bot[e]);
|
|
p2l = ddmult_low_small(p2l, bot[e], pl);
|
|
ph = p2h;
|
|
pl = p2l;
|
|
}
|
|
}
|
|
|
|
if et {
|
|
ph, pl = ddrenorm(ph, pl);
|
|
et -= 1;
|
|
p2h, p2l = ddmult_high(p2h, p2l, ph, top[et]);
|
|
p2l = ddmult_low(p2l, ph, pl, top[et], toperr[et]);
|
|
ph = p2h;
|
|
pl = p2l;
|
|
}
|
|
}
|
|
}
|
|
|
|
ph, pl = ddrenorm(ph, pl);
|
|
ohi.* = ph;
|
|
olo.* = pl;
|
|
}
|
|
|
|
// given a float value, returns the significant bits in bits, and the position of the
|
|
// decimal point in decimal_pos. +/-INF and NAN are specified by special values
|
|
// returned in the decimal_pos parameter.
|
|
// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000
|
|
real_to_str :: (start: **u8, len: *int, out: *u8, decimal_pos: *int, value: float64, frac_digits: u32) -> bool {
|
|
bits: s64;
|
|
e, tens: s32;
|
|
d := value;
|
|
|
|
COPYFP(*bits, *d);
|
|
expo := cast,no_check(s32)((bits >> 52) & 2047);
|
|
is_negative := cast,no_check(bool)(bits >> 63);
|
|
if is_negative d = -d;
|
|
|
|
if expo == 2047 { // is nan or inf?
|
|
if bits & cast,no_check(s64)(((cast,no_check(u64)1) << 52) - 1) {
|
|
start.* = nan_label.data;
|
|
len.* = nan_label.count;
|
|
} else {
|
|
start.* = inf_label.data;
|
|
len.* = inf_label.count;
|
|
}
|
|
|
|
decimal_pos.* = INFINITY_OR_NAN;
|
|
return is_negative;
|
|
}
|
|
|
|
if expo == 0 { // is zero or denormal
|
|
if (bits << 1) == 0 { // do zero
|
|
decimal_pos.* = 1;
|
|
start.*= out;
|
|
out.* = #char "0";
|
|
len.* = 1;
|
|
return is_negative;
|
|
}
|
|
|
|
// find the right expo for denormals
|
|
{
|
|
one : u64 : 1;
|
|
v: s64 = cast,no_check(s64)(one << 51);
|
|
while (bits & v) == 0 {
|
|
expo -= 1;
|
|
v >>= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// find the decimal exponent as well as the decimal bits of the value
|
|
{
|
|
ph, pl: float64;
|
|
|
|
// log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046
|
|
tens = expo - 1023;
|
|
tens = ifx tens < 0 then (tens * 617) / 2048 else ((tens * 1233) / 4096) + 1;
|
|
|
|
// move the significant bits into position and stick them into an int
|
|
raise_to_power10(*ph, *pl, d, 18 - tens);
|
|
|
|
// get full as much precision from double-double as possible
|
|
bits = ddtoS64(ph, pl);
|
|
|
|
// check if we undershot
|
|
if cast(u64)bits >= tento19th tens += 1;
|
|
}
|
|
|
|
// now do the rounding in integer land
|
|
#no_aoc { // In some (legit?) cases the tens + frac_digits can overflow.
|
|
frac_digits = ifx frac_digits & 0x80000000 then (frac_digits & 0x7ffffff) + 1 else cast,no_check(u32)tens + frac_digits;
|
|
}
|
|
|
|
if frac_digits < 24 {
|
|
dg: u32 = 1;
|
|
if cast,no_check(u64)bits >= powten[9] dg = 10;
|
|
|
|
done_fracing := false;
|
|
while cast,no_check(u64)bits >= powten[dg] {
|
|
dg += 1;
|
|
if dg == 20 {
|
|
done_fracing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if !done_fracing && frac_digits < dg {
|
|
// add 0.5 at the right position and round
|
|
e = cast,no_check(s32)dg - cast,no_check(s32)frac_digits;
|
|
if cast,no_check(u32)e < 24 {
|
|
r: u64 = powten[e];
|
|
bits = bits + cast,no_check(s64)(r / 2);
|
|
if cast,no_check(u64)bits >= powten[dg]
|
|
tens += 1;
|
|
bits /= cast,no_check(s64)r;
|
|
}
|
|
}
|
|
}
|
|
|
|
// kill long trailing runs of zeros
|
|
if bits {
|
|
done_killing := false;
|
|
while true {
|
|
if bits <= 0xffffffff break;
|
|
if bits % 1000 {
|
|
done_killing = true;
|
|
break;
|
|
}
|
|
bits /= 1000;
|
|
}
|
|
if !done_killing {
|
|
n := cast,no_check(u32)bits;
|
|
while (n % 1000) == 0
|
|
n /= 1000;
|
|
bits=n;
|
|
}
|
|
}
|
|
|
|
// convert to string
|
|
out += 64;
|
|
e = 0;
|
|
|
|
digitpair :: "00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899";
|
|
|
|
while true {
|
|
n: u32 = ---;
|
|
o := out - 8;
|
|
// do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned)
|
|
if bits >= 100000000 {
|
|
n = cast,no_check(u32)(bits % 100000000);
|
|
bits /= 100000000;
|
|
} else {
|
|
n = cast,no_check(u32)bits;
|
|
bits = 0;
|
|
}
|
|
|
|
while n {
|
|
out -= 2;
|
|
(.*)cast,no_check(*u16)out = (.*)cast,no_check(*u16)*digitpair[(n % 100) * 2];
|
|
n /= 100;
|
|
e += 2;
|
|
}
|
|
|
|
if bits == 0 {
|
|
if e && out.* == #char "0" {
|
|
out += 1;
|
|
e -= 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
while out != o {
|
|
out -= 1;
|
|
out.* = #char "0";
|
|
e += 1;
|
|
}
|
|
}
|
|
|
|
decimal_pos.* = tens;
|
|
start.*= out;
|
|
len.* = e;
|
|
|
|
return is_negative;
|
|
}
|
|
|