Округление до произвольного числа значащих цифр
Как вы можете круглый любой число (не только целые числа > 0) до N значащих цифр?
например, если я хочу округлить до трех значащих цифр, я ищу формулу, которая могла бы взять:
1,239,451 и вернуть 1,240,000
12.1257 и вернуть 12.1
.0681 и возвращения .0681
5 и вернуть 5
естественно, алгоритм не должен быть жестко запрограммирован только для обработки N из 3, хотя это было бы для начала.
17 ответов:
вот тот же код в Java без ошибки 12.100000000000001 другие ответы имеют
Я также удалил повторяющийся код, изменил
powerк целому типу, чтобы предотвратить плавающие проблемы, когдаn - dсделано, и сделал длинный промежуточный более яснымошибка была вызвана умножением большого числа на малое число. Вместо этого я делю два числа одинакового размера.
EDIT
Исправлено больше ошибок. Добавлена проверка на 0, как это было результат в НАН. Сделал функцию фактически работать с отрицательными числами (исходный код не обрабатывает отрицательные числа, потому что журнал отрицательного числа является комплексным числом)public static double roundToSignificantFigures(double num, int n) { if(num == 0) { return 0; } final double d = Math.ceil(Math.log10(num < 0 ? -num: num)); final int power = n - (int) d; final double magnitude = Math.pow(10, power); final long shifted = Math.round(num*magnitude); return shifted/magnitude; }
вот короткая и сладкая реализация JavaScript:
function sigFigs(n, sig) { var mult = Math.pow(10, sig - Math.floor(Math.log(n) / Math.LN10) - 1); return Math.round(n * mult) / mult; } alert(sigFigs(1234567, 3)); // Gives 1230000 alert(sigFigs(0.06805, 3)); // Gives 0.0681 alert(sigFigs(5, 3)); // Gives 5
резюме:
double roundit(double num, double N) { double d = log10(num); double power; if (num > 0) { d = ceil(d); power = -(d-N); } else { d = floor(d); power = -(d-N); } return (int)(num * pow(10.0, power) + 0.5) * pow(10.0, -power); }
таким образом, вам нужно найти десятичный знак первой ненулевой цифры, затем сохранить следующие N-1 цифры, а затем округлить N-ю цифру на основе остальных.
мы можем использовать журнал, чтобы сделать первый.
log 1239451 = 6.09 log 12.1257 = 1.08 log 0.0681 = -1.16Итак, для чисел > 0, возьмите ceil журнала. Для чисел
теперь у нас есть цифры
d: 7 в первом случае, 2 во втором, -2 в третьем.мы должны вокруг
(d-N)th цифра. Что-то вроде:double roundedrest = num * pow(10, -(d-N)); pow(1239451, -4) = 123.9451 pow(12.1257, 1) = 121.257 pow(0.0681, 4) = 681затем выполните стандартное округление:
roundedrest = (int)(roundedrest + 0.5);и отменить военнопленных.
roundednum = pow(roundedrest, -(power))где мощность-это мощность, рассчитанная выше.
о точности: ответ Pyrolistical действительно ближе к реальному результату. Но обратите внимание, что вы не можете представить 12.1 точно в любом случае. Если вы напечатаете ответы следующим образом:
System.out.println(new BigDecimal(n));ответы являются:
Pyro's: 12.0999999999999996447286321199499070644378662109375 Mine: 12.10000000000000142108547152020037174224853515625 Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375Итак, используйте ответ Пиро!
это не" короткая и сладкая " реализация JavaScript
Number(n).toPrecision(sig)например
alert(Number(12345).toPrecision(3)?
Извините, я не шучу здесь, это просто использование функции "roundit" от Claudiu и the .toPrecision в JavaScript дает мне разные результаты, но только в округлении последней цифры.
JavaScript:
Number(8.14301).toPrecision(4) == 8.143.NET
roundit(8.14301,4) == 8.144
Пиролистические (очень приятно!) решение проблемы. Максимальное двойное значение в Java составляет порядка 10^308, в то время как минимальное значение составляет порядка 10^-324. Поэтому вы можете столкнуться с проблемами при применении функции
roundToSignificantFiguresк чему-то, что находится в пределах нескольких степеней десяти изDouble.MIN_VALUE. Например, при вызовеroundToSignificantFigures(1.234E-310, 3);переменная
powerбудет иметь значение 3 - (-309) = 312. Следовательно, переменнаяmagnitudeстанетInfinity, и с тех пор это все мусор. К счастью, это не является непреодолимой проблемой: это только факторmagnitudeвот митинговый. Что действительно имеет значение, так это продуктnum * magnitude, и это не переполнение. Одним из способов решения этой проблемы является разбиение умножения на коэффициентmagintudeв два этапа:public static double roundToNumberOfSignificantDigits(double num, int n) { final double maxPowerOfTen = Math.floor(Math.log10(Double.MAX_VALUE)); if(num == 0) { return 0; } final double d = Math.ceil(Math.log10(num < 0 ? -num: num)); final int power = n - (int) d; double firstMagnitudeFactor = 1.0; double secondMagnitudeFactor = 1.0; if (power > maxPowerOfTen) { firstMagnitudeFactor = Math.pow(10.0, maxPowerOfTen); secondMagnitudeFactor = Math.pow(10.0, (double) power - maxPowerOfTen); } else { firstMagnitudeFactor = Math.pow(10.0, (double) power); } double toBeRounded = num * firstMagnitudeFactor; toBeRounded *= secondMagnitudeFactor; final long shifted = Math.round(toBeRounded); double rounded = ((double) shifted) / firstMagnitudeFactor; rounded /= secondMagnitudeFactor; return rounded; }
Как насчет этого решения java:
double roundToSignificantFigure(double num, int precision){ return new BigDecimal(num) .round(new MathContext(precision, RoundingMode.HALF_EVEN)) .doubleValue(); }
вот модифицированная версия JavaScript Ates, которая обрабатывает отрицательные числа.
function sigFigs(n, sig) { if ( n === 0 ) return 0 var mult = Math.pow(10, sig - Math.floor(Math.log(n < 0 ? -n: n) / Math.LN10) - 1); return Math.round(n * mult) / mult; }
Это произошло на 5 лет позже, но хотя я поделюсь для других, все еще имеющих ту же проблему. Мне это нравится, потому что это просто и никаких вычислений на стороне кода. Смотрите встроенные методы отображения значимых фигур для получения дополнительной информации.
Это если вы просто хотите, чтобы распечатать его.
public String toSignificantFiguresString(BigDecimal bd, int significantFigures){ return String.format("%."+significantFigures+"G", bd); }Это если вы хотите, чтобы преобразовать его:
public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){ String s = String.format("%."+significantFigures+"G", bd); BigDecimal result = new BigDecimal(s); return result; }вот пример этого в действии:
BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);
вы пробовали просто кодировать его так, как вы бы сделали это вручную?
- преобразовать число в строку
- начиная с начала строка, количество цифр-ведущие нули не являются важно, все остальное есть.
- когда вы доберетесь до" n-й " цифры, заглянуть вперед на следующую цифру и если это 5 или выше, округлить.
- замените все конечные цифры на нули.
[поправил, 2009-10-26]
по существу, для n значимых частичная цифры:
• умножьте число на 10N
• Залить 0,5
* Усечь цифры дроби (т. е., усечь результат в целое число)
• Делим на 10Nдля n значительное Интеграл (не дробных) чисел:
• разделить число на 10N
• Залить 0,5
* Усечь цифры дроби (т. е., усечь результат в целое число)
• Умножаем на 10Nвы можете сделать это на любом калькуляторе, например, который имеет оператор "INT" (integer truncation).
/** * Set Significant Digits. * @param value value * @param digits digits * @return */ public static BigDecimal setSignificantDigits(BigDecimal value, int digits) { //# Start with the leftmost non-zero digit (e.g. the "1" in 1200, or the "2" in 0.0256). //# Keep n digits. Replace the rest with zeros. //# Round up by one if appropriate. int p = value.precision(); int s = value.scale(); if (p < digits) { value = value.setScale(s + digits - p); //, RoundingMode.HALF_UP } value = value.movePointRight(s).movePointLeft(p - digits).setScale(0, RoundingMode.HALF_UP) .movePointRight(p - digits).movePointLeft(s); s = (s > (p - digits)) ? (s - (p - digits)) : 0; return value.setScale(s); }
вот Пиролистический (в настоящее время верхний ответ) код в Visual Basic.NET, если кому-то это нужно:
Public Shared Function roundToSignificantDigits(ByVal num As Double, ByVal n As Integer) As Double If (num = 0) Then Return 0 End If Dim d As Double = Math.Ceiling(Math.Log10(If(num < 0, -num, num))) Dim power As Integer = n - CInt(d) Dim magnitude As Double = Math.Pow(10, power) Dim shifted As Double = Math.Round(num * magnitude) Return shifted / magnitude End Function
JavaScript:
Number( my_number.toPrecision(3) );The
Numberфункция изменит вывод формы"8.143e+5"до"814300".
Это тот, который я придумал в VB:
Function SF(n As Double, SigFigs As Integer) Dim l As Integer = n.ToString.Length n = n / 10 ^ (l - SigFigs) n = Math.Round(n) n = n * 10 ^ (l - SigFigs) Return n End Function
return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();
мне нужно было это в Go, что было немного сложно из-за отсутствия стандартной библиотеки Go
math.Round()(перед go1.10). Так что мне тоже пришлось это взбить. Вот мой перевод отличный ответ Пиролистика:// TODO: replace in go1.10 with math.Round() func round(x float64) float64 { return float64(int64(x + 0.5)) } // SignificantDigits rounds a float64 to digits significant digits. // Translated from Java at https://stackoverflow.com/a/1581007/1068283 func SignificantDigits(x float64, digits int) float64 { if x == 0 { return 0 } power := digits - int(math.Ceil(math.Log10(math.Abs(x)))) magnitude := math.Pow(10, float64(power)) shifted := round(x * magnitude) return shifted / magnitude }
public static double roundToSignificantDigits(double num, int n) { return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString()); }этот код использует встроенную функцию форматирования, которая превращается в функцию округления
Comments