numeric.ex (4217B)
1 defmodule Postgrex.Extensions.Numeric do 2 @moduledoc false 3 import Postgrex.BinaryUtils, warn: false 4 use Postgrex.BinaryExtension, send: "numeric_send" 5 6 def encode(_) do 7 quote location: :keep, generated: true do 8 %Decimal{} = decimal -> 9 data = unquote(__MODULE__).encode_numeric(decimal) 10 [<<IO.iodata_length(data)::int32()>> | data] 11 12 n when is_float(n) -> 13 data = unquote(__MODULE__).encode_numeric(Decimal.from_float(n)) 14 [<<IO.iodata_length(data)::int32()>> | data] 15 16 n when is_integer(n) -> 17 data = unquote(__MODULE__).encode_numeric(Decimal.new(n)) 18 [<<IO.iodata_length(data)::int32()>> | data] 19 end 20 end 21 22 def decode(_) do 23 quote location: :keep do 24 <<len::int32(), data::binary-size(len)>> -> 25 unquote(__MODULE__).decode_numeric(data) 26 end 27 end 28 29 ## Helpers 30 31 # TODO: remove qNaN and sNaN when we depend on Decimal 2.0 32 def encode_numeric(%Decimal{coef: coef}) when coef in [:NaN, :qNaN, :sNaN] do 33 <<0::int16(), 0::int16(), 0xC000::uint16(), 0::int16()>> 34 end 35 36 def encode_numeric(%Decimal{sign: 1, coef: :inf}) do 37 <<0::int16(), 0::int16(), 0xD000::uint16(), 0::int16()>> 38 end 39 40 def encode_numeric(%Decimal{sign: -1, coef: :inf}) do 41 <<0::int16(), 0::int16(), 0xF000::uint16(), 0::int16()>> 42 end 43 44 def encode_numeric(%Decimal{sign: sign, coef: coef, exp: exp}) do 45 sign = encode_sign(sign) 46 scale = -exp 47 48 {integer, float, scale} = split_parts(coef, scale) 49 integer_digits = encode_digits(integer, []) 50 float_digits = encode_float(float, scale) 51 digits = integer_digits ++ float_digits 52 53 num_digits = length(digits) 54 weight = max(length(integer_digits) - 1, 0) 55 56 bin = for digit <- digits, into: "", do: <<digit::uint16()>> 57 [<<num_digits::int16(), weight::int16(), sign::uint16(), scale::int16()>> | bin] 58 end 59 60 defp encode_sign(1), do: 0x0000 61 defp encode_sign(-1), do: 0x4000 62 63 defp split_parts(coef, scale) when scale >= 0 do 64 integer_base = pow10(scale) 65 {div(coef, integer_base), rem(coef, integer_base), scale} 66 end 67 68 defp split_parts(coef, scale) when scale < 0 do 69 integer_base = pow10(-scale) 70 {coef * integer_base, 0, 0} 71 end 72 73 defp encode_float(float, scale) do 74 pending = pending_scale(float, scale) 75 float_prefix = div(pending, 4) 76 float_suffix = 4 - rem(scale, 4) 77 float = float * pow10(float_suffix) 78 List.duplicate(0, float_prefix) ++ encode_digits(float, []) 79 end 80 81 defp pending_scale(0, scale), do: scale 82 defp pending_scale(num, scale), do: pending_scale(div(num, 10), scale - 1) 83 84 defp encode_digits(coef, digits) when coef < 10_000 do 85 [coef | digits] 86 end 87 88 defp encode_digits(coef, digits) do 89 digit = rem(coef, 10_000) 90 coef = div(coef, 10_000) 91 encode_digits(coef, [digit | digits]) 92 end 93 94 def decode_numeric( 95 <<ndigits::int16(), weight::int16(), sign::uint16(), scale::int16(), tail::binary>> 96 ) do 97 decode_numeric(ndigits, weight, sign, scale, tail) 98 end 99 100 @nan Decimal.new("NaN") 101 @positive_inf Decimal.new("Inf") 102 @negative_inf Decimal.new("-Inf") 103 104 defp decode_numeric(0, _weight, 0xC000, _scale, "") do 105 @nan 106 end 107 108 defp decode_numeric(0, _weight, 0xD000, _scale, "") do 109 @positive_inf 110 end 111 112 defp decode_numeric(0, _weight, 0xF000, _scale, "") do 113 @negative_inf 114 end 115 116 defp decode_numeric(_num_digits, weight, sign, scale, bin) do 117 {value, weight} = decode_numeric_int(bin, weight, 0) 118 sign = decode_sign(sign) 119 coef = scale(value, (weight + 1) * 4 + scale) 120 Decimal.new(sign, coef, -scale) 121 end 122 123 defp decode_sign(0x0000), do: 1 124 defp decode_sign(0x4000), do: -1 125 126 defp scale(coef, 0), do: coef 127 defp scale(coef, diff) when diff < 0, do: div(coef, pow10(-diff)) 128 defp scale(coef, diff) when diff > 0, do: coef * pow10(diff) 129 130 Enum.reduce(0..100, 1, fn x, acc -> 131 defp pow10(unquote(x)), do: unquote(acc) 132 acc * 10 133 end) 134 135 defp pow10(num) when num > 100, do: pow10(100) * pow10(num - 100) 136 137 defp decode_numeric_int("", weight, acc), do: {acc, weight} 138 139 defp decode_numeric_int(<<digit::int16(), tail::binary>>, weight, acc) do 140 acc = acc * 10_000 + digit 141 decode_numeric_int(tail, weight - 1, acc) 142 end 143 end