Как сохранить результат десятичного вычисления в mysql и вернуть его обратно в том виде, в каком он был в памяти

Документация MySQL говорит:

Типы DECIMAL и NUMERIC хранят точные числовые значения данных. Эти типы используются, когда важно сохранить точную точность, например, с денежными данными.

Я сделал этот тест на столбце decimal_column DECIMAL(31,30).

insert into tests (decimal_column) values(1/3);

затем проверка того, что было сохранено, дает это

select * from tests ;
> result : 0.333333333000000000000000000000

затем обращение математической операции с этим запросом дает это

select decimal_column*3 from test;
> result: 0.999999999000000000000000000000

Я ожидал получить целое число «1», как мы делаем это на наших калькуляторах и в таблице Excel! как это

 #calculator or excel sheet
 >input: 1 / 3
 >result: 0.3333333333333333333333333333333333
 >input: * 3
 >result: 1

1- почему MySQL не сохранил точное двоичное представление (1/3), чтобы я мог снова использовать этот результат в своих вычислениях, поскольку они находятся в памяти, как калькулятор или лист Excel.

2- Как сохранить в mysql результат (1/3) в том виде, в каком он находится в памяти во время вычисления, чтобы я мог получить обратно точное значение и сделать что-то вроде 3 * $storedValue, чтобы получить 1 как целое число, как мы это делаем в калькуляторе или листе Excel.


person Accountant م    schedule 16.03.2017    source источник


Ответы (3)


Проблема не в хранилище. В вашем примере значение было нарушено перед сохранением его в таблице.

К сожалению, если вы напишете 1/3, это будет вычислено (и вставлено) с использованием представления по умолчанию:

SELECT 1/3

0.333333333

который, как видите, имеет недостаточную точность.

Дополнительная проблема заключается в том, что когда вы отправляете константу (1 или 3) на сервер, вы делаете это с помощью библиотеки или коннектора, которые могут позволить себе вольности с ценность. Например, он может считать, что «1» и «3» — целые числа, и их результат следует рассматривать как целое число. Таким образом, вы получаете «1/3 = 0», но «1./3 = 0,333333», потому что точка в «1». заставляет коннектор понять, что ему нужно использовать плавающую точку по умолчанию. И вы получаете только шесть 3-х, потому что «плавающая точка по умолчанию» соединителя имеет 6 цифр. Затем вы сохраняете его в базе данных, но уже слишком поздно. Вы сохраняете с высокой точностью значение, которое было усечено до низкой точности.

Вы можете попробовать приводить константы с самого начала. Вместо «1» вы используете приведение 1 к достаточно большому десятичному знаку. Я использую ваши 31,30 здесь, но убедитесь, что вам не нужно хранить большие числа. Возможно, "31,20" было бы лучше.

mysql> SELECT 1/3 UNION SELECT CAST(1 AS DECIMAL(31,30))/CAST(3 AS DECIMAL(31,30));
+----------------------------------+
| 1/3                              |
+----------------------------------+
| 0.333333333000000000000000000000 |
| 0.333333333333333333333333333333 |
+----------------------------------+
2 rows in set (0.00 sec)

Это очень неудобно, но результаты должны быть лучше. Кроме того, я думаю, что в выражении необходимо привести только одно значение one; Затем MySQL будет продвигать все задействованные количества по мере необходимости. Таким образом, может быть достаточно добавления CAST(0 AS DECIMAL(x,y)) к суммам и CAST(1 AS DECIMAL(x,y)) к умножениям.

mysql> SELECT 3*CAST(1 AS DECIMAL(31,30))/CAST(3 AS DECIMAL(31,30));
+-------------------------------------------------------+
| 3*CAST(1 AS DECIMAL(31,30))/CAST(3 AS DECIMAL(31,30)) |
+-------------------------------------------------------+
|                      1.000000000000000000000000000000 |
+-------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CAST(1 AS DECIMAL(31,30))*1/3;
+----------------------------------+
| CAST(1 AS DECIMAL(31,30))*1/3    |
+----------------------------------+
| 0.333333333333333333333333333333 |
+----------------------------------+
1 row in set (0.00 sec)

Обратите внимание, что это не работает, потому что умножение имеет более высокий приоритет:

