From: YongHao Hu Subject: msvcrt: Use Dragon4 algorithm to fix inconsistent rounding behaviour for sprintf and add tests. The Dragon4 algorithm implementation is based on http://www.ryanjuckett.com/programming/printing-floating-point-numbers/. Message-Id: <551267E6.1090900@gmail.com> Date: Wed, 25 Mar 2015 15:46:46 +0800 Try 3: - Superseded Patch 109456. - Use appropriate win32 types instead of typedef. (Thanks Dmitry Timoshkov) Try 2: - Superseded Patch 109432 - Use typedef instead of C99 types - Delete useless #ifndef - Add memset to test Try1: - Fix bug https://bugs.winehq.org/show_bug.cgi?id=37913 . - The Dragon4 algorithm implementation is based on http://www.ryanjuckett.com/programming/printing-floating-point-numbers/. --- dlls/msvcrt/dragon4.h | 668 ++++++++++++++++++++++++++++++++++++++++++ dlls/msvcrt/printf.h | 43 +-- dlls/msvcrt/tests/printf.c | 96 ++++++ dlls/msvcrt/wine_printfloat.h | 246 ++++++++++++++++ 4 files changed, 1036 insertions(+), 17 deletions(-) create mode 100644 dlls/msvcrt/dragon4.h create mode 100644 dlls/msvcrt/wine_printfloat.h diff --git a/dlls/msvcrt/dragon4.h b/dlls/msvcrt/dragon4.h new file mode 100644 index 0000000..b3c7df5 --- /dev/null +++ b/dlls/msvcrt/dragon4.h @@ -0,0 +1,668 @@ +/* + * Dragon4 algorithm implementation. + * Derived from code released under the ZLIB license. + * Copyright (c) 2014 Ryan Juckett. + * More details about this implementation on + * http://www.ryanjuckett.com/programming/printing-floating-point-numbers/ + * + * Modified for Wine use by YongHao Hu. + */ +#ifndef Wine__Dragon4_h +#define Wine__Dragon4_h +#ifdef small +#undef small +#endif +#define BIGINT_MAX_BLOCKS 35 + +static UINT32 LogBase2(UINT32 val) +{ + const UINT8 LogTable[256] = + { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + }; + + UINT32 temp; + + temp = val >> 24; + if(temp) + return 24 + LogTable[temp]; + + temp = val >> 16; + if(temp) + return 16 + LogTable[temp]; + + temp = val >> 8; + if(temp) + return 8 + LogTable[temp]; + + return LogTable[val]; +} + +typedef enum { + CutoffMode_TotalLength, + CutoffMode_FractionLength, +} CutoffMode; + +typedef struct { + UINT32 m_length; + UINT32 m_blocks[BIGINT_MAX_BLOCKS]; +} BigInt; + +static inline void SetU64(ULONG64 val, UINT32 *m_length, UINT32 *m_blocks) { + if(val > 0xFFFFFFFF) { + m_blocks[0] = val & 0xFFFFFFFF; + m_blocks[1] = (val >> 32) & 0xFFFFFFFF; + *m_length = 2; + }else if(val != 0) { + m_blocks[0] = val & 0xFFFFFFFF; + *m_length = 1; + }else { + *m_length = 0; + } +} + +static inline void SetU32(UINT32 val, UINT32 *m_length, UINT32 *m_blocks) { + if(val != 0) { + m_blocks[0] = val; + *m_length = (val != 0); + }else { + *m_length = 0; + } +} + +static INT32 BigInt_Compare(const BigInt *lhs, const BigInt * rhs) +{ + INT32 i, length_diff = lhs->m_length - rhs->m_length; + if (length_diff != 0) + return length_diff; + + for(i=lhs->m_length-1; i>=0; --i) { + if(lhs->m_blocks[i] == rhs->m_blocks[i]) + continue; + else if(lhs->m_blocks[i] > rhs->m_blocks[i]) + return 1; + else + return -1; + } + return 0; +} + +static void BigInt_Add(BigInt *result, const BigInt *lhs, const BigInt *rhs) +{ + const BigInt * large; + const BigInt * small; + UINT32 large_len, small_len, carry, *result_cur; + const UINT32 *large_cur, *large_end, *small_cur, *small_end; + ULONG64 sum; + if(lhs->m_length < rhs->m_length) { + small = lhs; + large = rhs; + }else { + small = rhs; + large = lhs; + } + large_len = large->m_length; + small_len = small->m_length; + + result->m_length = large_len; + + carry = 0; + large_cur = large->m_blocks; + large_end = large_cur + large_len; + small_cur = small->m_blocks; + small_end = small_cur + small_len; + result_cur = result->m_blocks; + while(small_cur != small_end) { + sum = carry + (ULONG64)(*large_cur) + (ULONG64)(*small_cur); + carry = sum >> 32; + (*result_cur) = sum & 0xFFFFFFFF; + ++large_cur; + ++small_cur; + ++result_cur; + } + + while(large_cur != large_end) { + sum = carry + (ULONG64)(*large_cur); + carry = sum >> 32; + (*result_cur) = sum & 0xFFFFFFFF; + ++large_cur; + ++result_cur; + } + + if(carry != 0) { + *result_cur = 1; + result->m_length = large_len + 1; + }else { + result->m_length = large_len; + } +} + +static void BigInt_Multiply(BigInt *result, const BigInt *lhs, const BigInt *rhs) +{ + const BigInt *large; + const BigInt *small; + UINT32 max_result_len, *cur, *end, *result_start; + const UINT32 *large_begin, *large_end, *small_cur, *small_end; + if(lhs->m_length < rhs->m_length) { + small = lhs; + large = rhs; + }else { + small = rhs; + large = lhs; + } + + max_result_len = large->m_length + small->m_length; + + for(cur=result->m_blocks, end=cur+max_result_len; cur!=end; ++cur) + *cur = 0; + + large_begin = large->m_blocks; + large_end = large_begin + large->m_length; + + result_start = result->m_blocks; + + small_cur = small->m_blocks; + small_end = small_cur + small->m_length; + for(; small_cur!=small_end; ++small_cur, ++result_start) { + const UINT32 multiplier = *small_cur; + if(multiplier != 0) { + const UINT32 *large_cur = large_begin; + UINT32 *result_cur = result_start; + ULONG64 carry = 0; + do { + ULONG64 product = (*result_cur) + (*large_cur)*(ULONG64)multiplier + carry; + carry = product >> 32; + *result_cur = product & 0xFFFFFFFF; + ++large_cur; + ++result_cur; + } while(large_cur != large_end); + + *result_cur = (UINT32)(carry & 0xFFFFFFFF); + } + } + + if(max_result_len>0 && result->m_blocks[max_result_len-1]==0) + result->m_length = max_result_len - 1; + else + result->m_length = max_result_len; +} + +static void BigInt_Multiply_Uint(BigInt *result, const BigInt*lhs, UINT32 rhs) +{ + UINT32 carry = 0, *result_cur = result->m_blocks; + const UINT32 *lhs_cur = lhs->m_blocks; + const UINT32 *lhs_end = lhs->m_blocks + lhs->m_length; + for( ; lhs_cur!=lhs_end; ++lhs_cur, ++result_cur) { + ULONG64 product = (ULONG64)(*lhs_cur) * rhs + carry; + *result_cur = (UINT32)(product & 0xFFFFFFFF); + carry = product >> 32; + } + + if(carry != 0) { + *result_cur = (UINT32)carry; + result->m_length = lhs->m_length + 1; + }else { + result->m_length = lhs->m_length; + } +} + +static void BigInt_Multiply2(BigInt *result, const BigInt *in) +{ + UINT32 carry = 0, *result_cur = result->m_blocks; + const UINT32 *lhs_cur = in->m_blocks; + const UINT32 *lhs_end = in->m_blocks + in->m_length; + for( ; lhs_cur!=lhs_end; ++lhs_cur, ++result_cur) { + UINT32 cur = *lhs_cur; + *result_cur = (cur << 1) | carry; + carry = cur >> 31; + } + + if(carry != 0) { + *result_cur = carry; + result->m_length = in->m_length + 1; + }else { + result->m_length = in->m_length; + } +} + +static void SingleBigInt_Multiply2(BigInt *result) +{ + UINT32 carry = 0; + UINT32 *cur = result->m_blocks; + UINT32 *end = result->m_blocks + result->m_length; + for( ; cur!=end; ++cur) { + UINT32 cur_temp = *cur; + *cur = (cur_temp << 1) | carry; + carry = cur_temp >> 31; + } + + if(carry != 0) { + *cur = carry; + ++result->m_length; + } +} + +static void BigInt_Multiply10(BigInt *result) +{ + ULONG64 carry = 0; + + UINT32 *cur = result->m_blocks; + UINT32 *end = result->m_blocks + result->m_length; + for( ; cur!=end; ++cur) { + ULONG64 product = (ULONG64)(*cur) * 10ull + carry; + (*cur) = (UINT32)(product & 0xFFFFFFFF); + carry = product >> 32; + } + + if(carry != 0) { + *cur = (UINT32)carry; + ++result->m_length; + } +} + +static const UINT32 g_PowerOf10_U32[] = +{ + 1, /* 10 ^ 0 */ + 10, /* 10 ^ 1 */ + 100, /* 10 ^ 2 */ + 1000, /* 10 ^ 3 */ + 10000, /* 10 ^ 4 */ + 100000, /* 10 ^ 5 */ + 1000000, /* 10 ^ 6 */ + 10000000, /* 10 ^ 7 */ +}; + +static const BigInt g_PowerOf10_Big[] = +{ + /* 10 ^ 8 */ + { 1, { 100000000 } }, + /* 10 ^ 16 */ + { 2, { 0x6fc10000, 0x002386f2 } }, + /* 10 ^ 32 */ + { 4, { 0x00000000, 0x85acef81, 0x2d6d415b, 0x000004ee, } }, + /* 10 ^ 64 */ + { 7, { 0x00000000, 0x00000000, 0xbf6a1f01, 0x6e38ed64, 0xdaa797ed, 0xe93ff9f4, 0x00184f03, } }, + /* 10 ^ 128 */ + { 14, { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x2e953e01, 0x03df9909, 0x0f1538fd, + 0x2374e42f, 0xd3cff5ec, 0xc404dc08, 0xbccdb0da, 0xa6337f19, 0xe91f2603, 0x0000024e, } }, + /* 10 ^ 256 */ + { 27, { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x982e7c01, 0xbed3875b, 0xd8d99f72, 0x12152f87, 0x6bde50c6, 0xcf4a6e70, + 0xd595d80f, 0x26b2716e, 0xadc666b0, 0x1d153624, 0x3c42d35a, 0x63ff540e, 0xcc5573c0, + 0x65f9ef17, 0x55bc28f2, 0x80dcc7f7, 0xf46eeddc, 0x5fdcefce, 0x000553f7, } } +}; + +static void BigInt_Pow10(BigInt * result, UINT32 exponent) +{ + + BigInt temp1; + BigInt temp2; + BigInt *cur_temp = &temp1; + BigInt *next_temp = &temp2; + UINT32 small_exponent, table_index; + small_exponent = exponent & 0x7; + SetU32(g_PowerOf10_U32[small_exponent], &cur_temp->m_length, cur_temp->m_blocks); + + exponent >>= 3; + table_index = 0; + + while(exponent != 0) { + if(exponent & 1) { + BigInt *swap; + BigInt_Multiply(next_temp, cur_temp, &g_PowerOf10_Big[table_index] ); + + swap = cur_temp; + cur_temp = next_temp; + next_temp = swap; + } + + ++table_index; + exponent >>= 1; + } + + *result = *cur_temp; +} + +static void BigInt_MultiplyPow10(BigInt *result, const BigInt *in, UINT32 exponent) +{ + + BigInt temp1; + BigInt temp2; + BigInt *cur_temp = &temp1; + BigInt *next_temp = &temp2; + + UINT32 small_exponent = exponent & 0x7; + UINT32 table_index = 0; + if(small_exponent != 0) + BigInt_Multiply_Uint(cur_temp, in, g_PowerOf10_U32[small_exponent]); + else + *cur_temp = *in; + + exponent >>= 3; + + while(exponent != 0) { + if(exponent & 1) { + BigInt *swap; + BigInt_Multiply(next_temp, cur_temp, &g_PowerOf10_Big[table_index]); + + swap = cur_temp; + cur_temp = next_temp; + next_temp = swap; + } + + ++table_index; + exponent >>= 1; + } + + *result = *cur_temp; +} + +static void BigInt_Pow2(BigInt * result, UINT32 exponent) +{ + UINT32 block_index = exponent / 32; + UINT32 bit_index = exponent % 32, i; + for(i=0; i<=block_index; ++i) + result->m_blocks[i] = 0; + + result->m_length = block_index + 1; + + result->m_blocks[block_index] |= (1 << bit_index); +} + +static UINT32 BigInt_DivideWithRemainder_MaxQuotient9(BigInt *dividend, const BigInt *divisor) +{ + UINT32 length = divisor->m_length; + const UINT32 *final_divisor_block = divisor->m_blocks + length - 1; + UINT32 *final_dividend_block = dividend->m_blocks + length - 1; + UINT32 quotient = *final_dividend_block / (*final_divisor_block+1); + + if(dividend->m_length < divisor->m_length) + return 0; + + if(quotient != 0) { + const UINT32 *divisor_cur = divisor->m_blocks; + UINT32 *dividend_cur = dividend->m_blocks; + + ULONG64 borrow = 0, carry = 0; + do { + ULONG64 product = (ULONG64)*divisor_cur * (ULONG64)quotient + carry; + ULONG64 difference = (ULONG64)*dividend_cur - (product & 0xFFFFFFFF) - borrow; + carry = product >> 32; + + borrow = (difference >> 32) & 1; + + *dividend_cur = difference & 0xFFFFFFFF; + + ++divisor_cur; + ++dividend_cur; + } while(divisor_cur <= final_divisor_block); + + while(length>0 && dividend->m_blocks[length-1]==0) + --length; + + dividend->m_length = length; + } + + if(BigInt_Compare(dividend, divisor) >= 0) { + const UINT32 *divisor_cur = divisor->m_blocks; + UINT32 *dividend_cur = dividend->m_blocks; + ULONG64 borrow = 0; + ++quotient; + + do{ + ULONG64 difference = (ULONG64)*dividend_cur - (ULONG64)*divisor_cur - borrow; + borrow = (difference >> 32) & 1; + + *dividend_cur = difference & 0xFFFFFFFF; + + ++divisor_cur; + ++dividend_cur; + } while(divisor_cur <= final_divisor_block); + + while(length>0 && dividend->m_blocks[length-1]==0) + --length; + + dividend->m_length = length; + } + return quotient; +} + +static void BigInt_ShiftLeft(BigInt *result, UINT32 shift) +{ + UINT32 shift_blocks = shift / 32, shift_bits = shift % 32; + + INT32 in_length = result->m_length; + const UINT32 *in_blocks = result->m_blocks; + + if(shift_bits == 0) { + UINT32 *in_cur = result->m_blocks+in_length, *out_cur = in_cur+shift_blocks; + UINT32 i = 0; + for ( ; in_cur>=in_blocks; --in_cur, --out_cur) + *out_cur = *in_cur; + + for(i=0; im_blocks[i] = 0; + + result->m_length += shift_blocks; + }else { + INT32 in_block_index = in_length - 1; + UINT32 out_block_index = in_length + shift_blocks; + const UINT32 low_bits_shift = (32 - shift_bits); + UINT32 high_bits = 0, i = 0, block, low_bits; + result->m_length = out_block_index + 1; + + block = result->m_blocks[in_block_index]; + low_bits = block >> low_bits_shift; + while(in_block_index > 0) { + result->m_blocks[out_block_index] = high_bits | low_bits; + high_bits = block << shift_bits; + + --in_block_index; + --out_block_index; + + block = result->m_blocks[in_block_index]; + low_bits = block >> low_bits_shift; + } + + result->m_blocks[out_block_index] = high_bits | low_bits; + result->m_blocks[out_block_index-1] = block << shift_bits; + + for(i=0; im_blocks[i] = 0; + + if(result->m_blocks[result->m_length-1] == 0) + --result->m_length; + } +} + +static UINT32 dragon4(const ULONG64 mantissa, const INT32 exponent, const UINT32 mantissa_high_bit_idx, + const BOOL has_unequal_margins, const CutoffMode cutoffMode, UINT32 cutoffNumber, + char *out_buffer, UINT32 buffer_size, INT32 *out_exponent) +{ + char *cur_digit = out_buffer; + BigInt scale; + BigInt scaled_value; + BigInt scaled_margin_low; + BigInt scaled_value_high; + + BigInt *scaled_margin_high; + BigInt optional_margin_high; + const double log10_2 = 0.30102999566398119521373889472449; + INT32 digit_exponent, cut_off_exponent, compare; + UINT32 highest_block, output_digit; + BOOL round_down = FALSE; + if(mantissa == 0) { + *cur_digit = '0'; + *out_exponent = 0; + return 1; + } + + if(has_unequal_margins) { + if(exponent > 0) { + SetU64(4*mantissa, &scaled_value.m_length, scaled_value.m_blocks); + BigInt_ShiftLeft( &scaled_value, exponent ); + + SetU32(4, &scale.m_length, scale.m_blocks); + + BigInt_Pow2(&scaled_margin_low, exponent); + + BigInt_Pow2(&optional_margin_high, exponent+1); + }else { + SetU64(4*mantissa, &scaled_value.m_length, scaled_value.m_blocks); + + BigInt_Pow2(&scale, -exponent+2); + + SetU32(1, &scaled_margin_low.m_length, scaled_margin_low.m_blocks); + + SetU32(2, &optional_margin_high.m_length, optional_margin_high.m_blocks); + } + + scaled_margin_high = &optional_margin_high; + }else { + if(exponent > 0) { + SetU64(2*mantissa, &scaled_value.m_length, scaled_value.m_blocks); + BigInt_ShiftLeft(&scaled_value, exponent); + + SetU32(2, &scale.m_length, scale.m_blocks); + + BigInt_Pow2(&scaled_margin_low, exponent); + }else { + SetU64(2*mantissa, &scaled_value.m_length, scaled_value.m_blocks); + + BigInt_Pow2(&scale, -exponent+1); + + SetU32(1, &scaled_margin_low.m_length, scaled_margin_low.m_blocks); + } + + scaled_margin_high = &scaled_margin_low; + } + + digit_exponent = (INT32)(ceil( (double)((INT32)mantissa_high_bit_idx + exponent) * log10_2 - 0.69 )); + + if(cutoffMode==CutoffMode_FractionLength && digit_exponent<=-(INT32)cutoffNumber) + digit_exponent = -(INT32)cutoffNumber + 1; + + if(digit_exponent > 0) { + BigInt temp; + BigInt_MultiplyPow10(&temp, &scale, digit_exponent); + scale = temp; + }else if(digit_exponent < 0) { + BigInt pow10; + BigInt temp; + BigInt_Pow10(&pow10, -digit_exponent); + + BigInt_Multiply(&temp, &scaled_value, &pow10); + scaled_value = temp; + + BigInt_Multiply(&temp, &scaled_margin_low, &pow10); + scaled_margin_low = temp; + + if (scaled_margin_high != &scaled_margin_low) + BigInt_Multiply2(scaled_margin_high, &scaled_margin_low); + } + + BigInt_Add(&scaled_value_high, &scaled_value, scaled_margin_high); + if(BigInt_Compare(&scaled_value_high, &scale) >= 0) { + digit_exponent = digit_exponent + 1; + }else { + BigInt_Multiply10(&scaled_value); + BigInt_Multiply10(&scaled_margin_low); + if(scaled_margin_high != &scaled_margin_low) + BigInt_Multiply2(scaled_margin_high, &scaled_margin_low); + } + + cut_off_exponent = digit_exponent - buffer_size; + switch(cutoffMode) { + case CutoffMode_TotalLength: { + INT32 desiredCutoffExponent = digit_exponent - (INT32)cutoffNumber; + if (desiredCutoffExponent > cut_off_exponent) + cut_off_exponent = desiredCutoffExponent; + break; + } + + case CutoffMode_FractionLength: { + INT32 desiredCutoffExponent = -(INT32)cutoffNumber; + if (desiredCutoffExponent > cut_off_exponent) + cut_off_exponent = desiredCutoffExponent; + break; + } + default: break; + } + + *out_exponent = digit_exponent-1; + + highest_block = scale.m_blocks[scale.m_length-1]; + if(highest_block<8 || highest_block>429496729) { + UINT32 highest_block_log2 = LogBase2(highest_block); + UINT32 shift = (32 + 27 - highest_block_log2) % 32; + + BigInt_ShiftLeft(&scale, shift); + BigInt_ShiftLeft(&scaled_value, shift); + BigInt_ShiftLeft(&scaled_margin_low, shift); + if(scaled_margin_high != &scaled_margin_low) + BigInt_Multiply2(scaled_margin_high, &scaled_margin_low); + } + + for(;;) { + digit_exponent = digit_exponent-1; + output_digit = BigInt_DivideWithRemainder_MaxQuotient9(&scaled_value, &scale); + + if(scaled_value.m_length==0 || (digit_exponent==cut_off_exponent)) + break; + + *cur_digit = (char)('0' + output_digit); + ++cur_digit; + + BigInt_Multiply10(&scaled_value); + } + + SingleBigInt_Multiply2(&scaled_value); + compare = BigInt_Compare(&scaled_value, &scale); + round_down = (compare<0); + + if(compare == 0) + round_down = FALSE; + + if(round_down) { + *cur_digit = (char)('0' + output_digit); + ++cur_digit; + }else { + if(output_digit == 9) { + for(;;) { + if(cur_digit == out_buffer) { + *cur_digit = '1'; + ++cur_digit; + *out_exponent += 1; + break; + } + + --cur_digit; + if(*cur_digit != '9') { + *cur_digit += 1; + ++cur_digit; + break; + } + } + }else { + *cur_digit = (char)('0' + output_digit + 1); + ++cur_digit; + } + } + return cur_digit - out_buffer; +} +#endif diff --git a/dlls/msvcrt/printf.h b/dlls/msvcrt/printf.h index 80133d6..078da42 100644 --- a/dlls/msvcrt/printf.h +++ b/dlls/msvcrt/printf.h @@ -26,6 +26,8 @@ #define FUNC_NAME(func) func ## _a #endif +#include "wine_printfloat.h" + typedef struct FUNC_NAME(pf_flags_t) { APICHAR Sign, LeftAlign, Alternate, PadZero; @@ -225,19 +227,6 @@ static inline int FUNC_NAME(pf_handle_string)(FUNC_NAME(puts_clbk) pf_puts, void return FUNC_NAME(pf_output_format_wstr)(pf_puts, puts_ctx, str, len, flags, locinfo); } -static inline void FUNC_NAME(pf_rebuild_format_string)(char *p, FUNC_NAME(pf_flags) *flags) -{ - *p++ = '%'; - if(flags->Alternate) - *p++ = flags->Alternate; - if(flags->Precision >= 0) { - sprintf(p, ".%d", flags->Precision); - p += strlen(p); - } - *p++ = flags->Format; - *p++ = 0; -} - /* pf_integer_conv: prints x to buf, including alternate formats and additional precision digits, but not field characters or the sign */ static inline void FUNC_NAME(pf_integer_conv)(APICHAR *buf, int buf_len, @@ -535,8 +524,8 @@ int FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, const API if(tmp != buf) HeapFree(GetProcessHeap(), 0, tmp); } else if(flags.Format && strchr("aeEfgG", flags.Format)) { - char float_fmt[20], buf_a[32], *tmp = buf_a, *decimal_point; - int len = flags.Precision + 10; + char buf_a[32], *tmp = buf_a, *decimal_point; + int len = flags.Precision + 10, exponent = 0, precision = 0; double val = pf_args(args_ctx, pos, VT_R8, valist).get_double; int r; BOOL inf = FALSE, nan = FALSE; @@ -578,13 +567,33 @@ int FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, const API if(!tmp) return -1; - FUNC_NAME(pf_rebuild_format_string)(float_fmt, &flags); if(val < 0) { flags.Sign = '-'; val = -val; } - sprintf(tmp, float_fmt, val); + if(flags.Precision < 0) + precision = 6; + else if(flags.Precision==0 && toupper(flags.Format)=='G') + precision = 1; + else + precision = flags.Precision; + + if(toupper(flags.Format)=='E' || toupper(flags.Format)=='G') { + if(toupper(flags.Format) == 'G') { + exponent = floor(log10(val)); + if(precision>exponent && exponent>=-4) { + precision = precision - 1 - exponent; + wine_print_double(tmp, len, val, PrintFloatFormat_Positional, precision, flags.Format, flags.Alternate); + }else { + precision -= 1; + wine_print_double(tmp, len, val, PrintFloatFormat_Scientific, precision, flags.Format, flags.Alternate); + } + }else if(toupper(flags.Format) == 'E') + wine_print_double(tmp, len, val, PrintFloatFormat_Scientific, precision, flags.Format, flags.Alternate); + }else if(toupper(flags.Format) == 'F') + wine_print_double(tmp, len, val, PrintFloatFormat_Positional, precision, flags.Format, flags.Alternate); + if(toupper(flags.Format)=='E' || toupper(flags.Format)=='G') FUNC_NAME(pf_fixup_exponent)(tmp); diff --git a/dlls/msvcrt/tests/printf.c b/dlls/msvcrt/tests/printf.c index 0606c25..4233985 100644 --- a/dlls/msvcrt/tests/printf.c +++ b/dlls/msvcrt/tests/printf.c @@ -86,6 +86,12 @@ static void test_sprintf( void ) int x, r; WCHAR wide[] = { 'w','i','d','e',0}; + memset(buffer,0,sizeof(buffer)); + format = "%.0f"; + r = sprintf(buffer,format,pnumber); + ok(!strcmp(buffer,"789456123")," failed: '%s'\n", buffer); + ok( r==9, "return count wrong\n"); + format = "%+#23.15e"; r = sprintf(buffer,format,pnumber); ok(!strcmp(buffer,"+7.894561230000000e+008"),"+#23.15e failed: '%s'\n", buffer); @@ -547,6 +553,96 @@ static void test_sprintf( void ) ok(!strcmp(buffer,"1"), "failed\n"); ok( r==1, "return count wrong\n"); + memset(buffer,0,sizeof(buffer)); + format = "%#.0f"; + r = sprintf(buffer, format,0.6); + ok(!strcmp(buffer,"1."), "failed\n"); + ok( r==2, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.0g"; + r = sprintf(buffer, format,0.654); + ok(!strcmp(buffer,"0.7"), "failed\n"); + ok( r==3, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.1g"; + r = sprintf(buffer, format,0.654); + ok(!strcmp(buffer,"0.7"), "failed\n"); + ok( r==3, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.2g"; + r = sprintf(buffer, format,0.654); + ok(!strcmp(buffer,"0.65"), "failed\n"); + ok( r==4, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.1f"; + r = sprintf(buffer, format,-3.25); + ok(!strcmp(buffer,"-3.3"), "failed\n"); + ok( r==4, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%#.1f"; + r = sprintf(buffer, format,-3.25); + ok(!strcmp(buffer,"-3.3"), "failed\n"); + ok( r==4, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.3f"; + r = sprintf(buffer, format,3.456); + ok(!strcmp(buffer,"3.456"), "failed\n"); + ok( r==5, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.1f"; + r = sprintf(buffer, format,12345.6); + ok(!strcmp(buffer,"12345.6"), "failed\n"); + ok( r==7, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.0f"; + r = sprintf(buffer, format,123456.0); + ok(!strcmp(buffer,"123456"), "failed\n"); + ok( r==6, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.0f"; + r = sprintf(buffer, format,0.5); + ok(!strcmp(buffer,"1"), "failed\n"); + ok( r==1, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.0f"; + r = sprintf(buffer, format,1.5); + ok(!strcmp(buffer,"2"), "failed\n"); + ok( r==1, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.0f"; + r = sprintf(buffer, format,2.5); + ok(!strcmp(buffer,"3"), "failed\n"); + ok( r==1, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.2f"; + r = sprintf(buffer, format,2597.625); + ok(!strcmp(buffer,"2597.63"), "failed\n"); + ok( r==7, "return count wrong\n"); + + memset(buffer,0,sizeof(buffer)); + format = "%.5e"; + r = sprintf(buffer, format,2597.625); + ok(!strcmp(buffer,"2.59763e+003"), "failed, %s\n", buffer); + ok( r==12, "return count wrong %d\n", r); + + memset(buffer,0,sizeof(buffer)); + format = "%.6g"; + r = sprintf(buffer, format,1.0e-9); + ok(!strcmp(buffer,"1e-009"), "failed: %s\n", buffer); + ok( r==6, "return count wrong\n"); + format = "%2.4e"; r = sprintf(buffer, format,8.6); ok(!strcmp(buffer,"8.6000e+000"), "failed\n"); diff --git a/dlls/msvcrt/wine_printfloat.h b/dlls/msvcrt/wine_printfloat.h new file mode 100644 index 0000000..08f5082 --- /dev/null +++ b/dlls/msvcrt/wine_printfloat.h @@ -0,0 +1,246 @@ +/* + * Print float algorithm implementation. + * Derived from code released under the ZLIB license. + * Copyright (c) 2014 Ryan Juckett. + * More details about this implementation on + * http://www.ryanjuckett.com/programming/printing-floating-point-numbers/ + * + * Modified for Wine use by YongHao Hu. + */ +#ifndef WINE__PrintFloat_h +#define WINE__PrintFloat_h +#include "dragon4.h" + +typedef enum { + PrintFloatFormat_Positional, + PrintFloatFormat_Scientific, +} PrintFloatFormat; + +typedef union { + double m_floating_point; + ULONG64 m_integer; +} FloatUnion64; + +static inline UINT32 get_exponent(ULONG64 m_integer) { return (m_integer >> 52) & 0x7FF; } +static inline ULONG64 get_mantissa(ULONG64 m_integer) { return m_integer & 0xFFFFFFFFFFFFFull; } + +static UINT32 format_positional(char *out_buffer, UINT32 buffer_size, ULONG64 mantissa, + INT32 exponent,UINT32 mantissa_high_bit_idx, BOOL has_unequal_margins, + INT32 precision, char format, char alternate) +{ + INT32 print_exponent; + UINT32 num_print_digits, max_print_len=buffer_size - 1, num_fration_digits=0; + + num_print_digits = dragon4(mantissa, exponent, mantissa_high_bit_idx, + has_unequal_margins, CutoffMode_FractionLength, precision, + out_buffer, max_print_len, &print_exponent ); + + if(print_exponent >= 0) { + UINT32 num_whole_digits = print_exponent + 1; + if(num_print_digits < num_whole_digits) { + if(num_whole_digits > max_print_len) + num_whole_digits = max_print_len; + + for( ; num_print_digits (UINT32)num_whole_digits) { + UINT32 max_fration_digits = max_print_len - num_whole_digits - 1; + num_fration_digits = num_print_digits - num_whole_digits; + if(num_fration_digits > max_fration_digits) + num_fration_digits = max_fration_digits; + + memmove(out_buffer+num_whole_digits+1, out_buffer+num_whole_digits, num_fration_digits); + out_buffer[num_whole_digits] = '.'; + num_print_digits = num_whole_digits + 1 + num_fration_digits; + } + }else { + if(max_print_len > 2) { + UINT32 num_fraction_zeros = (UINT32)(-print_exponent) - 1; + UINT32 max_fraction_zeros = max_print_len - 2; + UINT32 digit_start_idx, max_fration_digits, i; + if(num_fraction_zeros > max_fraction_zeros) + num_fraction_zeros = max_fraction_zeros; + + digit_start_idx = 2 + num_fraction_zeros; + + num_fration_digits = num_print_digits; + max_fration_digits = max_print_len - digit_start_idx; + if(num_fration_digits > max_fration_digits) + num_fration_digits = max_fration_digits; + + memmove(out_buffer+digit_start_idx, out_buffer, num_fration_digits); + + for(i=2; i 1) { + out_buffer[1] = '.'; + num_print_digits += 1; + } + + if(max_print_len > 0) { + out_buffer[0] = '0'; + num_print_digits += 1; + } + } + + if(alternate=='#' || (precision>(INT32)num_fration_digits && num_print_digits max_print_len) + total_digits = max_print_len; + + for( ; num_print_digits < total_digits; ++num_print_digits) + out_buffer[num_print_digits] = '0'; + } + } + + out_buffer[num_print_digits] = '\0'; + return num_print_digits; +} + +static UINT32 format_scientific(char *out_buffer, UINT32 buffer_size, ULONG64 mantissa, + INT32 exponent, UINT32 mantissa_high_bit_idx, int has_unequal_margins, + INT32 precision, char format, char alternate) +{ + INT32 print_exponent; + UINT32 num_print_digits, num_fration_digits; + char *cur_out; + + num_print_digits = dragon4(mantissa, exponent, mantissa_high_bit_idx, has_unequal_margins, + CutoffMode_TotalLength, precision + 1, out_buffer, buffer_size, &print_exponent ); + + cur_out = out_buffer; + if(toupper(format)=='G' && alternate!='#') { + while(num_print_digits-1 != 0) { + if(cur_out[num_print_digits-1] == '0') + --num_print_digits; + else + break; + } + } + + if(buffer_size > 1) { + cur_out += 1; + buffer_size -= 1; + } + + num_fration_digits = num_print_digits-1; + if(num_fration_digits==0 && alternate=='#') { + cur_out[0] = '.'; + ++cur_out; + }else if(num_fration_digits>0 && buffer_size>1) { + UINT32 max_fration_digits = buffer_size-2; + if(num_fration_digits > max_fration_digits) + num_fration_digits = max_fration_digits; + + memmove(cur_out+1, cur_out, num_fration_digits); + cur_out[0] = '.'; + cur_out += (1 + num_fration_digits); + buffer_size -= (1 + num_fration_digits); + } + + if(precision>(INT32)num_fration_digits && buffer_size>1) { + UINT32 num_zeros; + char *end; + if(toupper(format)!='G' || alternate=='#') { + if(num_fration_digits == 0) { + *cur_out = '.'; + ++cur_out; + --buffer_size; + } + + num_zeros = precision - num_fration_digits; + if(num_zeros > buffer_size-1) + num_zeros = buffer_size-1; + + for(end=cur_out+num_zeros; cur_out 1) { + char exponent_buffer[5]; + UINT32 hundreds_place, tens_place, ones_place; + UINT32 max_exponent_size, exponent_size; + if(format=='e' || format=='g') + exponent_buffer[0] = 'e'; + else + exponent_buffer[0] = 'E'; + + if(print_exponent >= 0) { + exponent_buffer[1] = '+'; + }else { + exponent_buffer[1] = '-'; + print_exponent = -print_exponent; + } + + hundreds_place = print_exponent / 100; + tens_place = (print_exponent-hundreds_place*100) / 10; + ones_place = (print_exponent-hundreds_place*100 - tens_place*10); + + exponent_buffer[2] = (char)('0' + hundreds_place); + exponent_buffer[3] = (char)('0' + tens_place); + exponent_buffer[4] = (char)('0' + ones_place); + + max_exponent_size = buffer_size-1; + exponent_size = (5 < max_exponent_size) ? 5 : max_exponent_size; + memcpy(cur_out, exponent_buffer, exponent_size); + + cur_out += exponent_size; + buffer_size -= exponent_size; + } + + cur_out[0] = '\0'; + return cur_out - out_buffer; +} + +static UINT32 wine_print_double(char *out_buffer, UINT32 buffer_size, double value, + PrintFloatFormat format, INT32 precision, char flag_format, char alternate) +{ + FloatUnion64 float_union; + UINT32 float_exponent, mantissa_high_bit_idx; + ULONG64 float_mantissa, mantissa; + INT32 exponent; + BOOL has_unequal_margins; + if(buffer_size == 0) { + return 0; + }else if(buffer_size == 1) { + out_buffer[0] = '\0'; + return 0; + } + + float_union.m_floating_point = value; + float_exponent = get_exponent(float_union.m_integer); + float_mantissa = get_mantissa(float_union.m_integer); + + if(float_exponent != 0) { + mantissa = (1ull << 52) | float_mantissa; + exponent = float_exponent - 1023 - 52; + mantissa_high_bit_idx = 52; + has_unequal_margins = (float_exponent != 1) && (float_mantissa == 0); + }else { + mantissa = float_mantissa; + exponent = 1 - 1023 - 52; + mantissa_high_bit_idx = LogBase2(mantissa); + has_unequal_margins = 0; + } + + if(format == PrintFloatFormat_Positional) + return format_positional(out_buffer, buffer_size, mantissa, + exponent, mantissa_high_bit_idx, has_unequal_margins, + precision,flag_format, alternate); + else + return format_scientific(out_buffer, buffer_size, mantissa, + exponent, mantissa_high_bit_idx, has_unequal_margins, + precision, flag_format, alternate); +} + +#endif