1 /* 2 * PostgreSQL numeric format 3 * 4 * Copyright: © 2014 DSoftOut 5 * Authors: NCrashed <ncrashed@gmail.com> 6 */ 7 module dpq2.conv.numeric; 8 9 private pure // inner representation from libpq sources 10 { 11 alias NumericDigit = ushort; 12 enum DEC_DIGITS = 4; 13 enum NUMERIC_NEG = 0x4000; 14 enum NUMERIC_NAN = 0xC000; 15 16 struct NumericVar 17 { 18 int weight; 19 int sign; 20 int dscale; 21 NumericDigit[] digits; 22 } 23 24 string numeric_out(in NumericVar num) 25 { 26 string str; 27 28 if(num.sign == NUMERIC_NAN) 29 { 30 return "NaN"; 31 } 32 33 str = get_str_from_var(num); 34 35 return str; 36 } 37 38 /* 39 * get_str_from_var() - 40 * 41 * Convert a var to text representation (guts of numeric_out). 42 * The var is displayed to the number of digits indicated by its dscale. 43 * Returns a palloc'd string. 44 */ 45 string get_str_from_var(in NumericVar var) 46 { 47 int dscale; 48 ubyte[] str; 49 ubyte* cp; 50 ubyte* endcp; 51 int i; 52 int d; 53 NumericDigit dig; 54 55 static if(DEC_DIGITS > 1) 56 { 57 NumericDigit d1; 58 } 59 60 dscale = var.dscale; 61 62 /* 63 * Allocate space for the result. 64 * 65 * i is set to the # of decimal digits before decimal point. dscale is the 66 * # of decimal digits we will print after decimal point. We may generate 67 * as many as DEC_DIGITS-1 excess digits at the end, and in addition we 68 * need room for sign, decimal point, null terminator. 69 */ 70 i = (var.weight + 1) * DEC_DIGITS; 71 if (i <= 0) 72 i = 1; 73 74 str = new ubyte[i + dscale + DEC_DIGITS + 2]; 75 cp = str.ptr; 76 77 /* 78 * Output a dash for negative values 79 */ 80 if (var.sign == NUMERIC_NEG) 81 *cp++ = '-'; 82 83 /* 84 * Output all digits before the decimal point 85 */ 86 if (var.weight < 0) 87 { 88 d = var.weight + 1; 89 *cp++ = '0'; 90 } 91 else 92 { 93 for (d = 0; d <= var.weight; d++) 94 { 95 dig = (d < var.digits.length) ? var.digits[d] : 0; 96 /* In the first digit, suppress extra leading decimal zeroes */ 97 static if(DEC_DIGITS == 4) 98 { 99 bool putit = (d > 0); 100 101 d1 = dig / 1000; 102 dig -= d1 * 1000; 103 putit |= (d1 > 0); 104 if (putit) 105 *cp++ = cast(char)(d1 + '0'); 106 d1 = dig / 100; 107 dig -= d1 * 100; 108 putit |= (d1 > 0); 109 if (putit) 110 *cp++ = cast(char)(d1 + '0'); 111 d1 = dig / 10; 112 dig -= d1 * 10; 113 putit |= (d1 > 0); 114 if (putit) 115 *cp++ = cast(char)(d1 + '0'); 116 *cp++ = cast(char)(dig + '0'); 117 } 118 else static if(DEC_DIGITS == 2) 119 { 120 d1 = dig / 10; 121 dig -= d1 * 10; 122 if (d1 > 0 || d > 0) 123 *cp++ = cast(char)(d1 + '0'); 124 *cp++ = cast(char)(dig + '0'); 125 } 126 else static if(DEC_DIGITS == 1) 127 { 128 *cp++ = cast(char)(dig + '0'); 129 } 130 else pragma(error, "unsupported NBASE"); 131 } 132 } 133 134 /* 135 * If requested, output a decimal point and all the digits that follow it. 136 * We initially put out a multiple of DEC_DIGITS digits, then truncate if 137 * needed. 138 */ 139 if (dscale > 0) 140 { 141 *cp++ = '.'; 142 endcp = cp + dscale; 143 for (i = 0; i < dscale; d++, i += DEC_DIGITS) 144 { 145 dig = (d >= 0 && d < var.digits.length) ? var.digits[d] : 0; 146 static if(DEC_DIGITS == 4) 147 { 148 d1 = dig / 1000; 149 dig -= d1 * 1000; 150 *cp++ = cast(char)(d1 + '0'); 151 d1 = dig / 100; 152 dig -= d1 * 100; 153 *cp++ = cast(char)(d1 + '0'); 154 d1 = dig / 10; 155 dig -= d1 * 10; 156 *cp++ = cast(char)(d1 + '0'); 157 *cp++ = cast(char)(dig + '0'); 158 } 159 else static if(DEC_DIGITS == 2) 160 { 161 d1 = dig / 10; 162 dig -= d1 * 10; 163 *cp++ = cast(char)(d1 + '0'); 164 *cp++ = cast(char)(dig + '0'); 165 } 166 else static if(DEC_DIGITS == 1) 167 { 168 *cp++ = cast(char)(dig + '0'); 169 } 170 else pragma(error, "unsupported NBASE"); 171 } 172 cp = endcp; 173 } 174 175 /* 176 * terminate the string and return it 177 */ 178 *cp = '\0'; 179 180 return (cast(char*) str).fromStringz; 181 } 182 } 183 184 import std.conv: to; 185 import std.string: fromStringz; 186 import std.bitmanip: bigEndianToNative; 187 188 package string rawValueToNumeric(in ubyte[] v) pure 189 { 190 import dpq2.result: ValueConvException, ConvExceptionType; 191 192 struct NumericVar_net // network byte order 193 { 194 ubyte[2] num; // num of digits 195 ubyte[2] weight; 196 ubyte[2] sign; 197 ubyte[2] dscale; 198 } 199 200 if(!(v.length >= NumericVar_net.sizeof)) 201 throw new ValueConvException(ConvExceptionType.SIZE_MISMATCH, 202 "Value length ("~to!string(v.length)~") less than it is possible for numeric type", 203 __FILE__, __LINE__); 204 205 NumericVar_net* h = cast(NumericVar_net*) v.ptr; 206 207 NumericVar res; 208 res.weight = bigEndianToNative!short(h.weight); 209 res.sign = bigEndianToNative!ushort(h.sign); 210 res.dscale = bigEndianToNative!ushort(h.dscale); 211 212 auto len = (v.length - NumericVar_net.sizeof) / NumericDigit.sizeof; 213 214 res.digits = new NumericDigit[len]; 215 216 size_t offset = NumericVar_net.sizeof; 217 foreach(i; 0 .. len) 218 { 219 res.digits[i] = bigEndianToNative!NumericDigit( 220 (&(v[offset]))[0..NumericDigit.sizeof] 221 ); 222 offset += NumericDigit.sizeof; 223 } 224 225 return numeric_out(res); 226 }