mysql> SELECT CAST(0 AS DECIMAL(31,30))+1/3;
+----------------------------------+
| CAST(0 AS DECIMAL(31,30))+1/3    |
+----------------------------------+
| 0.333333333000000000000000000000 |
+----------------------------------+
1 row in set (0.00 sec)
person LSerni    schedule 16.03.2017
comment
благодарю вас. но все же что-то меня смущает. почему, когда я делаю в листе excel 1/3, умножаю результат на 3. Я получаю 1, когда я форматирую этот результат 1 для отображения десятичных чисел, которые я все еще вижу (1.0000000000000....) вообще без дробей для более чем 70 цифр, все НУЛИ. ! - person Accountant م; 16.03.2017
comment
В Excel есть несколько полезных приемов форматирования: то, что он позволяет вам увидеть, не то, что внутри. И пытается понять. Например, 16.01.16 буквально 1 в арифметике — это 16 января 2016 года в моей копии Excel. И 16/1/16+1 - это не 2, а что-то вроде 46250 или около того - количество юлианских дней с даты, которую я не помню. В других случаях Excel исправит то, что он считает ошибкой округления... правильно это делать или нет. Подводя итог: не берите Excel (или любую другую программу [Microsoft]) в качестве примера того, как должна работать математика (или программистская математика). - person LSerni; 16.03.2017
comment
господи, все эти годы работы с калькуляторами я думал, что когда я делаю 1/3, а затем обращаю процесс result * 3, я получаю то же самое 1, с которого начал. Большое вам спасибо за ваше время. Я собираюсь с интересом прочитать это руководство по плавающей запятой. - person Accountant م; 16.03.2017

Это зависит от того, для чего вам нужна информация.

Если он хранится для расчета и хранения, вычислите результат (0,33 вместо 1/3) и сохраните его как десятичное число (1,5). Но вы не можете легко вычислить его в обратном направлении, если хотите отобразить его.

Если он сохраняется для отображения в более позднее время, но никогда не будет изменен снова (или, по крайней мере, не быстро), вы можете сохранить его как varchar, но это нарушит сортировку.

Или вы можете хранить различные элементы (положительные, отрицательные, итоговые и т. д.) как десятичные числа (5,0) и отображать/вычислять их при использовании.

И, конечно же, вы можете комбинировать вышеперечисленное, если хотите сократить время вычислений при выборе.

person Ashwin Golani    schedule 16.03.2017

MySQL выполняет свою внутреннюю дробную арифметику, используя 64-битные числа IEEE 754 с плавающей запятой.

Как правило, они приблизительны. Совершенно неразумно ожидать, что IEEE с плавающей запятой или десятичной арифметикой, если на то пошло, достигнет точного равенства при выполнении

   3*(1/3) == 1

Это просто не то, как работает компьютерная арифметика.

Также нет способа сохранить точное представление значения 1/3, если только вы не используете экзотическую вычислительную систему, которая хранит рациональные (дробные) числа в виде пар (числитель, знаменатель). MySQL не является такой системой.

Большинство калькуляторов неявно округляют свои результаты до количества цифр, которые они могут отображать. Excel также содержит модуль форматирования. Вы можете выбрать формат ячейки, нажав -1. Модуль форматирования округляет эти числа с плавающей запятой. Вы можете добиться того же эффекта в MySQL, используя ROUND( ) функция. Эта функция не изменяет сохраненное значение, но визуализирует его таким образом, чтобы скрыть крошечные ошибки, присущие плавающей арифметике IEEE 754.

SELECT ROUND(decimal_column, 2) AS decimal_column

(Разве бухгалтеры не должны изучать этот материал в школе?)

person O. Jones    schedule 16.03.2017
comment
поэтому, когда я делаю на своем калькуляторе 1/3, затем умножаю результат на 3 *3, и калькулятор дает мне 1 . означает ли это, что это 1 не совсем то целое значение 1, с которого я начал вычисление? это скорее что-то вроде 1.0000000000000000000000000000000000000000001 ? - person Accountant م; 16.03.2017
comment
Разве бухгалтеры не должны изучать этот материал в школе? . Нет, это не так. то, как числа с плавающей запятой представлены внутри двоичной системы, выходит за рамки бухгалтерского учета. это до сих пор даже смущает некоторых программистов. - person Accountant م; 16.03.2017